@hot-updater/server 0.21.10 → 0.21.12
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/db/index.cjs +16 -276
- package/dist/db/index.d.cts +10 -48
- package/dist/db/index.d.ts +10 -48
- package/dist/db/index.js +17 -272
- package/dist/db/ormCore.cjs +278 -0
- package/dist/db/ormCore.d.cts +26 -0
- package/dist/db/ormCore.d.ts +26 -0
- package/dist/db/ormCore.js +273 -0
- package/dist/db/pluginCore.cjs +71 -0
- package/dist/db/pluginCore.js +69 -0
- package/dist/db/types.cjs +12 -0
- package/dist/db/types.d.cts +31 -0
- package/dist/db/types.d.ts +31 -0
- package/dist/db/types.js +10 -0
- package/dist/index.cjs +2 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/package.json +10 -9
- package/src/db/index.spec.ts +100 -2
- package/src/db/index.ts +49 -448
- package/src/db/ormCore.ts +441 -0
- package/src/db/pluginCore.ts +119 -0
- package/src/db/types.ts +57 -0
- package/src/handler-standalone-integration.spec.ts +9 -9
package/src/db/index.spec.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { r2Storage } from "@hot-updater/cloudflare";
|
|
|
4
4
|
import type { Bundle, GetBundlesArgs, UpdateInfo } from "@hot-updater/core";
|
|
5
5
|
import { NIL_UUID } from "@hot-updater/core";
|
|
6
6
|
import { firebaseStorage } from "@hot-updater/firebase";
|
|
7
|
-
import { kyselyAdapter } from "@hot-updater/server/adapters/kysely";
|
|
8
7
|
import { supabaseStorage } from "@hot-updater/supabase";
|
|
9
8
|
import { setupGetUpdateInfoTestSuite } from "@hot-updater/test-utils";
|
|
10
9
|
import { Kysely } from "kysely";
|
|
@@ -19,6 +18,7 @@ import {
|
|
|
19
18
|
it,
|
|
20
19
|
vi,
|
|
21
20
|
} from "vitest";
|
|
21
|
+
import { kyselyAdapter } from "../adapters/kysely";
|
|
22
22
|
import { createHotUpdater } from "./index";
|
|
23
23
|
|
|
24
24
|
describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
@@ -31,7 +31,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
31
31
|
db: kysely,
|
|
32
32
|
provider: "postgresql",
|
|
33
33
|
}),
|
|
34
|
-
|
|
34
|
+
storages: [
|
|
35
35
|
s3Storage({
|
|
36
36
|
region: "us-east-1",
|
|
37
37
|
credentials: {
|
|
@@ -88,6 +88,104 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
88
88
|
|
|
89
89
|
setupGetUpdateInfoTestSuite({ getUpdateInfo });
|
|
90
90
|
|
|
91
|
+
describe("getBundleById", () => {
|
|
92
|
+
it("should retrieve bundle by id without Prisma validation errors", async () => {
|
|
93
|
+
const bundle: Bundle = {
|
|
94
|
+
id: "00000000-0000-0000-0000-000000000010",
|
|
95
|
+
platform: "ios",
|
|
96
|
+
shouldForceUpdate: false,
|
|
97
|
+
enabled: true,
|
|
98
|
+
fileHash: "test-hash",
|
|
99
|
+
gitCommitHash: null,
|
|
100
|
+
message: "Test bundle for getBundleById",
|
|
101
|
+
channel: "production",
|
|
102
|
+
storageUri: "s3://test-bucket/test.zip",
|
|
103
|
+
targetAppVersion: "1.0.0",
|
|
104
|
+
fingerprintHash: null,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
await hotUpdater.insertBundle(bundle);
|
|
108
|
+
|
|
109
|
+
// This should not throw a Prisma validation error
|
|
110
|
+
const retrieved = await hotUpdater.getBundleById(bundle.id);
|
|
111
|
+
|
|
112
|
+
expect(retrieved).not.toBeNull();
|
|
113
|
+
expect(retrieved?.id).toBe(bundle.id);
|
|
114
|
+
expect(retrieved?.platform).toBe(bundle.platform);
|
|
115
|
+
expect(retrieved?.fileHash).toBe(bundle.fileHash);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should return null for non-existent bundle id", async () => {
|
|
119
|
+
const retrieved = await hotUpdater.getBundleById(
|
|
120
|
+
"99999999-9999-9999-9999-999999999999",
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect(retrieved).toBeNull();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("getChannels", () => {
|
|
128
|
+
it("should retrieve all unique channels without Prisma validation errors", async () => {
|
|
129
|
+
const bundles: Bundle[] = [
|
|
130
|
+
{
|
|
131
|
+
id: "00000000-0000-0000-0000-000000000020",
|
|
132
|
+
platform: "ios",
|
|
133
|
+
shouldForceUpdate: false,
|
|
134
|
+
enabled: true,
|
|
135
|
+
fileHash: "hash1",
|
|
136
|
+
gitCommitHash: null,
|
|
137
|
+
message: "Bundle 1",
|
|
138
|
+
channel: "production",
|
|
139
|
+
storageUri: "s3://test/1.zip",
|
|
140
|
+
targetAppVersion: "1.0.0",
|
|
141
|
+
fingerprintHash: null,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "00000000-0000-0000-0000-000000000021",
|
|
145
|
+
platform: "android",
|
|
146
|
+
shouldForceUpdate: false,
|
|
147
|
+
enabled: true,
|
|
148
|
+
fileHash: "hash2",
|
|
149
|
+
gitCommitHash: null,
|
|
150
|
+
message: "Bundle 2",
|
|
151
|
+
channel: "staging",
|
|
152
|
+
storageUri: "s3://test/2.zip",
|
|
153
|
+
targetAppVersion: "1.0.0",
|
|
154
|
+
fingerprintHash: null,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: "00000000-0000-0000-0000-000000000022",
|
|
158
|
+
platform: "ios",
|
|
159
|
+
shouldForceUpdate: false,
|
|
160
|
+
enabled: true,
|
|
161
|
+
fileHash: "hash3",
|
|
162
|
+
gitCommitHash: null,
|
|
163
|
+
message: "Bundle 3",
|
|
164
|
+
channel: "production",
|
|
165
|
+
storageUri: "s3://test/3.zip",
|
|
166
|
+
targetAppVersion: "1.0.0",
|
|
167
|
+
fingerprintHash: null,
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
for (const bundle of bundles) {
|
|
172
|
+
await hotUpdater.insertBundle(bundle);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// This should not throw a Prisma validation error
|
|
176
|
+
const channels = await hotUpdater.getChannels();
|
|
177
|
+
|
|
178
|
+
expect(channels).toHaveLength(2);
|
|
179
|
+
expect(channels).toContain("production");
|
|
180
|
+
expect(channels).toContain("staging");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should return empty array when no bundles exist", async () => {
|
|
184
|
+
const channels = await hotUpdater.getChannels();
|
|
185
|
+
expect(channels).toEqual([]);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
91
189
|
describe("getAppUpdateInfo with storage plugins", () => {
|
|
92
190
|
beforeEach(() => {
|
|
93
191
|
// Fix time for deterministic signed URLs
|
package/src/db/index.ts
CHANGED
|
@@ -1,73 +1,54 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AppUpdateInfo,
|
|
3
|
-
AppVersionGetBundlesArgs,
|
|
4
|
-
Bundle,
|
|
5
|
-
FingerprintGetBundlesArgs,
|
|
6
|
-
GetBundlesArgs,
|
|
7
|
-
Platform,
|
|
8
|
-
UpdateInfo,
|
|
9
|
-
} from "@hot-updater/core";
|
|
10
|
-
import { NIL_UUID } from "@hot-updater/core";
|
|
11
1
|
import type { StoragePlugin } from "@hot-updater/plugin-core";
|
|
12
|
-
import { filterCompatibleAppVersions } from "@hot-updater/plugin-core";
|
|
13
|
-
import type { InferFumaDB } from "fumadb";
|
|
14
|
-
import { fumadb } from "fumadb";
|
|
15
|
-
import { calculatePagination } from "../calculatePagination";
|
|
16
2
|
import { createHandler } from "../handler";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
offset: number;
|
|
38
|
-
}): Promise<{ data: Bundle[]; pagination: PaginationInfo }>;
|
|
39
|
-
insertBundle(bundle: Bundle): Promise<void>;
|
|
40
|
-
updateBundleById(bundleId: string, newBundle: Partial<Bundle>): Promise<void>;
|
|
41
|
-
deleteBundleById(bundleId: string): Promise<void>;
|
|
42
|
-
}
|
|
3
|
+
import {
|
|
4
|
+
createOrmDatabaseCore,
|
|
5
|
+
type HotUpdaterClient,
|
|
6
|
+
type Migrator,
|
|
7
|
+
} from "./ormCore";
|
|
8
|
+
import { createPluginDatabaseCore } from "./pluginCore";
|
|
9
|
+
import {
|
|
10
|
+
type DatabaseAdapter,
|
|
11
|
+
type DatabaseAPI,
|
|
12
|
+
isDatabasePlugin,
|
|
13
|
+
isDatabasePluginFactory,
|
|
14
|
+
type StoragePluginFactory,
|
|
15
|
+
} from "./types";
|
|
16
|
+
|
|
17
|
+
export type { HotUpdaterClient, Migrator } from "./ormCore";
|
|
18
|
+
export { HotUpdaterDB } from "./ormCore";
|
|
19
|
+
|
|
20
|
+
type OrmCore = ReturnType<typeof createOrmDatabaseCore>;
|
|
21
|
+
type PluginCore = ReturnType<typeof createPluginDatabaseCore>;
|
|
22
|
+
type HotUpdaterCoreInternal = OrmCore | PluginCore;
|
|
43
23
|
|
|
44
24
|
export type HotUpdaterAPI = DatabaseAPI & {
|
|
45
25
|
handler: (request: Request) => Promise<Response>;
|
|
46
|
-
|
|
47
26
|
adapterName: string;
|
|
48
27
|
createMigrator: () => Migrator;
|
|
49
28
|
generateSchema: HotUpdaterClient["generateSchema"];
|
|
50
29
|
};
|
|
51
30
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
export type StoragePluginFactory = (args: { cwd: string }) => StoragePlugin;
|
|
55
|
-
|
|
56
|
-
export interface HotUpdaterOptions {
|
|
31
|
+
interface HotUpdaterOptions {
|
|
57
32
|
database: DatabaseAdapter;
|
|
33
|
+
/**
|
|
34
|
+
* Storage plugins for handling file uploads and downloads.
|
|
35
|
+
*/
|
|
36
|
+
storages?: (StoragePlugin | StoragePluginFactory)[];
|
|
37
|
+
/**
|
|
38
|
+
* @deprecated Use `storages` instead. This field will be removed in a future version.
|
|
39
|
+
*/
|
|
58
40
|
storagePlugins?: (StoragePlugin | StoragePluginFactory)[];
|
|
59
41
|
basePath?: string;
|
|
60
42
|
cwd?: string;
|
|
61
43
|
}
|
|
62
44
|
|
|
63
45
|
export function createHotUpdater(options: HotUpdaterOptions): HotUpdaterAPI {
|
|
64
|
-
const client = HotUpdaterDB.client(options.database);
|
|
65
|
-
const cwd = options.cwd ?? process.cwd();
|
|
66
|
-
|
|
67
46
|
// Initialize storage plugins - call factories if they are functions
|
|
68
|
-
const storagePlugins = (
|
|
69
|
-
|
|
70
|
-
|
|
47
|
+
const storagePlugins = (
|
|
48
|
+
options?.storages ??
|
|
49
|
+
options?.storagePlugins ??
|
|
50
|
+
[]
|
|
51
|
+
).map((plugin) => (typeof plugin === "function" ? plugin() : plugin));
|
|
71
52
|
|
|
72
53
|
const resolveFileUrl = async (
|
|
73
54
|
storageUri: string | null,
|
|
@@ -92,408 +73,28 @@ export function createHotUpdater(options: HotUpdaterOptions): HotUpdaterAPI {
|
|
|
92
73
|
return fileUrl;
|
|
93
74
|
};
|
|
94
75
|
|
|
95
|
-
|
|
96
|
-
async getBundleById(id: string): Promise<Bundle | null> {
|
|
97
|
-
const version = await client.version();
|
|
98
|
-
const orm = client.orm(version);
|
|
99
|
-
const result = await orm.findFirst("bundles", {
|
|
100
|
-
select: [
|
|
101
|
-
"id",
|
|
102
|
-
"platform",
|
|
103
|
-
"should_force_update",
|
|
104
|
-
"enabled",
|
|
105
|
-
"file_hash",
|
|
106
|
-
"git_commit_hash",
|
|
107
|
-
"message",
|
|
108
|
-
"channel",
|
|
109
|
-
"storage_uri",
|
|
110
|
-
"target_app_version",
|
|
111
|
-
"fingerprint_hash",
|
|
112
|
-
"metadata",
|
|
113
|
-
],
|
|
114
|
-
where: (b) => b.and(b.isNotNull("id"), b("id", "=", id)),
|
|
115
|
-
});
|
|
116
|
-
if (!result) return null;
|
|
117
|
-
const bundle: Bundle = {
|
|
118
|
-
id: result.id,
|
|
119
|
-
platform: result.platform as Platform,
|
|
120
|
-
shouldForceUpdate: Boolean(result.should_force_update),
|
|
121
|
-
enabled: Boolean(result.enabled),
|
|
122
|
-
fileHash: result.file_hash,
|
|
123
|
-
gitCommitHash: result.git_commit_hash ?? null,
|
|
124
|
-
message: result.message ?? null,
|
|
125
|
-
channel: result.channel,
|
|
126
|
-
storageUri: result.storage_uri,
|
|
127
|
-
targetAppVersion: result.target_app_version ?? null,
|
|
128
|
-
fingerprintHash: result.fingerprint_hash ?? null,
|
|
129
|
-
};
|
|
130
|
-
return bundle;
|
|
131
|
-
},
|
|
132
|
-
async getUpdateInfo(args: GetBundlesArgs): Promise<UpdateInfo | null> {
|
|
133
|
-
const version = await client.version();
|
|
134
|
-
const orm = client.orm(version);
|
|
135
|
-
|
|
136
|
-
type UpdateSelectRow = {
|
|
137
|
-
id: string;
|
|
138
|
-
should_force_update: boolean;
|
|
139
|
-
message: string | null;
|
|
140
|
-
storage_uri: string | null;
|
|
141
|
-
file_hash: string;
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
const toUpdateInfo = (
|
|
145
|
-
row: UpdateSelectRow,
|
|
146
|
-
status: "UPDATE" | "ROLLBACK",
|
|
147
|
-
): UpdateInfo => ({
|
|
148
|
-
id: row.id,
|
|
149
|
-
shouldForceUpdate:
|
|
150
|
-
status === "ROLLBACK" ? true : Boolean(row.should_force_update),
|
|
151
|
-
message: row.message ?? null,
|
|
152
|
-
status,
|
|
153
|
-
storageUri: row.storage_uri ?? null,
|
|
154
|
-
fileHash: row.file_hash ?? null,
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
const INIT_BUNDLE_ROLLBACK_UPDATE_INFO: UpdateInfo = {
|
|
158
|
-
id: NIL_UUID,
|
|
159
|
-
message: null,
|
|
160
|
-
shouldForceUpdate: true,
|
|
161
|
-
status: "ROLLBACK",
|
|
162
|
-
storageUri: null,
|
|
163
|
-
fileHash: null,
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const appVersionStrategy = async ({
|
|
167
|
-
platform,
|
|
168
|
-
appVersion,
|
|
169
|
-
bundleId,
|
|
170
|
-
minBundleId = NIL_UUID,
|
|
171
|
-
channel = "production",
|
|
172
|
-
}: AppVersionGetBundlesArgs): Promise<UpdateInfo | null> => {
|
|
173
|
-
const versionRows = await orm.findMany("bundles", {
|
|
174
|
-
select: ["target_app_version"],
|
|
175
|
-
where: (b) => b.and(b("platform", "=", platform)),
|
|
176
|
-
});
|
|
177
|
-
const allTargetVersions = Array.from(
|
|
178
|
-
new Set(
|
|
179
|
-
(versionRows ?? [])
|
|
180
|
-
.map((r) => r.target_app_version)
|
|
181
|
-
.filter((v): v is string => Boolean(v)),
|
|
182
|
-
),
|
|
183
|
-
);
|
|
184
|
-
const compatibleVersions = filterCompatibleAppVersions(
|
|
185
|
-
allTargetVersions,
|
|
186
|
-
appVersion,
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const baseRows =
|
|
190
|
-
compatibleVersions.length === 0
|
|
191
|
-
? []
|
|
192
|
-
: await orm.findMany("bundles", {
|
|
193
|
-
select: [
|
|
194
|
-
"id",
|
|
195
|
-
"should_force_update",
|
|
196
|
-
"message",
|
|
197
|
-
"storage_uri",
|
|
198
|
-
"file_hash",
|
|
199
|
-
"channel",
|
|
200
|
-
"target_app_version",
|
|
201
|
-
"enabled",
|
|
202
|
-
],
|
|
203
|
-
where: (b) =>
|
|
204
|
-
b.and(
|
|
205
|
-
b("enabled", "=", true),
|
|
206
|
-
b("platform", "=", platform),
|
|
207
|
-
b("id", ">=", minBundleId ?? NIL_UUID),
|
|
208
|
-
b("channel", "=", channel),
|
|
209
|
-
b.isNotNull("target_app_version"),
|
|
210
|
-
),
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
const candidates = (baseRows ?? []).filter((r) =>
|
|
214
|
-
r.target_app_version
|
|
215
|
-
? compatibleVersions.includes(r.target_app_version)
|
|
216
|
-
: false,
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
const byIdDesc = (a: { id: string }, b: { id: string }) =>
|
|
220
|
-
b.id.localeCompare(a.id);
|
|
221
|
-
const sorted = (candidates ?? []).slice().sort(byIdDesc);
|
|
222
|
-
|
|
223
|
-
const latestCandidate = sorted[0] ?? null;
|
|
224
|
-
const currentBundle = sorted.find((b) => b.id === bundleId);
|
|
225
|
-
const updateCandidate =
|
|
226
|
-
sorted.find((b) => b.id.localeCompare(bundleId) > 0) ?? null;
|
|
227
|
-
const rollbackCandidate =
|
|
228
|
-
sorted.find((b) => b.id.localeCompare(bundleId) < 0) ?? null;
|
|
229
|
-
|
|
230
|
-
if (bundleId === NIL_UUID) {
|
|
231
|
-
if (latestCandidate && latestCandidate.id !== bundleId) {
|
|
232
|
-
return toUpdateInfo(latestCandidate, "UPDATE");
|
|
233
|
-
}
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (currentBundle) {
|
|
238
|
-
if (
|
|
239
|
-
latestCandidate &&
|
|
240
|
-
latestCandidate.id.localeCompare(currentBundle.id) > 0
|
|
241
|
-
) {
|
|
242
|
-
return toUpdateInfo(latestCandidate, "UPDATE");
|
|
243
|
-
}
|
|
244
|
-
return null;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (updateCandidate) {
|
|
248
|
-
return toUpdateInfo(updateCandidate, "UPDATE");
|
|
249
|
-
}
|
|
250
|
-
if (rollbackCandidate) {
|
|
251
|
-
return toUpdateInfo(rollbackCandidate, "ROLLBACK");
|
|
252
|
-
}
|
|
76
|
+
let core: HotUpdaterCoreInternal;
|
|
253
77
|
|
|
254
|
-
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
|
|
258
|
-
};
|
|
78
|
+
const database = options.database;
|
|
259
79
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
"id",
|
|
270
|
-
"should_force_update",
|
|
271
|
-
"message",
|
|
272
|
-
"storage_uri",
|
|
273
|
-
"file_hash",
|
|
274
|
-
"channel",
|
|
275
|
-
"fingerprint_hash",
|
|
276
|
-
"enabled",
|
|
277
|
-
],
|
|
278
|
-
where: (b) =>
|
|
279
|
-
b.and(
|
|
280
|
-
b("enabled", "=", true),
|
|
281
|
-
b("platform", "=", platform),
|
|
282
|
-
b("id", ">=", minBundleId ?? NIL_UUID),
|
|
283
|
-
b("channel", "=", channel),
|
|
284
|
-
b("fingerprint_hash", "=", fingerprintHash),
|
|
285
|
-
),
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
const byIdDesc = (a: { id: string }, b: { id: string }) =>
|
|
289
|
-
b.id.localeCompare(a.id);
|
|
290
|
-
const sorted = (candidates ?? []).slice().sort(byIdDesc);
|
|
291
|
-
|
|
292
|
-
const latestCandidate = sorted[0] ?? null;
|
|
293
|
-
const currentBundle = sorted.find((b) => b.id === bundleId);
|
|
294
|
-
const updateCandidate =
|
|
295
|
-
sorted.find((b) => b.id.localeCompare(bundleId) > 0) ?? null;
|
|
296
|
-
const rollbackCandidate =
|
|
297
|
-
sorted.find((b) => b.id.localeCompare(bundleId) < 0) ?? null;
|
|
298
|
-
|
|
299
|
-
if (bundleId === NIL_UUID) {
|
|
300
|
-
if (latestCandidate && latestCandidate.id !== bundleId) {
|
|
301
|
-
return toUpdateInfo(latestCandidate, "UPDATE");
|
|
302
|
-
}
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (currentBundle) {
|
|
307
|
-
if (
|
|
308
|
-
latestCandidate &&
|
|
309
|
-
latestCandidate.id.localeCompare(currentBundle.id) > 0
|
|
310
|
-
) {
|
|
311
|
-
return toUpdateInfo(latestCandidate, "UPDATE");
|
|
312
|
-
}
|
|
313
|
-
return null;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (updateCandidate) {
|
|
317
|
-
return toUpdateInfo(updateCandidate, "UPDATE");
|
|
318
|
-
}
|
|
319
|
-
if (rollbackCandidate) {
|
|
320
|
-
return toUpdateInfo(rollbackCandidate, "ROLLBACK");
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (minBundleId && bundleId.localeCompare(minBundleId) <= 0) {
|
|
324
|
-
return null;
|
|
325
|
-
}
|
|
326
|
-
return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
if (args._updateStrategy === "appVersion") {
|
|
330
|
-
return appVersionStrategy(args);
|
|
331
|
-
}
|
|
332
|
-
if (args._updateStrategy === "fingerprint") {
|
|
333
|
-
return fingerprintStrategy(args);
|
|
334
|
-
}
|
|
335
|
-
return null;
|
|
336
|
-
},
|
|
337
|
-
|
|
338
|
-
async getAppUpdateInfo(
|
|
339
|
-
args: GetBundlesArgs,
|
|
340
|
-
): Promise<AppUpdateInfo | null> {
|
|
341
|
-
const info = await this.getUpdateInfo(args);
|
|
342
|
-
if (!info) return null;
|
|
343
|
-
const { storageUri, ...rest } = info as UpdateInfo & {
|
|
344
|
-
storageUri: string | null;
|
|
345
|
-
};
|
|
346
|
-
const fileUrl = await resolveFileUrl(storageUri ?? null);
|
|
347
|
-
return { ...rest, fileUrl };
|
|
348
|
-
},
|
|
349
|
-
|
|
350
|
-
async getChannels(): Promise<string[]> {
|
|
351
|
-
const version = await client.version();
|
|
352
|
-
const orm = client.orm(version);
|
|
353
|
-
const rows = await orm.findMany("bundles", {
|
|
354
|
-
select: ["channel"],
|
|
355
|
-
where: (b) => b.isNotNull("channel"),
|
|
356
|
-
});
|
|
357
|
-
const set = new Set(rows?.map((r) => r.channel) ?? []);
|
|
358
|
-
return Array.from(set);
|
|
359
|
-
},
|
|
360
|
-
|
|
361
|
-
async getBundles(options: {
|
|
362
|
-
where?: { channel?: string; platform?: string };
|
|
363
|
-
limit: number;
|
|
364
|
-
offset: number;
|
|
365
|
-
}): Promise<{ data: Bundle[]; pagination: PaginationInfo }> {
|
|
366
|
-
const version = await client.version();
|
|
367
|
-
const orm = client.orm(version);
|
|
368
|
-
const { where, limit, offset } = options;
|
|
369
|
-
|
|
370
|
-
const rows = await orm.findMany("bundles", {
|
|
371
|
-
select: [
|
|
372
|
-
"id",
|
|
373
|
-
"platform",
|
|
374
|
-
"should_force_update",
|
|
375
|
-
"enabled",
|
|
376
|
-
"file_hash",
|
|
377
|
-
"git_commit_hash",
|
|
378
|
-
"message",
|
|
379
|
-
"channel",
|
|
380
|
-
"storage_uri",
|
|
381
|
-
"target_app_version",
|
|
382
|
-
"fingerprint_hash",
|
|
383
|
-
"metadata",
|
|
384
|
-
],
|
|
385
|
-
where: (b) => {
|
|
386
|
-
const conditions = [];
|
|
387
|
-
if (where?.channel) {
|
|
388
|
-
conditions.push(b("channel", "=", where.channel));
|
|
389
|
-
}
|
|
390
|
-
if (where?.platform) {
|
|
391
|
-
conditions.push(b("platform", "=", where.platform));
|
|
392
|
-
}
|
|
393
|
-
return conditions.length > 0 ? b.and(...conditions) : true;
|
|
394
|
-
},
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
const all: Bundle[] = rows
|
|
398
|
-
.map(
|
|
399
|
-
(r): Bundle => ({
|
|
400
|
-
id: r.id,
|
|
401
|
-
platform: r.platform as Platform,
|
|
402
|
-
shouldForceUpdate: Boolean(r.should_force_update),
|
|
403
|
-
enabled: Boolean(r.enabled),
|
|
404
|
-
fileHash: r.file_hash,
|
|
405
|
-
gitCommitHash: r.git_commit_hash ?? null,
|
|
406
|
-
message: r.message ?? null,
|
|
407
|
-
channel: r.channel,
|
|
408
|
-
storageUri: r.storage_uri,
|
|
409
|
-
targetAppVersion: r.target_app_version ?? null,
|
|
410
|
-
fingerprintHash: r.fingerprint_hash ?? null,
|
|
411
|
-
}),
|
|
412
|
-
)
|
|
413
|
-
.sort((a, b) => b.id.localeCompare(a.id));
|
|
414
|
-
|
|
415
|
-
const total = all.length;
|
|
416
|
-
const sliced = all.slice(offset, offset + limit);
|
|
417
|
-
|
|
418
|
-
return {
|
|
419
|
-
data: sliced,
|
|
420
|
-
pagination: calculatePagination(total, { limit, offset }),
|
|
421
|
-
};
|
|
422
|
-
},
|
|
423
|
-
|
|
424
|
-
async insertBundle(bundle: Bundle): Promise<void> {
|
|
425
|
-
const version = await client.version();
|
|
426
|
-
const orm = client.orm(version);
|
|
427
|
-
const values = {
|
|
428
|
-
id: bundle.id,
|
|
429
|
-
platform: bundle.platform,
|
|
430
|
-
should_force_update: bundle.shouldForceUpdate,
|
|
431
|
-
enabled: bundle.enabled,
|
|
432
|
-
file_hash: bundle.fileHash,
|
|
433
|
-
git_commit_hash: bundle.gitCommitHash,
|
|
434
|
-
message: bundle.message,
|
|
435
|
-
channel: bundle.channel,
|
|
436
|
-
storage_uri: bundle.storageUri,
|
|
437
|
-
target_app_version: bundle.targetAppVersion,
|
|
438
|
-
fingerprint_hash: bundle.fingerprintHash,
|
|
439
|
-
metadata: bundle.metadata ?? {},
|
|
440
|
-
};
|
|
441
|
-
const { id, ...updateValues } = values;
|
|
442
|
-
await orm.upsert("bundles", {
|
|
443
|
-
where: (b) => b("id", "=", id),
|
|
444
|
-
create: values,
|
|
445
|
-
update: updateValues,
|
|
446
|
-
});
|
|
447
|
-
},
|
|
448
|
-
|
|
449
|
-
async updateBundleById(
|
|
450
|
-
bundleId: string,
|
|
451
|
-
newBundle: Partial<Bundle>,
|
|
452
|
-
): Promise<void> {
|
|
453
|
-
const version = await client.version();
|
|
454
|
-
const orm = client.orm(version);
|
|
455
|
-
const current = await this.getBundleById(bundleId);
|
|
456
|
-
if (!current) throw new Error("targetBundleId not found");
|
|
457
|
-
const merged: Bundle = { ...current, ...newBundle };
|
|
458
|
-
const values = {
|
|
459
|
-
id: merged.id,
|
|
460
|
-
platform: merged.platform,
|
|
461
|
-
should_force_update: merged.shouldForceUpdate,
|
|
462
|
-
enabled: merged.enabled,
|
|
463
|
-
file_hash: merged.fileHash,
|
|
464
|
-
git_commit_hash: merged.gitCommitHash,
|
|
465
|
-
message: merged.message,
|
|
466
|
-
channel: merged.channel,
|
|
467
|
-
storage_uri: merged.storageUri,
|
|
468
|
-
target_app_version: merged.targetAppVersion,
|
|
469
|
-
fingerprint_hash: merged.fingerprintHash,
|
|
470
|
-
metadata: merged.metadata ?? {},
|
|
471
|
-
};
|
|
472
|
-
const { id: id2, ...updateValues2 } = values;
|
|
473
|
-
await orm.upsert("bundles", {
|
|
474
|
-
where: (b) => b("id", "=", id2),
|
|
475
|
-
create: values,
|
|
476
|
-
update: updateValues2,
|
|
477
|
-
});
|
|
478
|
-
},
|
|
479
|
-
|
|
480
|
-
async deleteBundleById(bundleId: string): Promise<void> {
|
|
481
|
-
const version = await client.version();
|
|
482
|
-
const orm = client.orm(version);
|
|
483
|
-
await orm.deleteMany("bundles", { where: (b) => b("id", "=", bundleId) });
|
|
484
|
-
},
|
|
485
|
-
};
|
|
80
|
+
if (isDatabasePluginFactory(database) || isDatabasePlugin(database)) {
|
|
81
|
+
const plugin = isDatabasePluginFactory(database) ? database() : database;
|
|
82
|
+
core = createPluginDatabaseCore(plugin, resolveFileUrl);
|
|
83
|
+
} else {
|
|
84
|
+
core = createOrmDatabaseCore({
|
|
85
|
+
database,
|
|
86
|
+
resolveFileUrl,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
486
89
|
|
|
487
90
|
return {
|
|
488
|
-
...api,
|
|
91
|
+
...core.api,
|
|
489
92
|
handler: createHandler(
|
|
490
|
-
api,
|
|
93
|
+
core.api,
|
|
491
94
|
options?.basePath ? { basePath: options.basePath } : {},
|
|
492
95
|
),
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
createMigrator: () => client.createMigrator(),
|
|
497
|
-
generateSchema: client.generateSchema,
|
|
96
|
+
adapterName: core.adapterName,
|
|
97
|
+
createMigrator: core.createMigrator,
|
|
98
|
+
generateSchema: core.generateSchema,
|
|
498
99
|
};
|
|
499
100
|
}
|