@hot-updater/server 0.36.7 → 1.0.0-rc.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/databaseConstraintErrors.cjs +23 -0
- package/dist/adapters/databaseConstraintErrors.mjs +21 -0
- package/dist/adapters/databasePluginUtils.cjs +117 -0
- package/dist/adapters/databasePluginUtils.mjs +106 -0
- package/dist/adapters/drizzle.cjs +53 -176
- package/dist/adapters/drizzle.d.cts +6 -4
- package/dist/adapters/drizzle.d.mts +6 -4
- package/dist/adapters/drizzle.mjs +54 -177
- package/dist/adapters/drizzleCrud.cjs +250 -0
- package/dist/adapters/drizzleCrud.mjs +249 -0
- package/dist/adapters/drizzleLazyDB.cjs +84 -36
- package/dist/adapters/drizzleLazyDB.mjs +84 -36
- package/dist/adapters/drizzleQuery.cjs +68 -0
- package/dist/adapters/drizzleQuery.mjs +67 -0
- package/dist/adapters/kysely.cjs +18 -138
- package/dist/adapters/kysely.d.cts +3 -12
- package/dist/adapters/kysely.d.mts +3 -12
- package/dist/adapters/kysely.mjs +19 -139
- package/dist/adapters/kyselyCrud.cjs +254 -0
- package/dist/adapters/kyselyCrud.mjs +253 -0
- package/dist/adapters/kyselyQuery.cjs +56 -0
- package/dist/adapters/kyselyQuery.mjs +55 -0
- package/dist/adapters/mongodb.cjs +21 -174
- package/dist/adapters/mongodb.d.cts +3 -2
- package/dist/adapters/mongodb.d.mts +3 -2
- package/dist/adapters/mongodb.mjs +21 -174
- package/dist/adapters/mongodbCollections.cjs +36 -0
- package/dist/adapters/mongodbCollections.mjs +30 -0
- package/dist/adapters/mongodbQuery.cjs +77 -0
- package/dist/adapters/mongodbQuery.mjs +70 -0
- package/dist/adapters/mongodbReads.cjs +148 -0
- package/dist/adapters/mongodbReads.mjs +147 -0
- package/dist/adapters/mongodbWrites.cjs +134 -0
- package/dist/adapters/mongodbWrites.mjs +133 -0
- package/dist/adapters/prisma.cjs +205 -180
- package/dist/adapters/prisma.d.cts +3 -3
- package/dist/adapters/prisma.d.mts +3 -3
- package/dist/adapters/prisma.mjs +206 -181
- package/dist/adapters/prismaQuery.cjs +58 -0
- package/dist/adapters/prismaQuery.mjs +56 -0
- package/dist/adapters/prismaRows.cjs +198 -0
- package/dist/adapters/prismaRows.mjs +188 -0
- package/dist/analytics/bounded/activeOverview.cjs +87 -0
- package/dist/analytics/bounded/activeOverview.mjs +85 -0
- package/dist/analytics/bounded/installationSearch.cjs +42 -0
- package/dist/analytics/bounded/installationSearch.mjs +42 -0
- package/dist/analytics/bounded/persistence.cjs +39 -0
- package/dist/analytics/bounded/persistence.mjs +38 -0
- package/dist/analytics/bounded/provider.cjs +170 -0
- package/dist/analytics/bounded/provider.d.cts +7 -0
- package/dist/analytics/bounded/provider.d.mts +7 -0
- package/dist/analytics/bounded/provider.mjs +170 -0
- package/dist/analytics/bounded/scan.cjs +144 -0
- package/dist/analytics/bounded/scan.mjs +138 -0
- package/dist/analytics/domain.d.cts +121 -0
- package/dist/analytics/domain.d.mts +121 -0
- package/dist/analytics/errors.cjs +22 -0
- package/dist/analytics/errors.mjs +20 -0
- package/dist/analytics/eventInput.cjs +111 -0
- package/dist/analytics/eventInput.mjs +111 -0
- package/dist/analytics/persistence.d.cts +6 -0
- package/dist/analytics/persistence.d.mts +6 -0
- package/dist/analytics/queryInput.cjs +58 -0
- package/dist/analytics/queryInput.mjs +55 -0
- package/dist/analytics/routes.cjs +63 -0
- package/dist/analytics/routes.mjs +61 -0
- package/dist/analytics/types.d.cts +17 -0
- package/dist/analytics/types.d.mts +17 -0
- package/dist/apiKeys.cjs +139 -0
- package/dist/apiKeys.d.cts +50 -0
- package/dist/apiKeys.d.mts +50 -0
- package/dist/apiKeys.mjs +133 -0
- package/dist/createHotUpdaterCore.cjs +48 -26
- package/dist/createHotUpdaterCore.d.cts +49 -17
- package/dist/createHotUpdaterCore.d.mts +49 -17
- package/dist/createHotUpdaterCore.mjs +51 -29
- package/dist/db/createBundleDiff.cjs +45 -68
- package/dist/db/createBundleDiff.d.cts +3 -3
- package/dist/db/createBundleDiff.d.mts +3 -3
- package/dist/db/createBundleDiff.mjs +47 -68
- package/dist/db/databasePluginCore.cjs +138 -0
- package/dist/db/databasePluginCore.mjs +137 -0
- package/dist/db/fixedMigrator.cjs +36 -93
- package/dist/db/fixedMigrator.mjs +36 -93
- package/dist/db/fixedMigratorMongo.cjs +319 -0
- package/dist/db/fixedMigratorMongo.mjs +319 -0
- package/dist/db/fixedMigratorShared.cjs +23 -0
- package/dist/db/fixedMigratorShared.mjs +18 -0
- package/dist/db/mongoMigrationExecution.cjs +11 -0
- package/dist/db/mongoMigrationExecution.mjs +11 -0
- package/dist/db/releaseCatalog.cjs +66 -0
- package/dist/db/releaseCatalog.d.cts +17 -0
- package/dist/db/releaseCatalog.d.mts +17 -0
- package/dist/db/releaseCatalog.mjs +64 -0
- package/dist/db/schema/mongodb.cjs +14 -6
- package/dist/db/schema/mongodb.mjs +14 -6
- package/dist/db/schema/registry.cjs +0 -2
- package/dist/db/schema/registry.mjs +1 -2
- package/dist/db/schema/sql.cjs +31 -14
- package/dist/db/schema/sql.mjs +32 -13
- package/dist/db/schema/sqlOperations.cjs +4 -4
- package/dist/db/schema/sqlOperations.mjs +5 -5
- package/dist/db/schemaGenerators.cjs +52 -18
- package/dist/db/schemaGenerators.mjs +52 -18
- package/dist/db/schemaReadiness.cjs +4 -3
- package/dist/db/schemaReadiness.mjs +4 -3
- package/dist/db/sqlMigrationExecution.cjs +67 -0
- package/dist/db/sqlMigrationExecution.mjs +66 -0
- package/dist/db/types.cjs +2 -6
- package/dist/db/types.d.cts +60 -16
- package/dist/db/types.d.mts +60 -16
- package/dist/db/types.mjs +3 -6
- package/dist/db/updateArtifacts.cjs +114 -60
- package/dist/db/updateArtifacts.mjs +117 -62
- package/dist/handler.cjs +108 -282
- package/dist/handler.d.cts +3 -46
- package/dist/handler.d.mts +3 -46
- package/dist/handler.mjs +107 -281
- package/dist/handlerBundleRoutes.cjs +155 -0
- package/dist/handlerBundleRoutes.mjs +155 -0
- package/dist/handlerErrors.cjs +9 -0
- package/dist/handlerErrors.mjs +9 -0
- package/dist/handlerParameters.cjs +39 -0
- package/dist/handlerParameters.mjs +34 -0
- package/dist/handlerReleaseCatalogRoutes.cjs +111 -0
- package/dist/handlerReleaseCatalogRoutes.mjs +110 -0
- package/dist/handlerReleaseManagementRoutes.cjs +125 -0
- package/dist/handlerReleaseManagementRoutes.mjs +124 -0
- package/dist/handlerTypes.d.cts +63 -0
- package/dist/handlerTypes.d.mts +63 -0
- package/dist/handlerVersionRoutes.cjs +10 -0
- package/dist/handlerVersionRoutes.d.cts +4 -0
- package/dist/handlerVersionRoutes.d.mts +4 -0
- package/dist/handlerVersionRoutes.mjs +9 -0
- package/dist/index.cjs +10 -1
- package/dist/index.d.cts +9 -3
- package/dist/index.d.mts +9 -3
- package/dist/index.mjs +5 -2
- package/dist/node.cjs +19 -12
- package/dist/node.d.cts +15 -10
- package/dist/node.d.mts +15 -10
- package/dist/node.mjs +19 -13
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/schema/dsl.cjs +19 -2
- package/dist/schema/dsl.mjs +17 -3
- package/dist/schema/index.cjs +1 -8
- package/dist/schema/index.mjs +2 -8
- package/dist/schema/types.cjs +3 -1
- package/dist/schema/types.mjs +3 -2
- package/dist/schema/v1_0_0.cjs +364 -0
- package/dist/schema/v1_0_0.mjs +357 -0
- package/dist/storageAccess.cjs +69 -25
- package/dist/storageAccess.mjs +68 -25
- package/dist/types/index.d.cts +4 -3
- package/dist/types/index.d.mts +5 -4
- package/package.json +8 -7
- package/src/adapters/databaseConstraintErrors.ts +25 -0
- package/src/adapters/databasePluginTestDatabase.ts +106 -0
- package/src/adapters/databasePluginUtils.spec.ts +29 -0
- package/src/adapters/databasePluginUtils.ts +214 -0
- package/src/adapters/drizzle.spec.ts +208 -47
- package/src/adapters/drizzle.ts +106 -293
- package/src/adapters/drizzleCrud.ts +419 -0
- package/src/adapters/drizzleLazyDB.ts +206 -100
- package/src/adapters/drizzleQuery.ts +164 -0
- package/src/adapters/kysely.spec.ts +126 -88
- package/src/adapters/kysely.ts +76 -334
- package/src/adapters/kyselyCrud.ts +588 -0
- package/src/adapters/kyselyQuery.ts +128 -0
- package/src/adapters/mongodb.spec.ts +141 -0
- package/src/adapters/mongodb.ts +60 -288
- package/src/adapters/mongodbCollections.ts +62 -0
- package/src/adapters/mongodbQuery.ts +226 -0
- package/src/adapters/mongodbReads.ts +311 -0
- package/src/adapters/mongodbRows.spec.ts +64 -0
- package/src/adapters/mongodbRows.ts +118 -0
- package/src/adapters/mongodbTestClient.ts +342 -0
- package/src/adapters/mongodbTestFilter.ts +166 -0
- package/src/adapters/mongodbWrites.ts +260 -0
- package/src/adapters/prisma.spec.ts +196 -0
- package/src/adapters/prisma.ts +417 -292
- package/src/adapters/prismaQuery.ts +129 -0
- package/src/adapters/prismaRows.spec.ts +98 -0
- package/src/adapters/prismaRows.ts +301 -0
- package/src/adapters/prismaTestClient.ts +396 -0
- package/src/analytics/analytics.integration.spec.ts +144 -0
- package/src/analytics/bounded/activeOverview.spec.ts +189 -0
- package/src/analytics/bounded/activeOverview.ts +148 -0
- package/src/analytics/bounded/installationSearch.spec.ts +130 -0
- package/src/analytics/bounded/installationSearch.ts +69 -0
- package/src/analytics/bounded/persistence.ts +43 -0
- package/src/analytics/bounded/provider.spec.ts +219 -0
- package/src/analytics/bounded/provider.ts +229 -0
- package/src/analytics/bounded/scan.ts +235 -0
- package/src/analytics/domain.ts +139 -0
- package/src/analytics/errors.ts +18 -0
- package/src/analytics/eventInput.ts +168 -0
- package/src/analytics/persistence.ts +10 -0
- package/src/analytics/queryInput.ts +102 -0
- package/src/analytics/routes.ts +123 -0
- package/src/analytics/types.ts +44 -0
- package/src/apiKeys.integration.spec.ts +192 -0
- package/src/apiKeys.spec.ts +162 -0
- package/src/apiKeys.ts +220 -0
- package/src/createHotUpdaterCore.ts +164 -100
- package/src/db/bundleRows.spec.ts +164 -0
- package/src/db/bundleRows.ts +10 -140
- package/src/db/createBundleDiff.spec.ts +172 -127
- package/src/db/createBundleDiff.ts +91 -110
- package/src/db/databasePluginCore.spec.ts +62 -0
- package/src/db/databasePluginCore.testFixtures.ts +76 -0
- package/src/db/databasePluginCore.ts +185 -0
- package/src/db/databasePluginUpdateCheck.bench.ts +107 -0
- package/src/db/fixedMigrator.spec.ts +184 -42
- package/src/db/fixedMigrator.ts +64 -205
- package/src/db/fixedMigratorMongo.spec.ts +192 -0
- package/src/db/fixedMigratorMongo.ts +425 -0
- package/src/db/fixedMigratorShared.ts +36 -0
- package/src/db/hotUpdaterSchema.ts +0 -1
- package/src/db/index.spec.ts +333 -1025
- package/src/db/mongoMigrationExecution.ts +23 -0
- package/src/db/node-sqlite.d.ts +25 -0
- package/src/db/releaseCatalog.ts +148 -0
- package/src/db/schema/mongodb.ts +20 -6
- package/src/db/schema/sql.ts +52 -11
- package/src/db/schema/sqlOperations.ts +5 -4
- package/src/db/schemaGenerators.ts +107 -23
- package/src/db/schemaReadiness.ts +2 -1
- package/src/db/sqlMigrationExecution.spec.ts +65 -0
- package/src/db/sqlMigrationExecution.ts +108 -0
- package/src/db/types.spec.ts +55 -0
- package/src/db/types.ts +129 -65
- package/src/db/updateArtifacts.spec.ts +359 -0
- package/src/db/updateArtifacts.ts +290 -126
- package/src/handler-standalone.integration.spec.ts +401 -364
- package/src/handler.spec.ts +137 -582
- package/src/handler.testFixtures.ts +30 -0
- package/src/handler.ts +218 -616
- package/src/handlerAuth.integration.spec.ts +33 -0
- package/src/handlerBundleRoutes.ts +259 -0
- package/src/handlerErrors.ts +7 -0
- package/src/handlerManagement.spec.ts +262 -0
- package/src/handlerParameters.ts +105 -0
- package/src/handlerReleaseCatalogRoutes.ts +154 -0
- package/src/handlerReleaseManagementRoutes.ts +213 -0
- package/src/handlerTypes.ts +96 -0
- package/src/handlerVersionRoutes.ts +12 -0
- package/src/index.ts +22 -2
- package/src/node.spec.ts +79 -10
- package/src/node.ts +20 -17
- package/src/releaseCatalogRoutes.integration.spec.ts +238 -0
- package/src/runtime.spec.ts +126 -959
- package/src/runtime.testFixtures.ts +71 -0
- package/src/schema/dsl-all-versions.spec.ts +26 -17
- package/src/schema/dsl.ts +41 -2
- package/src/schema/index.ts +3 -9
- package/src/schema/types.ts +9 -3
- package/src/schema/v1_0_0.spec.ts +146 -0
- package/src/schema/v1_0_0.ts +465 -0
- package/src/storageAccess.spec.ts +127 -32
- package/src/storageAccess.ts +106 -55
- package/src/types/index.ts +2 -1
- package/dist/db/bundleRows.cjs +0 -82
- package/dist/db/bundleRows.d.cts +0 -33
- package/dist/db/bundleRows.d.mts +0 -33
- package/dist/db/bundleRows.mjs +0 -79
- package/dist/db/pluginCore.cjs +0 -242
- package/dist/db/pluginCore.mjs +0 -241
- package/dist/db/schema/sqlMigrations.cjs +0 -74
- package/dist/db/schema/sqlMigrations.mjs +0 -73
- package/dist/db/schemaEnhancements.cjs +0 -20
- package/dist/db/schemaEnhancements.mjs +0 -19
- package/dist/route.cjs +0 -7
- package/dist/route.mjs +0 -7
- package/dist/schema/v0_21_0.cjs +0 -38
- package/dist/schema/v0_21_0.mjs +0 -37
- package/dist/schema/v0_29_0.cjs +0 -45
- package/dist/schema/v0_29_0.mjs +0 -44
- package/dist/schema/v0_31_0.cjs +0 -82
- package/dist/schema/v0_31_0.mjs +0 -80
- package/src/db/pluginCore.spec.ts +0 -1095
- package/src/db/pluginCore.ts +0 -475
- package/src/db/pluginUpdateCheck.bench.ts +0 -254
- package/src/db/schema/sqlMigrations.ts +0 -219
- package/src/db/schemaEnhancements.ts +0 -55
- package/src/route.ts +0 -7
- package/src/schema/v0_21_0.ts +0 -52
- package/src/schema/v0_29_0.ts +0 -62
- package/src/schema/v0_31_0.spec.ts +0 -73
- package/src/schema/v0_31_0.ts +0 -120
package/src/db/index.spec.ts
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import { PGlite } from "@electric-sql/pglite";
|
|
2
|
-
import type { Bundle
|
|
2
|
+
import type { Bundle } from "@hot-updater/core";
|
|
3
3
|
import { NIL_UUID } from "@hot-updater/core";
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
StorageResolveContext,
|
|
7
|
-
} from "@hot-updater/plugin-core";
|
|
8
|
-
import { createDatabasePlugin } from "@hot-updater/plugin-core";
|
|
9
|
-
import {
|
|
10
|
-
setupBundleMethodsTestSuite,
|
|
11
|
-
setupGetUpdateInfoTestSuite,
|
|
12
|
-
} from "@hot-updater/test-utils";
|
|
4
|
+
import { createStoragePlugin } from "@hot-updater/plugin-core";
|
|
5
|
+
import { sql } from "drizzle-orm";
|
|
13
6
|
import { Kysely } from "kysely";
|
|
14
7
|
import { PGliteDialect } from "kysely-pglite-dialect";
|
|
15
|
-
import type
|
|
8
|
+
import { type ClientSession, MongoClient } from "mongodb";
|
|
16
9
|
import {
|
|
17
10
|
afterAll,
|
|
18
11
|
afterEach,
|
|
@@ -24,20 +17,28 @@ import {
|
|
|
24
17
|
vi,
|
|
25
18
|
} from "vitest";
|
|
26
19
|
|
|
20
|
+
import { createInMemoryDatabasePlugin } from "../../../test-utils/test/inMemoryDatabasePlugin";
|
|
27
21
|
import { drizzleAdapter } from "../adapters/drizzle";
|
|
28
22
|
import { kyselyAdapter } from "../adapters/kysely";
|
|
29
23
|
import { mongoAdapter } from "../adapters/mongodb";
|
|
30
24
|
import { prismaAdapter } from "../adapters/prisma";
|
|
31
|
-
import { createHotUpdater } from "../index";
|
|
32
|
-
import { bundleToRow } from "./bundleRows";
|
|
33
25
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} from "./
|
|
26
|
+
createHotUpdater as createRuntimeHotUpdater,
|
|
27
|
+
type CreateHotUpdaterOptions,
|
|
28
|
+
} from "../index";
|
|
29
|
+
import { bundleToRow } from "./bundleRows";
|
|
30
|
+
import { createTableSql, hotUpdaterSchemaVersions } from "./hotUpdaterSchema";
|
|
38
31
|
import { createMigrator, generateSchema } from "./index";
|
|
39
32
|
import { generateDrizzleSchema } from "./schemaGenerators";
|
|
40
|
-
import type {
|
|
33
|
+
import type { DatabasePlugin, ORMProvider } from "./types";
|
|
34
|
+
|
|
35
|
+
const createHotUpdater = (
|
|
36
|
+
options: Omit<CreateHotUpdaterOptions, "clientAccess">,
|
|
37
|
+
) =>
|
|
38
|
+
createRuntimeHotUpdater({
|
|
39
|
+
...options,
|
|
40
|
+
clientAccess: { type: "public" },
|
|
41
|
+
});
|
|
41
42
|
|
|
42
43
|
const RAW_PRISMA_SCHEMA = `model bundles {
|
|
43
44
|
id String @id
|
|
@@ -71,7 +72,7 @@ model bundle_patches {
|
|
|
71
72
|
}
|
|
72
73
|
model private_hot_updater_settings {
|
|
73
74
|
key String @id
|
|
74
|
-
value String @default("0.
|
|
75
|
+
value String @default("0.36.0")
|
|
75
76
|
}`;
|
|
76
77
|
|
|
77
78
|
const RAW_DRIZZLE_SCHEMA = `import { relations } from "drizzle-orm";
|
|
@@ -135,6 +136,11 @@ export const bundle_patches = pgTable(
|
|
|
135
136
|
.onDelete("cascade"),
|
|
136
137
|
])
|
|
137
138
|
|
|
139
|
+
export const private_hot_updater_settings = pgTable("private_hot_updater_settings", {
|
|
140
|
+
key: varchar("key", { length: 255 }).primaryKey().notNull(),
|
|
141
|
+
version: varchar("version", { length: 255 }).notNull().default("0.36.0"),
|
|
142
|
+
})
|
|
143
|
+
|
|
138
144
|
export const bundle_patchesRelations = relations(bundle_patches, ({ one, many }) => ({
|
|
139
145
|
bundle: one(bundles, {
|
|
140
146
|
relationName: "bundle_patches_bundles_patches",
|
|
@@ -150,24 +156,30 @@ export const bundle_patchesRelations = relations(bundle_patches, ({ one, many })
|
|
|
150
156
|
|
|
151
157
|
function createTestStoragePlugin(
|
|
152
158
|
protocol: string,
|
|
153
|
-
resolveFileUrl: (
|
|
154
|
-
storageUri: string,
|
|
155
|
-
context?: StorageResolveContext,
|
|
156
|
-
) => string,
|
|
157
159
|
readText: (storageUri: string) => Promise<string | null> = async () => null,
|
|
158
|
-
)
|
|
159
|
-
return {
|
|
160
|
+
) {
|
|
161
|
+
return createStoragePlugin({
|
|
160
162
|
name: `${protocol}TestStorage`,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
async getDownloadUrl(storageUri, context) {
|
|
166
|
-
return { fileUrl: resolveFileUrl(storageUri, context) };
|
|
167
|
-
},
|
|
168
|
-
},
|
|
163
|
+
protocol,
|
|
164
|
+
async get({ storageUri }) {
|
|
165
|
+
const text = await readText(storageUri);
|
|
166
|
+
return { response: text === null ? null : new Response(text) };
|
|
169
167
|
},
|
|
170
|
-
|
|
168
|
+
async getDownloadUrl({ storageUri }) {
|
|
169
|
+
const prefixes: Record<string, string> = {
|
|
170
|
+
gs: "https://firebase.example.com/",
|
|
171
|
+
r2: "https://r2.example.com/",
|
|
172
|
+
s3: "https://s3.example.com/",
|
|
173
|
+
"supabase-storage":
|
|
174
|
+
"https://supabase.example.com/storage/v1/object/sign/",
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
url: storageUri
|
|
178
|
+
.replace(`${protocol}://`, prefixes[protocol] ?? "")
|
|
179
|
+
.replace(/([^:]\/)\/+/g, "$1"),
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
});
|
|
171
183
|
}
|
|
172
184
|
|
|
173
185
|
function createSchemaOnlyAdapter({
|
|
@@ -180,75 +192,29 @@ function createSchemaOnlyAdapter({
|
|
|
180
192
|
name: string;
|
|
181
193
|
provider: ORMProvider;
|
|
182
194
|
path: string;
|
|
183
|
-
}):
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
async getBundles() {
|
|
190
|
-
return {
|
|
191
|
-
data: [],
|
|
192
|
-
pagination: {
|
|
193
|
-
currentPage: 1,
|
|
194
|
-
hasNextPage: false,
|
|
195
|
-
hasPreviousPage: false,
|
|
196
|
-
total: 0,
|
|
197
|
-
totalPages: 0,
|
|
198
|
-
},
|
|
199
|
-
};
|
|
200
|
-
},
|
|
201
|
-
async getChannels() {
|
|
202
|
-
return [];
|
|
203
|
-
},
|
|
204
|
-
async appendBundle() {},
|
|
205
|
-
async updateBundle() {},
|
|
206
|
-
async deleteBundle() {},
|
|
207
|
-
async commitBundle() {},
|
|
208
|
-
});
|
|
209
|
-
factory.adapterName = name;
|
|
210
|
-
factory.provider = provider;
|
|
211
|
-
factory.generateSchema = (_version, schemaName = name) => {
|
|
212
|
-
return {
|
|
195
|
+
}): DatabasePlugin {
|
|
196
|
+
return {
|
|
197
|
+
...createInMemoryDatabasePlugin(),
|
|
198
|
+
adapterName: name,
|
|
199
|
+
provider,
|
|
200
|
+
generateSchema: (_version, schemaName = name) => ({
|
|
213
201
|
code,
|
|
214
202
|
path: path || schemaName,
|
|
215
|
-
}
|
|
203
|
+
}),
|
|
216
204
|
};
|
|
217
|
-
return factory;
|
|
218
205
|
}
|
|
219
206
|
|
|
220
207
|
const transactionBundle: Bundle = {
|
|
208
|
+
archiveByteSize: 1_024,
|
|
221
209
|
id: "00000000-0000-0000-0000-000000000777",
|
|
222
210
|
platform: "ios",
|
|
223
|
-
shouldForceUpdate: false,
|
|
224
|
-
enabled: true,
|
|
225
211
|
fileHash: "transaction-hash",
|
|
226
212
|
gitCommitHash: null,
|
|
227
|
-
message: "transaction bundle",
|
|
228
|
-
channel: "production",
|
|
229
213
|
storageUri: "s3://test-bucket/transaction.zip",
|
|
230
|
-
targetAppVersion: "1.0.0",
|
|
231
|
-
fingerprintHash: null,
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
const appVersionFastPathBundle: Bundle = {
|
|
235
|
-
...transactionBundle,
|
|
236
|
-
id: "00000000-0000-0000-0000-000000000778",
|
|
237
|
-
fileHash: "app-version-fast-path-hash",
|
|
238
|
-
message: "app version fast path bundle",
|
|
239
|
-
targetAppVersion: "1.0.0",
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
const fingerprintFastPathBundle: Bundle = {
|
|
243
|
-
...transactionBundle,
|
|
244
|
-
id: "00000000-0000-0000-0000-000000000779",
|
|
245
|
-
fileHash: "fingerprint-fast-path-hash",
|
|
246
|
-
fingerprintHash: "fingerprint-fast-path",
|
|
247
|
-
message: "fingerprint fast path bundle",
|
|
248
|
-
targetAppVersion: null,
|
|
249
214
|
};
|
|
215
|
+
const transactionChannelId = "00000000-0000-0000-0000-000000000700";
|
|
250
216
|
|
|
251
|
-
describe("server/db hotUpdater
|
|
217
|
+
describe("server/db hotUpdater (PGlite + Kysely)", async () => {
|
|
252
218
|
const db = new PGlite();
|
|
253
219
|
|
|
254
220
|
const kysely = new Kysely<object>({ dialect: new PGliteDialect(db) });
|
|
@@ -266,42 +232,11 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
266
232
|
db: kysely,
|
|
267
233
|
provider: "postgresql",
|
|
268
234
|
}),
|
|
269
|
-
|
|
270
|
-
createTestStoragePlugin(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
.replace("s3://", "https://s3.example.com/")
|
|
275
|
-
.replace(/([^:]\/)\/+/g, "$1"),
|
|
276
|
-
readStoredText,
|
|
277
|
-
),
|
|
278
|
-
createTestStoragePlugin(
|
|
279
|
-
"r2",
|
|
280
|
-
(storageUri) =>
|
|
281
|
-
storageUri
|
|
282
|
-
.replace("r2://", "https://r2.example.com/")
|
|
283
|
-
.replace(/([^:]\/)\/+/g, "$1"),
|
|
284
|
-
readStoredText,
|
|
285
|
-
),
|
|
286
|
-
createTestStoragePlugin(
|
|
287
|
-
"supabase-storage",
|
|
288
|
-
(storageUri) =>
|
|
289
|
-
storageUri
|
|
290
|
-
.replace(
|
|
291
|
-
"supabase-storage://",
|
|
292
|
-
"https://supabase.example.com/storage/v1/object/sign/",
|
|
293
|
-
)
|
|
294
|
-
.replace(/([^:]\/)\/+/g, "$1"),
|
|
295
|
-
readStoredText,
|
|
296
|
-
),
|
|
297
|
-
createTestStoragePlugin(
|
|
298
|
-
"gs",
|
|
299
|
-
(storageUri) =>
|
|
300
|
-
storageUri
|
|
301
|
-
.replace("gs://", "https://firebase.example.com/")
|
|
302
|
-
.replace(/([^:]\/)\/+/g, "$1"),
|
|
303
|
-
readStoredText,
|
|
304
|
-
),
|
|
235
|
+
storage: [
|
|
236
|
+
createTestStoragePlugin("s3", readStoredText),
|
|
237
|
+
createTestStoragePlugin("r2", readStoredText),
|
|
238
|
+
createTestStoragePlugin("supabase-storage", readStoredText),
|
|
239
|
+
createTestStoragePlugin("gs", readStoredText),
|
|
305
240
|
],
|
|
306
241
|
});
|
|
307
242
|
const prismaSchemaHotUpdater = createHotUpdater({
|
|
@@ -331,7 +266,22 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
331
266
|
|
|
332
267
|
it("uses the default generated schema artifact path for Drizzle", () => {
|
|
333
268
|
const adapter = drizzleAdapter({
|
|
334
|
-
db: {
|
|
269
|
+
db: {
|
|
270
|
+
_: {
|
|
271
|
+
fullSchema: {
|
|
272
|
+
bundle_patches: {},
|
|
273
|
+
bundles: {},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
$count: vi.fn(),
|
|
277
|
+
delete: vi.fn(),
|
|
278
|
+
insert: vi.fn(),
|
|
279
|
+
query: {
|
|
280
|
+
bundle_patches: { findFirst: vi.fn(), findMany: vi.fn() },
|
|
281
|
+
bundles: { findFirst: vi.fn(), findMany: vi.fn() },
|
|
282
|
+
},
|
|
283
|
+
update: vi.fn(),
|
|
284
|
+
},
|
|
335
285
|
provider: "sqlite",
|
|
336
286
|
});
|
|
337
287
|
|
|
@@ -351,8 +301,11 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
351
301
|
|
|
352
302
|
beforeEach(async () => {
|
|
353
303
|
storageTexts.clear();
|
|
304
|
+
await db.exec("DELETE FROM release_catalogs");
|
|
305
|
+
await db.exec("DELETE FROM releases");
|
|
354
306
|
await db.exec("DELETE FROM bundle_patches");
|
|
355
307
|
await db.exec("DELETE FROM bundles");
|
|
308
|
+
await db.exec("DELETE FROM channels");
|
|
356
309
|
});
|
|
357
310
|
|
|
358
311
|
afterAll(async () => {
|
|
@@ -360,34 +313,20 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
360
313
|
await db.close();
|
|
361
314
|
});
|
|
362
315
|
|
|
363
|
-
const getUpdateInfo = async (
|
|
364
|
-
bundles: Bundle[],
|
|
365
|
-
options: GetBundlesArgs,
|
|
366
|
-
): Promise<UpdateInfo | null> => {
|
|
367
|
-
// Insert fixtures via the server API to exercise its types + mapping
|
|
368
|
-
for (const b of bundles) {
|
|
369
|
-
await hotUpdater.insertBundle(b);
|
|
370
|
-
}
|
|
371
|
-
return hotUpdater.getUpdateInfo(options);
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
setupGetUpdateInfoTestSuite({ getUpdateInfo });
|
|
375
|
-
setupBundleMethodsTestSuite({
|
|
376
|
-
getBundleById: hotUpdater.getBundleById.bind(hotUpdater),
|
|
377
|
-
getChannels: hotUpdater.getChannels.bind(hotUpdater),
|
|
378
|
-
insertBundle: hotUpdater.insertBundle.bind(hotUpdater),
|
|
379
|
-
getBundles: hotUpdater.getBundles.bind(hotUpdater),
|
|
380
|
-
updateBundleById: hotUpdater.updateBundleById.bind(hotUpdater),
|
|
381
|
-
deleteBundleById: hotUpdater.deleteBundleById.bind(hotUpdater),
|
|
382
|
-
});
|
|
383
|
-
|
|
384
316
|
describe("schema generation", () => {
|
|
385
317
|
it("includes relations, defaults, and indexes in Prisma output", () => {
|
|
386
318
|
const code = generateSchema(prismaSchemaHotUpdater, "latest").code;
|
|
387
319
|
|
|
388
|
-
expect(code).toContain('channel String @default("production")');
|
|
389
320
|
expect(code).toContain('metadata Json @default("{}")');
|
|
390
|
-
expect(code).toContain('value String @default("0.
|
|
321
|
+
expect(code).toContain('value String @default("1.0.0")');
|
|
322
|
+
expect(code).toContain("model channels {");
|
|
323
|
+
expect(code).toContain("id String @db.VarChar(255) @id");
|
|
324
|
+
expect(code).toContain("name String @db.VarChar(255)");
|
|
325
|
+
expect(code).toContain('@@unique([name], map: "channels_name_key")');
|
|
326
|
+
expect(code).toContain("channel_id String @db.VarChar(255)");
|
|
327
|
+
expect(code).toContain(
|
|
328
|
+
'channelRecord channels @relation("releases_channels", fields: [channel_id], references: [id], onUpdate: Restrict, onDelete: Restrict)',
|
|
329
|
+
);
|
|
391
330
|
expect(code).toContain(
|
|
392
331
|
'patches bundle_patches[] @relation("bundle_patches_bundles_patches")',
|
|
393
332
|
);
|
|
@@ -400,7 +339,13 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
400
339
|
expect(code).toContain(
|
|
401
340
|
'baseBundle bundles @relation("bundle_patches_bundles_baseForPatches"',
|
|
402
341
|
);
|
|
403
|
-
expect(code).toContain(
|
|
342
|
+
expect(code).not.toContain(
|
|
343
|
+
'@@index([channel], map: "bundles_channel_idx")',
|
|
344
|
+
);
|
|
345
|
+
expect(code).toContain(
|
|
346
|
+
'@@index([scope_key, id], map: "releases_scope_order_idx")',
|
|
347
|
+
);
|
|
348
|
+
expect(code).toContain("scope_key String @db.VarChar(2048) @id");
|
|
404
349
|
expect(code).not.toContain("bundles_platform_idx");
|
|
405
350
|
expect(code).toContain(
|
|
406
351
|
'@@index([bundle_id], map: "bundle_patches_bundle_id_idx")',
|
|
@@ -414,12 +359,10 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
414
359
|
expect(code).not.toContain('metadata Json @default("{}")');
|
|
415
360
|
});
|
|
416
361
|
|
|
417
|
-
it("
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
expect(code).not.toContain("rollout_cohort_count");
|
|
422
|
-
expect(code).not.toContain("bundle_patches");
|
|
362
|
+
it("rejects generating a retired schema snapshot", () => {
|
|
363
|
+
expect(() => generateSchema(prismaSchemaHotUpdater, "0.21.0")).toThrow(
|
|
364
|
+
"Unsupported Hot Updater schema version: 0.21.0",
|
|
365
|
+
);
|
|
423
366
|
});
|
|
424
367
|
|
|
425
368
|
it("includes foreign keys and indexes in Drizzle output", () => {
|
|
@@ -431,36 +374,40 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
431
374
|
/export const bundle_patches = [\s\S]*?(?=\n\nexport const bundle_patchesRelations = )/,
|
|
432
375
|
)?.[0];
|
|
433
376
|
|
|
434
|
-
expect(code).toContain(
|
|
435
|
-
'channel: text("channel").notNull().default("production")',
|
|
436
|
-
);
|
|
437
377
|
expect(code).toContain(
|
|
438
378
|
'metadata: json("metadata").notNull().default({})',
|
|
439
379
|
);
|
|
440
380
|
expect(code).toContain('name: "bundle_patches_bundle_id_fk"');
|
|
441
381
|
expect(code).toContain('name: "bundle_patches_base_bundle_id_fk"');
|
|
442
|
-
expect(
|
|
443
|
-
|
|
444
|
-
);
|
|
382
|
+
expect(code).toContain('.onDelete("cascade")');
|
|
383
|
+
expect(bundlesBlock).not.toContain("bundles_channel_idx");
|
|
445
384
|
expect(bundlesBlock).not.toContain("bundles_platform_idx");
|
|
446
|
-
expect(bundlesBlock).toContain(
|
|
447
|
-
'index("bundles_target_app_version_idx").on(table.target_app_version)',
|
|
448
|
-
);
|
|
385
|
+
expect(bundlesBlock).not.toContain("target_app_version");
|
|
449
386
|
expect(bundlesBlock).not.toContain(
|
|
450
387
|
'index("bundle_patches_bundle_id_idx").on(table.bundle_id)',
|
|
451
388
|
);
|
|
452
389
|
expect(bundlePatchesBlock).toContain(
|
|
453
390
|
'index("bundle_patches_bundle_id_idx").on(table.bundle_id)',
|
|
454
391
|
);
|
|
455
|
-
expect(
|
|
456
|
-
'index("
|
|
392
|
+
expect(code).toContain(
|
|
393
|
+
'index("releases_scope_order_idx").on(table.scope_key, table.id)',
|
|
394
|
+
);
|
|
395
|
+
expect(code).toContain(
|
|
396
|
+
'scope_key: varchar("scope_key", { length: 2048 }).primaryKey().notNull()',
|
|
457
397
|
);
|
|
458
398
|
const generatedCode = generateDrizzleSchema("postgresql");
|
|
459
399
|
expect(generatedCode).toContain(
|
|
460
400
|
'id: varchar("id", { length: 255 }).primaryKey().notNull()',
|
|
461
401
|
);
|
|
462
402
|
expect(generatedCode).toContain(
|
|
463
|
-
'version: varchar("version", { length: 255 }).notNull().default("0.
|
|
403
|
+
'version: varchar("version", { length: 255 }).notNull().default("1.0.0")',
|
|
404
|
+
);
|
|
405
|
+
expect(generatedCode).toContain(
|
|
406
|
+
'uniqueIndex("channels_name_key").on(table.name)',
|
|
407
|
+
);
|
|
408
|
+
expect(generatedCode).toContain('name: "releases_channel_id_fk"');
|
|
409
|
+
expect(generatedCode).toContain(
|
|
410
|
+
'index("releases_channel_platform_order_idx").on(table.channel_id, table.platform, table.id)',
|
|
464
411
|
);
|
|
465
412
|
expect(generatedCode).not.toContain('key: varchar("key"');
|
|
466
413
|
expect(generatedCode).not.toContain('value: text("value"');
|
|
@@ -468,46 +415,24 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
468
415
|
});
|
|
469
416
|
|
|
470
417
|
describe("migrator enhancements", () => {
|
|
471
|
-
it("
|
|
418
|
+
it("registers only schema 1.0.0", () => {
|
|
472
419
|
expect(hotUpdaterSchemaVersions.map((schema) => schema.version)).toEqual([
|
|
473
|
-
"0.
|
|
474
|
-
"0.29.0",
|
|
475
|
-
"0.31.0",
|
|
420
|
+
"1.0.0",
|
|
476
421
|
]);
|
|
477
|
-
|
|
478
|
-
const v029Sql = createSchemaMigrationSql(
|
|
479
|
-
"0.21.0",
|
|
480
|
-
"0.29.0",
|
|
481
|
-
"postgresql",
|
|
482
|
-
).join("\n");
|
|
483
|
-
const v031Sql = createSchemaMigrationSql(
|
|
484
|
-
"0.29.0",
|
|
485
|
-
"0.31.0",
|
|
486
|
-
"postgresql",
|
|
487
|
-
).join("\n");
|
|
488
|
-
|
|
489
|
-
expect(v029Sql).toContain(
|
|
490
|
-
"alter table bundles add column rollout_cohort_count",
|
|
491
|
-
);
|
|
492
|
-
expect(v029Sql).not.toContain("bundle_patches");
|
|
493
|
-
expect(v031Sql).toContain(
|
|
494
|
-
"alter table bundles add column manifest_storage_uri",
|
|
495
|
-
);
|
|
496
|
-
expect(v031Sql).toContain("create table if not exists bundle_patches");
|
|
497
|
-
expect(v031Sql).toContain(
|
|
498
|
-
"add constraint bundle_patches_bundle_id_fk foreign key",
|
|
499
|
-
);
|
|
500
422
|
});
|
|
501
423
|
|
|
502
424
|
it("omits MySQL defaults for text and JSON columns", () => {
|
|
503
425
|
const sql = createTableSql("mysql").join("\n");
|
|
504
426
|
|
|
505
|
-
expect(sql).toContain("
|
|
506
|
-
expect(sql).not.toContain("channel text not null default");
|
|
427
|
+
expect(sql).toContain("create table channels");
|
|
507
428
|
expect(sql).toContain("metadata json not null");
|
|
508
429
|
expect(sql).not.toContain("metadata json not null default");
|
|
509
430
|
expect(sql).toContain("`key` varchar(255) primary key");
|
|
510
431
|
expect(sql).not.toContain("\nkey varchar(255) primary key");
|
|
432
|
+
expect(sql).toContain(
|
|
433
|
+
"create table api_keys (\nid varchar(255) primary key not null",
|
|
434
|
+
);
|
|
435
|
+
expect(sql).not.toContain("create table api_keys (\nid text primary key");
|
|
511
436
|
expect(sql).toContain(
|
|
512
437
|
"create index bundle_patches_bundle_id_idx on bundle_patches(bundle_id)",
|
|
513
438
|
);
|
|
@@ -534,9 +459,11 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
534
459
|
});
|
|
535
460
|
const sql = result.getSQL?.() ?? "";
|
|
536
461
|
|
|
537
|
-
expect(sql).toContain("create index bundles_channel_idx on bundles");
|
|
538
462
|
expect(sql).toContain(
|
|
539
|
-
"
|
|
463
|
+
"create index releases_scope_order_idx on releases",
|
|
464
|
+
);
|
|
465
|
+
expect(sql).toContain(
|
|
466
|
+
"add constraint releases_strategy_target_check check",
|
|
540
467
|
);
|
|
541
468
|
expect(sql).toContain(
|
|
542
469
|
"add constraint bundle_patches_bundle_id_fk foreign key",
|
|
@@ -558,7 +485,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
558
485
|
}
|
|
559
486
|
});
|
|
560
487
|
|
|
561
|
-
it("
|
|
488
|
+
it("rejects an existing v0 Kysely schema instead of upgrading it", async () => {
|
|
562
489
|
const migrationDb = new PGlite();
|
|
563
490
|
const migrationKysely = new Kysely<object>({
|
|
564
491
|
dialect: new PGliteDialect(migrationDb),
|
|
@@ -572,20 +499,6 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
572
499
|
|
|
573
500
|
try {
|
|
574
501
|
await migrationDb.exec(`
|
|
575
|
-
create table bundles (
|
|
576
|
-
id uuid primary key,
|
|
577
|
-
platform text not null,
|
|
578
|
-
should_force_update boolean not null,
|
|
579
|
-
enabled boolean not null,
|
|
580
|
-
file_hash text not null,
|
|
581
|
-
git_commit_hash text,
|
|
582
|
-
message text,
|
|
583
|
-
channel text not null default 'production',
|
|
584
|
-
storage_uri text not null,
|
|
585
|
-
target_app_version text,
|
|
586
|
-
fingerprint_hash text,
|
|
587
|
-
metadata json not null default '{}'::json
|
|
588
|
-
);
|
|
589
502
|
create table private_hot_updater_settings (
|
|
590
503
|
key varchar(255) primary key,
|
|
591
504
|
value text not null
|
|
@@ -595,31 +508,12 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
595
508
|
`);
|
|
596
509
|
|
|
597
510
|
const migrator = createMigrator(migrationHotUpdater);
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
expect(sql).toContain(
|
|
605
|
-
"alter table bundles add column rollout_cohort_count",
|
|
606
|
-
);
|
|
607
|
-
expect(sql).toContain(
|
|
608
|
-
"alter table bundles add column manifest_storage_uri",
|
|
609
|
-
);
|
|
610
|
-
expect(sql).toContain("create table if not exists bundle_patches");
|
|
611
|
-
expect(sql).not.toContain("create table if not exists bundles");
|
|
612
|
-
|
|
613
|
-
await result.execute();
|
|
614
|
-
|
|
615
|
-
const version = await migrationDb.query<{ value: string }>(
|
|
616
|
-
"select value from private_hot_updater_settings where key = 'version'",
|
|
617
|
-
);
|
|
618
|
-
expect(version.rows[0]?.value).toBe("0.31.0");
|
|
619
|
-
await migrationDb.query(
|
|
620
|
-
"select rollout_cohort_count, target_cohorts, manifest_storage_uri from bundles limit 0",
|
|
621
|
-
);
|
|
622
|
-
await migrationDb.query("select * from bundle_patches limit 0");
|
|
511
|
+
await expect(
|
|
512
|
+
migrator.migrateToLatest({
|
|
513
|
+
mode: "from-schema",
|
|
514
|
+
updateSettings: true,
|
|
515
|
+
}),
|
|
516
|
+
).rejects.toThrow("Hot Updater v1 cannot migrate schema 0.21.0");
|
|
623
517
|
} finally {
|
|
624
518
|
await migrationKysely.destroy();
|
|
625
519
|
await migrationDb.close();
|
|
@@ -694,7 +588,10 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
694
588
|
|
|
695
589
|
it("creates MongoDB indexes for runtime query fields", async () => {
|
|
696
590
|
const collection = {
|
|
697
|
-
|
|
591
|
+
find: vi.fn(() => ({
|
|
592
|
+
limit: () => ({ toArray: async () => [] }),
|
|
593
|
+
})),
|
|
594
|
+
listIndexes: () => ({ toArray: async () => [] }),
|
|
698
595
|
};
|
|
699
596
|
const client = {
|
|
700
597
|
db: () => ({
|
|
@@ -711,19 +608,28 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
711
608
|
expect(result.operations).toEqual(
|
|
712
609
|
expect.arrayContaining([
|
|
713
610
|
expect.objectContaining({
|
|
714
|
-
|
|
611
|
+
description:
|
|
612
|
+
"Create unique MongoDB index: bundles_id_idx on bundles(id)",
|
|
715
613
|
}),
|
|
716
614
|
expect.objectContaining({
|
|
717
|
-
|
|
615
|
+
description:
|
|
616
|
+
"Create unique MongoDB index: bundle_patches_id_idx on bundle_patches(id)",
|
|
718
617
|
}),
|
|
719
618
|
expect.objectContaining({
|
|
720
|
-
|
|
619
|
+
description:
|
|
620
|
+
"Create unique MongoDB index: release_catalogs_scope_key_idx on release_catalogs(scope_key)",
|
|
721
621
|
}),
|
|
722
622
|
expect.objectContaining({
|
|
723
|
-
|
|
623
|
+
description:
|
|
624
|
+
"Create MongoDB index: releases_fingerprint_hash_idx on releases(fingerprint_hash)",
|
|
724
625
|
}),
|
|
725
626
|
expect.objectContaining({
|
|
726
|
-
|
|
627
|
+
description:
|
|
628
|
+
"Create MongoDB index: bundles_platform_idx on bundles(platform)",
|
|
629
|
+
}),
|
|
630
|
+
expect.objectContaining({
|
|
631
|
+
description:
|
|
632
|
+
"Create MongoDB index: bundle_patches_base_bundle_id_idx on bundle_patches(base_bundle_id)",
|
|
727
633
|
}),
|
|
728
634
|
]),
|
|
729
635
|
);
|
|
@@ -802,7 +708,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
802
708
|
`);
|
|
803
709
|
|
|
804
710
|
await expect(migrationHotUpdater.getChannels()).rejects.toThrow(
|
|
805
|
-
"Hot Updater
|
|
711
|
+
"Hot Updater v1 cannot migrate schema 0.21.0 in place.",
|
|
806
712
|
);
|
|
807
713
|
} finally {
|
|
808
714
|
await migrationKysely.destroy();
|
|
@@ -812,7 +718,12 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
812
718
|
|
|
813
719
|
it("rejects runtime access when a MongoDB schema is stale", async () => {
|
|
814
720
|
const settings = {
|
|
815
|
-
|
|
721
|
+
find: vi.fn(({ key }: { readonly key: string }) => ({
|
|
722
|
+
limit: () => ({
|
|
723
|
+
toArray: async () =>
|
|
724
|
+
key === "version" ? [{ key, value: "0.21.0" }] : [],
|
|
725
|
+
}),
|
|
726
|
+
})),
|
|
816
727
|
};
|
|
817
728
|
const bundles = {
|
|
818
729
|
countDocuments: vi.fn(async () => 0),
|
|
@@ -836,7 +747,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
836
747
|
});
|
|
837
748
|
|
|
838
749
|
await expect(mongoHotUpdater.getBundles({ limit: 10 })).rejects.toThrow(
|
|
839
|
-
"Hot Updater
|
|
750
|
+
"Hot Updater v1 cannot migrate schema 0.21.0 in place.",
|
|
840
751
|
);
|
|
841
752
|
expect(bundles.countDocuments).not.toHaveBeenCalled();
|
|
842
753
|
});
|
|
@@ -848,426 +759,84 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
848
759
|
limit: 10,
|
|
849
760
|
where: { id: { in: [] } },
|
|
850
761
|
});
|
|
851
|
-
const byTargetAppVersion = await hotUpdater.getBundles({
|
|
852
|
-
limit: 10,
|
|
853
|
-
where: { targetAppVersionIn: [] },
|
|
854
|
-
});
|
|
855
|
-
|
|
856
762
|
expect(byId.data).toEqual([]);
|
|
857
763
|
expect(byId.pagination.total).toBe(0);
|
|
858
|
-
expect(byTargetAppVersion.data).toEqual([]);
|
|
859
|
-
expect(byTargetAppVersion.pagination.total).toBe(0);
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
it("combines Prisma targetAppVersion filters without overwriting", async () => {
|
|
863
|
-
const bundles = {
|
|
864
|
-
count: vi.fn(async () => 0),
|
|
865
|
-
createMany: vi.fn(),
|
|
866
|
-
deleteMany: vi.fn(),
|
|
867
|
-
findFirst: vi.fn(),
|
|
868
|
-
findMany: vi.fn(async () => []),
|
|
869
|
-
upsert: vi.fn(),
|
|
870
|
-
};
|
|
871
|
-
const patches = {
|
|
872
|
-
count: vi.fn(),
|
|
873
|
-
createMany: vi.fn(),
|
|
874
|
-
deleteMany: vi.fn(),
|
|
875
|
-
findFirst: vi.fn(),
|
|
876
|
-
findMany: vi.fn(async () => []),
|
|
877
|
-
upsert: vi.fn(),
|
|
878
|
-
};
|
|
879
|
-
const plugin = prismaAdapter({
|
|
880
|
-
prisma: { bundles, bundle_patches: patches },
|
|
881
|
-
provider: "postgresql",
|
|
882
|
-
})();
|
|
883
|
-
|
|
884
|
-
await plugin.getBundles({
|
|
885
|
-
limit: 10,
|
|
886
|
-
where: {
|
|
887
|
-
targetAppVersion: "1.0.x",
|
|
888
|
-
targetAppVersionNotNull: true,
|
|
889
|
-
},
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
expect(bundles.findMany).toHaveBeenCalledWith(
|
|
893
|
-
expect.objectContaining({
|
|
894
|
-
where: expect.objectContaining({
|
|
895
|
-
AND: [
|
|
896
|
-
{ target_app_version: "1.0.x" },
|
|
897
|
-
{ target_app_version: { not: null } },
|
|
898
|
-
],
|
|
899
|
-
}),
|
|
900
|
-
}),
|
|
901
|
-
);
|
|
902
|
-
});
|
|
903
|
-
|
|
904
|
-
it("combines MongoDB targetAppVersion filters without overwriting", async () => {
|
|
905
|
-
const toArray = vi.fn(async () => []);
|
|
906
|
-
const limit = vi.fn(() => ({ toArray }));
|
|
907
|
-
const skip = vi.fn(() => ({ limit }));
|
|
908
|
-
const sort = vi.fn(() => ({ skip }));
|
|
909
|
-
const bundles = {
|
|
910
|
-
countDocuments: vi.fn(async () => 0),
|
|
911
|
-
distinct: vi.fn(),
|
|
912
|
-
find: vi.fn(() => ({ sort })),
|
|
913
|
-
findOne: vi.fn(),
|
|
914
|
-
};
|
|
915
|
-
const patches = {
|
|
916
|
-
deleteMany: vi.fn(),
|
|
917
|
-
find: vi.fn(() => ({
|
|
918
|
-
sort: vi.fn(() => ({ toArray: vi.fn(async () => []) })),
|
|
919
|
-
})),
|
|
920
|
-
insertMany: vi.fn(),
|
|
921
|
-
};
|
|
922
|
-
const client = {
|
|
923
|
-
db: () => ({
|
|
924
|
-
collection: (name: string) =>
|
|
925
|
-
name === "bundle_patches" ? patches : bundles,
|
|
926
|
-
}),
|
|
927
|
-
} as unknown as MongoClient;
|
|
928
|
-
const plugin = mongoAdapter({ client })();
|
|
929
|
-
|
|
930
|
-
await plugin.getBundles({
|
|
931
|
-
limit: 10,
|
|
932
|
-
where: {
|
|
933
|
-
targetAppVersion: "1.0.x",
|
|
934
|
-
targetAppVersionNotNull: true,
|
|
935
|
-
},
|
|
936
|
-
});
|
|
937
|
-
|
|
938
|
-
expect(bundles.countDocuments).toHaveBeenCalledWith({
|
|
939
|
-
$and: [
|
|
940
|
-
{ target_app_version: "1.0.x" },
|
|
941
|
-
{ target_app_version: { $exists: true, $nin: [null, ""] } },
|
|
942
|
-
],
|
|
943
|
-
});
|
|
944
764
|
});
|
|
945
765
|
|
|
946
|
-
it("
|
|
947
|
-
const
|
|
948
|
-
const fingerprintRow = bundleToRow(fingerprintFastPathBundle);
|
|
949
|
-
const bundles = {
|
|
950
|
-
count: vi.fn(async () => {
|
|
951
|
-
throw new Error("unexpected generic Prisma count");
|
|
952
|
-
}),
|
|
953
|
-
createMany: vi.fn(),
|
|
954
|
-
deleteMany: vi.fn(),
|
|
955
|
-
findFirst: vi.fn(),
|
|
956
|
-
findMany: vi
|
|
957
|
-
.fn()
|
|
958
|
-
.mockResolvedValueOnce([
|
|
959
|
-
{ target_app_version: appVersionRow.target_app_version },
|
|
960
|
-
])
|
|
961
|
-
.mockResolvedValueOnce([appVersionRow])
|
|
962
|
-
.mockResolvedValueOnce([fingerprintRow]),
|
|
963
|
-
upsert: vi.fn(),
|
|
964
|
-
};
|
|
965
|
-
const patches = {
|
|
766
|
+
it("commits Prisma bundle changes inside a transaction when available", async () => {
|
|
767
|
+
const rootBundles = {
|
|
966
768
|
count: vi.fn(),
|
|
967
|
-
|
|
769
|
+
create: vi.fn(),
|
|
968
770
|
deleteMany: vi.fn(),
|
|
969
771
|
findFirst: vi.fn(),
|
|
970
|
-
findMany: vi.fn(
|
|
971
|
-
upsert: vi.fn(),
|
|
972
|
-
};
|
|
973
|
-
const plugin = prismaAdapter({
|
|
974
|
-
prisma: { bundles, bundle_patches: patches },
|
|
975
|
-
provider: "postgresql",
|
|
976
|
-
})();
|
|
977
|
-
|
|
978
|
-
await expect(
|
|
979
|
-
plugin.getUpdateInfo?.({
|
|
980
|
-
_updateStrategy: "appVersion",
|
|
981
|
-
appVersion: "1.0.0",
|
|
982
|
-
bundleId: NIL_UUID,
|
|
983
|
-
platform: "ios",
|
|
984
|
-
}),
|
|
985
|
-
).resolves.toMatchObject({
|
|
986
|
-
id: appVersionFastPathBundle.id,
|
|
987
|
-
status: "UPDATE",
|
|
988
|
-
});
|
|
989
|
-
await expect(
|
|
990
|
-
plugin.getUpdateInfo?.({
|
|
991
|
-
_updateStrategy: "fingerprint",
|
|
992
|
-
bundleId: NIL_UUID,
|
|
993
|
-
fingerprintHash: "fingerprint-fast-path",
|
|
994
|
-
platform: "ios",
|
|
995
|
-
}),
|
|
996
|
-
).resolves.toMatchObject({
|
|
997
|
-
id: fingerprintFastPathBundle.id,
|
|
998
|
-
status: "UPDATE",
|
|
999
|
-
});
|
|
1000
|
-
|
|
1001
|
-
expect(bundles.count).not.toHaveBeenCalled();
|
|
1002
|
-
expect(bundles.findMany).toHaveBeenNthCalledWith(
|
|
1003
|
-
1,
|
|
1004
|
-
expect.objectContaining({
|
|
1005
|
-
select: { target_app_version: true },
|
|
1006
|
-
where: expect.objectContaining({
|
|
1007
|
-
channel: "production",
|
|
1008
|
-
id: { gte: NIL_UUID },
|
|
1009
|
-
}),
|
|
1010
|
-
}),
|
|
1011
|
-
);
|
|
1012
|
-
expect(bundles.findMany).toHaveBeenNthCalledWith(
|
|
1013
|
-
2,
|
|
1014
|
-
expect.objectContaining({
|
|
1015
|
-
where: expect.objectContaining({
|
|
1016
|
-
target_app_version: { in: ["1.0.0"] },
|
|
1017
|
-
}),
|
|
1018
|
-
}),
|
|
1019
|
-
);
|
|
1020
|
-
expect(bundles.findMany).toHaveBeenNthCalledWith(
|
|
1021
|
-
3,
|
|
1022
|
-
expect.objectContaining({
|
|
1023
|
-
where: expect.objectContaining({
|
|
1024
|
-
fingerprint_hash: "fingerprint-fast-path",
|
|
1025
|
-
}),
|
|
1026
|
-
}),
|
|
1027
|
-
);
|
|
1028
|
-
expect(patches.findMany).toHaveBeenCalledTimes(2);
|
|
1029
|
-
});
|
|
1030
|
-
|
|
1031
|
-
it("uses Drizzle update-check queries without generic list pagination", async () => {
|
|
1032
|
-
const appVersionRow = bundleToRow(appVersionFastPathBundle);
|
|
1033
|
-
const fingerprintRow = bundleToRow(fingerprintFastPathBundle);
|
|
1034
|
-
const tables = {
|
|
1035
|
-
bundle_patches: {
|
|
1036
|
-
bundle_id: "bundle_id",
|
|
1037
|
-
id: "patch_id",
|
|
1038
|
-
order_index: "order_index",
|
|
1039
|
-
},
|
|
1040
|
-
bundles: {
|
|
1041
|
-
channel: "channel",
|
|
1042
|
-
enabled: "enabled",
|
|
1043
|
-
fingerprint_hash: "fingerprint_hash",
|
|
1044
|
-
id: "id",
|
|
1045
|
-
platform: "platform",
|
|
1046
|
-
target_app_version: "target_app_version",
|
|
1047
|
-
},
|
|
1048
|
-
};
|
|
1049
|
-
const bundleFindMany = vi
|
|
1050
|
-
.fn()
|
|
1051
|
-
.mockResolvedValueOnce([
|
|
1052
|
-
{ target_app_version: appVersionRow.target_app_version },
|
|
1053
|
-
])
|
|
1054
|
-
.mockResolvedValueOnce([appVersionRow])
|
|
1055
|
-
.mockResolvedValueOnce([fingerprintRow]);
|
|
1056
|
-
const patchFindMany = vi.fn(async () => []);
|
|
1057
|
-
const db = {
|
|
1058
|
-
_: { fullSchema: tables },
|
|
1059
|
-
$count: vi.fn(async () => {
|
|
1060
|
-
throw new Error("unexpected generic Drizzle count");
|
|
1061
|
-
}),
|
|
1062
|
-
delete: vi.fn(() => ({
|
|
1063
|
-
where: vi.fn(async () => undefined),
|
|
1064
|
-
})),
|
|
1065
|
-
insert: vi.fn(),
|
|
1066
|
-
query: {
|
|
1067
|
-
bundle_patches: {
|
|
1068
|
-
findMany: patchFindMany,
|
|
1069
|
-
},
|
|
1070
|
-
bundles: {
|
|
1071
|
-
findFirst: vi.fn(),
|
|
1072
|
-
findMany: bundleFindMany,
|
|
1073
|
-
},
|
|
1074
|
-
},
|
|
1075
|
-
select: vi.fn(),
|
|
772
|
+
findMany: vi.fn(),
|
|
1076
773
|
update: vi.fn(),
|
|
774
|
+
upsert: vi.fn(),
|
|
1077
775
|
};
|
|
1078
|
-
const
|
|
1079
|
-
db,
|
|
1080
|
-
provider: "postgresql",
|
|
1081
|
-
})();
|
|
1082
|
-
|
|
1083
|
-
await expect(
|
|
1084
|
-
plugin.getUpdateInfo?.({
|
|
1085
|
-
_updateStrategy: "appVersion",
|
|
1086
|
-
appVersion: "1.0.0",
|
|
1087
|
-
bundleId: NIL_UUID,
|
|
1088
|
-
platform: "ios",
|
|
1089
|
-
}),
|
|
1090
|
-
).resolves.toMatchObject({
|
|
1091
|
-
id: appVersionFastPathBundle.id,
|
|
1092
|
-
status: "UPDATE",
|
|
1093
|
-
});
|
|
1094
|
-
await expect(
|
|
1095
|
-
plugin.getUpdateInfo?.({
|
|
1096
|
-
_updateStrategy: "fingerprint",
|
|
1097
|
-
bundleId: NIL_UUID,
|
|
1098
|
-
fingerprintHash: "fingerprint-fast-path",
|
|
1099
|
-
platform: "ios",
|
|
1100
|
-
}),
|
|
1101
|
-
).resolves.toMatchObject({
|
|
1102
|
-
id: fingerprintFastPathBundle.id,
|
|
1103
|
-
status: "UPDATE",
|
|
1104
|
-
});
|
|
1105
|
-
|
|
1106
|
-
expect(db.$count).not.toHaveBeenCalled();
|
|
1107
|
-
expect(bundleFindMany).toHaveBeenNthCalledWith(
|
|
1108
|
-
1,
|
|
1109
|
-
expect.objectContaining({
|
|
1110
|
-
columns: { target_app_version: true },
|
|
1111
|
-
}),
|
|
1112
|
-
);
|
|
1113
|
-
const appVersionWhere = JSON.stringify(bundleFindMany.mock.calls[0]?.[0]);
|
|
1114
|
-
const fingerprintWhere = JSON.stringify(
|
|
1115
|
-
bundleFindMany.mock.calls[2]?.[0],
|
|
1116
|
-
);
|
|
1117
|
-
expect(appVersionWhere).toContain("production");
|
|
1118
|
-
expect(appVersionWhere).toContain(NIL_UUID);
|
|
1119
|
-
expect(fingerprintWhere).toContain("production");
|
|
1120
|
-
expect(fingerprintWhere).toContain(NIL_UUID);
|
|
1121
|
-
expect(fingerprintWhere).toContain("fingerprint-fast-path");
|
|
1122
|
-
expect(bundleFindMany).toHaveBeenCalledTimes(3);
|
|
1123
|
-
expect(patchFindMany).toHaveBeenCalledTimes(2);
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
it("uses MongoDB update-check queries without generic list pagination", async () => {
|
|
1127
|
-
const appVersionRow = bundleToRow(appVersionFastPathBundle);
|
|
1128
|
-
const fingerprintRow = bundleToRow(fingerprintFastPathBundle);
|
|
1129
|
-
const projectToArray = vi.fn(async () => [
|
|
1130
|
-
{ target_app_version: appVersionRow.target_app_version },
|
|
1131
|
-
]);
|
|
1132
|
-
const sortToArray = vi
|
|
1133
|
-
.fn()
|
|
1134
|
-
.mockResolvedValueOnce([appVersionRow])
|
|
1135
|
-
.mockResolvedValueOnce([fingerprintRow]);
|
|
1136
|
-
const project = vi.fn(() => ({ toArray: projectToArray }));
|
|
1137
|
-
const sort = vi.fn(() => ({ toArray: sortToArray }));
|
|
1138
|
-
const bundles = {
|
|
1139
|
-
countDocuments: vi.fn(async () => {
|
|
1140
|
-
throw new Error("unexpected generic MongoDB count");
|
|
1141
|
-
}),
|
|
1142
|
-
deleteMany: vi.fn(),
|
|
1143
|
-
distinct: vi.fn(),
|
|
1144
|
-
find: vi.fn((filter: Record<string, unknown>) => {
|
|
1145
|
-
const targetAppVersion = filter["target_app_version"];
|
|
1146
|
-
if (
|
|
1147
|
-
typeof targetAppVersion === "object" &&
|
|
1148
|
-
targetAppVersion !== null &&
|
|
1149
|
-
"$exists" in targetAppVersion
|
|
1150
|
-
) {
|
|
1151
|
-
return { project };
|
|
1152
|
-
}
|
|
1153
|
-
return { sort };
|
|
1154
|
-
}),
|
|
1155
|
-
findOne: vi.fn(),
|
|
1156
|
-
updateOne: vi.fn(),
|
|
1157
|
-
};
|
|
1158
|
-
const patches = {
|
|
1159
|
-
deleteMany: vi.fn(),
|
|
1160
|
-
find: vi.fn(() => ({
|
|
1161
|
-
sort: vi.fn(() => ({ toArray: vi.fn(async () => []) })),
|
|
1162
|
-
})),
|
|
1163
|
-
insertMany: vi.fn(),
|
|
1164
|
-
};
|
|
1165
|
-
const client = {
|
|
1166
|
-
db: () => ({
|
|
1167
|
-
collection: (name: string) =>
|
|
1168
|
-
name === "bundle_patches" ? patches : bundles,
|
|
1169
|
-
}),
|
|
1170
|
-
} as unknown as MongoClient;
|
|
1171
|
-
const plugin = mongoAdapter({ client })();
|
|
1172
|
-
|
|
1173
|
-
await expect(
|
|
1174
|
-
plugin.getUpdateInfo?.({
|
|
1175
|
-
_updateStrategy: "appVersion",
|
|
1176
|
-
appVersion: "1.0.0",
|
|
1177
|
-
bundleId: NIL_UUID,
|
|
1178
|
-
platform: "ios",
|
|
1179
|
-
}),
|
|
1180
|
-
).resolves.toMatchObject({
|
|
1181
|
-
id: appVersionFastPathBundle.id,
|
|
1182
|
-
status: "UPDATE",
|
|
1183
|
-
});
|
|
1184
|
-
await expect(
|
|
1185
|
-
plugin.getUpdateInfo?.({
|
|
1186
|
-
_updateStrategy: "fingerprint",
|
|
1187
|
-
bundleId: NIL_UUID,
|
|
1188
|
-
fingerprintHash: "fingerprint-fast-path",
|
|
1189
|
-
platform: "ios",
|
|
1190
|
-
}),
|
|
1191
|
-
).resolves.toMatchObject({
|
|
1192
|
-
id: fingerprintFastPathBundle.id,
|
|
1193
|
-
status: "UPDATE",
|
|
1194
|
-
});
|
|
1195
|
-
|
|
1196
|
-
expect(bundles.countDocuments).not.toHaveBeenCalled();
|
|
1197
|
-
expect(bundles.find).toHaveBeenNthCalledWith(
|
|
1198
|
-
1,
|
|
1199
|
-
expect.objectContaining({
|
|
1200
|
-
channel: "production",
|
|
1201
|
-
id: { $gte: NIL_UUID },
|
|
1202
|
-
target_app_version: { $exists: true, $nin: [null, ""] },
|
|
1203
|
-
}),
|
|
1204
|
-
);
|
|
1205
|
-
expect(bundles.find).toHaveBeenNthCalledWith(
|
|
1206
|
-
2,
|
|
1207
|
-
expect.objectContaining({
|
|
1208
|
-
target_app_version: { $in: ["1.0.0"] },
|
|
1209
|
-
}),
|
|
1210
|
-
);
|
|
1211
|
-
expect(bundles.find).toHaveBeenNthCalledWith(
|
|
1212
|
-
3,
|
|
1213
|
-
expect.objectContaining({
|
|
1214
|
-
fingerprint_hash: "fingerprint-fast-path",
|
|
1215
|
-
}),
|
|
1216
|
-
);
|
|
1217
|
-
expect(patches.find).toHaveBeenCalledTimes(2);
|
|
1218
|
-
});
|
|
1219
|
-
|
|
1220
|
-
it("commits Prisma bundle changes inside a transaction when available", async () => {
|
|
1221
|
-
const rootBundles = {
|
|
776
|
+
const rootPatches = {
|
|
1222
777
|
count: vi.fn(),
|
|
1223
|
-
|
|
778
|
+
create: vi.fn(),
|
|
1224
779
|
deleteMany: vi.fn(),
|
|
1225
780
|
findFirst: vi.fn(),
|
|
1226
781
|
findMany: vi.fn(),
|
|
782
|
+
update: vi.fn(),
|
|
1227
783
|
upsert: vi.fn(),
|
|
1228
784
|
};
|
|
1229
|
-
const
|
|
785
|
+
const channels = {
|
|
1230
786
|
count: vi.fn(),
|
|
1231
|
-
|
|
787
|
+
create: vi.fn(),
|
|
1232
788
|
deleteMany: vi.fn(),
|
|
1233
|
-
findFirst: vi.fn()
|
|
789
|
+
findFirst: vi.fn(async () => ({
|
|
790
|
+
id: transactionChannelId,
|
|
791
|
+
name: "production",
|
|
792
|
+
})),
|
|
1234
793
|
findMany: vi.fn(),
|
|
794
|
+
update: vi.fn(),
|
|
1235
795
|
upsert: vi.fn(),
|
|
1236
796
|
};
|
|
1237
797
|
const txBundles = {
|
|
1238
798
|
...rootBundles,
|
|
1239
|
-
|
|
1240
|
-
};
|
|
1241
|
-
const txPatches = {
|
|
1242
|
-
...rootPatches,
|
|
1243
|
-
deleteMany: vi.fn(async () => undefined),
|
|
799
|
+
create: vi.fn(async ({ data }) => data),
|
|
1244
800
|
};
|
|
801
|
+
const txPatches = { ...rootPatches };
|
|
1245
802
|
const $transaction = vi.fn(
|
|
1246
803
|
async (operation: (tx: Record<string, unknown>) => Promise<unknown>) =>
|
|
1247
804
|
operation({
|
|
1248
805
|
bundle_patches: txPatches,
|
|
1249
806
|
bundles: txBundles,
|
|
807
|
+
channels,
|
|
1250
808
|
}),
|
|
1251
809
|
);
|
|
1252
|
-
const
|
|
810
|
+
const adapter = prismaAdapter({
|
|
1253
811
|
prisma: {
|
|
1254
812
|
$transaction,
|
|
1255
813
|
bundle_patches: rootPatches,
|
|
1256
814
|
bundles: rootBundles,
|
|
815
|
+
channels,
|
|
1257
816
|
},
|
|
1258
817
|
provider: "postgresql",
|
|
1259
|
-
})
|
|
818
|
+
});
|
|
1260
819
|
|
|
1261
|
-
await
|
|
1262
|
-
|
|
820
|
+
await adapter.commit({
|
|
821
|
+
changes: [
|
|
822
|
+
{
|
|
823
|
+
model: "bundles",
|
|
824
|
+
operation: "insert",
|
|
825
|
+
row: bundleToRow(transactionBundle),
|
|
826
|
+
},
|
|
827
|
+
],
|
|
828
|
+
});
|
|
1263
829
|
|
|
1264
830
|
expect($transaction).toHaveBeenCalledTimes(1);
|
|
1265
|
-
expect(txBundles.
|
|
1266
|
-
expect(rootBundles.
|
|
831
|
+
expect(txBundles.create).toHaveBeenCalledTimes(1);
|
|
832
|
+
expect(rootBundles.create).not.toHaveBeenCalled();
|
|
1267
833
|
});
|
|
1268
834
|
|
|
1269
835
|
it("commits Drizzle bundle changes inside a transaction when available", async () => {
|
|
1270
836
|
const tables = {
|
|
837
|
+
bundle_events: {
|
|
838
|
+
id: "event_id",
|
|
839
|
+
},
|
|
1271
840
|
bundle_patches: {
|
|
1272
841
|
bundle_id: "bundle_id",
|
|
1273
842
|
id: "patch_id",
|
|
@@ -1276,6 +845,20 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
1276
845
|
bundles: {
|
|
1277
846
|
id: "id",
|
|
1278
847
|
},
|
|
848
|
+
channels: {
|
|
849
|
+
id: sql.raw("channel_id"),
|
|
850
|
+
name: sql.raw("channel_name"),
|
|
851
|
+
},
|
|
852
|
+
api_keys: {
|
|
853
|
+
id: "access_key_id",
|
|
854
|
+
},
|
|
855
|
+
release_catalogs: {
|
|
856
|
+
scope_key: "scope_key",
|
|
857
|
+
},
|
|
858
|
+
releases: {
|
|
859
|
+
id: "release_id",
|
|
860
|
+
scope_key: "scope_key",
|
|
861
|
+
},
|
|
1279
862
|
};
|
|
1280
863
|
const rootInsert = vi.fn(() => ({
|
|
1281
864
|
values: vi.fn(() => ({ execute: vi.fn(async () => undefined) })),
|
|
@@ -1291,13 +874,37 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
1291
874
|
})),
|
|
1292
875
|
insert,
|
|
1293
876
|
query: {
|
|
877
|
+
bundle_events: {
|
|
878
|
+
findFirst: vi.fn(),
|
|
879
|
+
findMany: vi.fn(),
|
|
880
|
+
},
|
|
1294
881
|
bundle_patches: {
|
|
882
|
+
findFirst: vi.fn(),
|
|
1295
883
|
findMany: vi.fn(),
|
|
1296
884
|
},
|
|
1297
885
|
bundles: {
|
|
1298
886
|
findFirst: vi.fn(async () => undefined),
|
|
1299
887
|
findMany: vi.fn(),
|
|
1300
888
|
},
|
|
889
|
+
channels: {
|
|
890
|
+
findFirst: vi.fn(async () => ({
|
|
891
|
+
id: transactionChannelId,
|
|
892
|
+
name: "production",
|
|
893
|
+
})),
|
|
894
|
+
findMany: vi.fn(),
|
|
895
|
+
},
|
|
896
|
+
api_keys: {
|
|
897
|
+
findFirst: vi.fn(),
|
|
898
|
+
findMany: vi.fn(),
|
|
899
|
+
},
|
|
900
|
+
release_catalogs: {
|
|
901
|
+
findFirst: vi.fn(),
|
|
902
|
+
findMany: vi.fn(),
|
|
903
|
+
},
|
|
904
|
+
releases: {
|
|
905
|
+
findFirst: vi.fn(),
|
|
906
|
+
findMany: vi.fn(),
|
|
907
|
+
},
|
|
1301
908
|
},
|
|
1302
909
|
select: vi.fn(),
|
|
1303
910
|
update: vi.fn(() => ({
|
|
@@ -1315,26 +922,27 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
1315
922
|
...createDb(rootInsert),
|
|
1316
923
|
transaction,
|
|
1317
924
|
};
|
|
1318
|
-
const
|
|
925
|
+
const adapter = drizzleAdapter({
|
|
1319
926
|
db,
|
|
1320
927
|
provider: "postgresql",
|
|
1321
|
-
})
|
|
928
|
+
});
|
|
1322
929
|
|
|
1323
|
-
await
|
|
1324
|
-
|
|
930
|
+
await adapter.commit({
|
|
931
|
+
changes: [
|
|
932
|
+
{
|
|
933
|
+
model: "bundles",
|
|
934
|
+
operation: "insert",
|
|
935
|
+
row: bundleToRow(transactionBundle),
|
|
936
|
+
},
|
|
937
|
+
],
|
|
938
|
+
});
|
|
1325
939
|
|
|
1326
940
|
expect(transaction).toHaveBeenCalledTimes(1);
|
|
1327
941
|
expect(txInsert).toHaveBeenCalledTimes(1);
|
|
1328
942
|
expect(rootInsert).not.toHaveBeenCalled();
|
|
1329
943
|
});
|
|
1330
944
|
|
|
1331
|
-
it("
|
|
1332
|
-
const session = {
|
|
1333
|
-
endSession: vi.fn(async () => undefined),
|
|
1334
|
-
withTransaction: vi.fn(async (operation: () => Promise<void>) =>
|
|
1335
|
-
operation(),
|
|
1336
|
-
),
|
|
1337
|
-
};
|
|
945
|
+
it("does not expose an implicit MongoDB transaction", () => {
|
|
1338
946
|
const bundles = {
|
|
1339
947
|
countDocuments: vi.fn(),
|
|
1340
948
|
deleteMany: vi.fn(),
|
|
@@ -1353,64 +961,69 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
1353
961
|
collection: (name: string) =>
|
|
1354
962
|
name === "bundle_patches" ? patches : bundles,
|
|
1355
963
|
}),
|
|
1356
|
-
startSession: vi.fn(() => session),
|
|
1357
964
|
} as unknown as MongoClient;
|
|
1358
|
-
const
|
|
965
|
+
const adapter = mongoAdapter({ client });
|
|
1359
966
|
|
|
1360
|
-
|
|
1361
|
-
await plugin.commitBundle();
|
|
1362
|
-
|
|
1363
|
-
expect(client.startSession).toHaveBeenCalledTimes(1);
|
|
1364
|
-
expect(session.withTransaction).toHaveBeenCalledTimes(1);
|
|
1365
|
-
expect(bundles.updateOne).toHaveBeenCalledWith(
|
|
1366
|
-
{ id: transactionBundle.id },
|
|
1367
|
-
expect.any(Object),
|
|
1368
|
-
expect.objectContaining({ session, upsert: true }),
|
|
1369
|
-
);
|
|
1370
|
-
expect(patches.deleteMany).toHaveBeenCalledWith(
|
|
1371
|
-
{ bundle_id: transactionBundle.id },
|
|
1372
|
-
{ session },
|
|
1373
|
-
);
|
|
1374
|
-
expect(session.endSession).toHaveBeenCalledTimes(1);
|
|
967
|
+
expect(Reflect.has(adapter, "transaction")).toBe(false);
|
|
1375
968
|
});
|
|
1376
|
-
});
|
|
1377
969
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
970
|
+
it("uses a MongoDB session when transactions are enabled", async () => {
|
|
971
|
+
const client = new MongoClient("mongodb://localhost");
|
|
972
|
+
const session = client.startSession();
|
|
973
|
+
const withTransaction = vi.fn(
|
|
974
|
+
async (operation: (session: ClientSession) => Promise<unknown>) =>
|
|
975
|
+
operation(session),
|
|
976
|
+
);
|
|
977
|
+
Object.defineProperty(session, "withTransaction", {
|
|
978
|
+
value: withTransaction,
|
|
979
|
+
});
|
|
980
|
+
const insertOne = vi.fn(async () => undefined);
|
|
981
|
+
Object.defineProperty(client, "db", {
|
|
982
|
+
value: () => ({
|
|
983
|
+
collection: () => ({
|
|
984
|
+
findOne: vi.fn(async () => ({
|
|
985
|
+
id: transactionChannelId,
|
|
986
|
+
name: "production",
|
|
987
|
+
})),
|
|
988
|
+
insertOne,
|
|
989
|
+
updateOne: vi.fn(async () => ({ matchedCount: 1 })),
|
|
990
|
+
}),
|
|
1393
991
|
}),
|
|
1394
|
-
)
|
|
1395
|
-
|
|
992
|
+
});
|
|
993
|
+
const withSession = vi.fn(
|
|
994
|
+
async (operation: (session: ClientSession) => Promise<unknown>) =>
|
|
995
|
+
operation(session),
|
|
1396
996
|
);
|
|
997
|
+
Object.defineProperty(client, "withSession", { value: withSession });
|
|
998
|
+
const adapter = mongoAdapter({ client, transactions: true });
|
|
999
|
+
|
|
1000
|
+
await adapter.commit({
|
|
1001
|
+
changes: [
|
|
1002
|
+
{
|
|
1003
|
+
model: "bundles",
|
|
1004
|
+
operation: "insert",
|
|
1005
|
+
row: bundleToRow(transactionBundle),
|
|
1006
|
+
},
|
|
1007
|
+
],
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
expect(withSession).toHaveBeenCalledTimes(1);
|
|
1011
|
+
expect(withTransaction).toHaveBeenCalledTimes(1);
|
|
1012
|
+
expect(insertOne).toHaveBeenCalledWith(bundleToRow(transactionBundle), {
|
|
1013
|
+
session,
|
|
1014
|
+
});
|
|
1397
1015
|
});
|
|
1398
1016
|
});
|
|
1399
1017
|
|
|
1400
1018
|
describe("getBundleById", () => {
|
|
1401
1019
|
it("should retrieve bundle by id without Prisma validation errors", async () => {
|
|
1402
1020
|
const bundle: Bundle = {
|
|
1021
|
+
archiveByteSize: 1_024,
|
|
1403
1022
|
id: "00000000-0000-0000-0000-000000000010",
|
|
1404
1023
|
platform: "ios",
|
|
1405
|
-
shouldForceUpdate: false,
|
|
1406
|
-
enabled: true,
|
|
1407
1024
|
fileHash: "test-hash",
|
|
1408
1025
|
gitCommitHash: null,
|
|
1409
|
-
message: "Test bundle for getBundleById",
|
|
1410
|
-
channel: "production",
|
|
1411
1026
|
storageUri: "s3://test-bucket/test.zip",
|
|
1412
|
-
targetAppVersion: "1.0.0",
|
|
1413
|
-
fingerprintHash: null,
|
|
1414
1027
|
};
|
|
1415
1028
|
|
|
1416
1029
|
await hotUpdater.insertBundle(bundle);
|
|
@@ -1434,59 +1047,23 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
1434
1047
|
});
|
|
1435
1048
|
|
|
1436
1049
|
describe("getChannels", () => {
|
|
1437
|
-
it("
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
message: "Bundle 1",
|
|
1447
|
-
channel: "production",
|
|
1448
|
-
storageUri: "s3://test/1.zip",
|
|
1449
|
-
targetAppVersion: "1.0.0",
|
|
1450
|
-
fingerprintHash: null,
|
|
1451
|
-
},
|
|
1452
|
-
{
|
|
1453
|
-
id: "00000000-0000-0000-0000-000000000021",
|
|
1454
|
-
platform: "android",
|
|
1455
|
-
shouldForceUpdate: false,
|
|
1456
|
-
enabled: true,
|
|
1457
|
-
fileHash: "hash2",
|
|
1458
|
-
gitCommitHash: null,
|
|
1459
|
-
message: "Bundle 2",
|
|
1460
|
-
channel: "staging",
|
|
1461
|
-
storageUri: "s3://test/2.zip",
|
|
1462
|
-
targetAppVersion: "1.0.0",
|
|
1463
|
-
fingerprintHash: null,
|
|
1464
|
-
},
|
|
1465
|
-
{
|
|
1466
|
-
id: "00000000-0000-0000-0000-000000000022",
|
|
1467
|
-
platform: "ios",
|
|
1468
|
-
shouldForceUpdate: false,
|
|
1469
|
-
enabled: true,
|
|
1470
|
-
fileHash: "hash3",
|
|
1471
|
-
gitCommitHash: null,
|
|
1472
|
-
message: "Bundle 3",
|
|
1473
|
-
channel: "production",
|
|
1474
|
-
storageUri: "s3://test/3.zip",
|
|
1475
|
-
targetAppVersion: "1.0.0",
|
|
1476
|
-
fingerprintHash: null,
|
|
1477
|
-
},
|
|
1478
|
-
];
|
|
1479
|
-
|
|
1480
|
-
for (const bundle of bundles) {
|
|
1481
|
-
await hotUpdater.insertBundle(bundle);
|
|
1482
|
-
}
|
|
1050
|
+
it("retrieves canonical Channel rows without Prisma validation errors", async () => {
|
|
1051
|
+
await hotUpdater.insertChannel({
|
|
1052
|
+
onConflict: "returnExisting",
|
|
1053
|
+
row: { id: "channel-production", name: "production" },
|
|
1054
|
+
});
|
|
1055
|
+
await hotUpdater.insertChannel({
|
|
1056
|
+
onConflict: "returnExisting",
|
|
1057
|
+
row: { id: "channel-staging", name: "staging" },
|
|
1058
|
+
});
|
|
1483
1059
|
|
|
1484
|
-
// This should not throw a Prisma validation error
|
|
1485
1060
|
const channels = await hotUpdater.getChannels();
|
|
1486
1061
|
|
|
1487
1062
|
expect(channels).toHaveLength(2);
|
|
1488
|
-
expect(channels).
|
|
1489
|
-
|
|
1063
|
+
expect(channels.map(({ name }) => name)).toEqual([
|
|
1064
|
+
"production",
|
|
1065
|
+
"staging",
|
|
1066
|
+
]);
|
|
1490
1067
|
});
|
|
1491
1068
|
|
|
1492
1069
|
it("should return empty array when no bundles exist", async () => {
|
|
@@ -1495,7 +1072,7 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
1495
1072
|
});
|
|
1496
1073
|
});
|
|
1497
1074
|
|
|
1498
|
-
describe("
|
|
1075
|
+
describe("getArtifactInfo with storage plugins", () => {
|
|
1499
1076
|
beforeEach(() => {
|
|
1500
1077
|
// Fix time for deterministic signed URLs
|
|
1501
1078
|
vi.useFakeTimers();
|
|
@@ -1508,27 +1085,17 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
1508
1085
|
|
|
1509
1086
|
it("resolves s3:// storage URI to signed URL via s3StoragePlugin", async () => {
|
|
1510
1087
|
const bundle: Bundle = {
|
|
1088
|
+
archiveByteSize: 1_024,
|
|
1511
1089
|
id: "00000000-0000-0000-0000-000000000001",
|
|
1512
1090
|
platform: "ios",
|
|
1513
|
-
shouldForceUpdate: false,
|
|
1514
|
-
enabled: true,
|
|
1515
1091
|
fileHash: "hash123",
|
|
1516
1092
|
gitCommitHash: null,
|
|
1517
|
-
message: "Test bundle",
|
|
1518
|
-
channel: "production",
|
|
1519
1093
|
storageUri: "s3://test-bucket/bundles/bundle.zip",
|
|
1520
|
-
targetAppVersion: "1.0.0",
|
|
1521
|
-
fingerprintHash: null,
|
|
1522
1094
|
};
|
|
1523
1095
|
|
|
1524
1096
|
await hotUpdater.insertBundle(bundle);
|
|
1525
1097
|
|
|
1526
|
-
const updateInfo = await hotUpdater.
|
|
1527
|
-
appVersion: "1.0.0",
|
|
1528
|
-
bundleId: NIL_UUID,
|
|
1529
|
-
platform: "ios",
|
|
1530
|
-
_updateStrategy: "appVersion",
|
|
1531
|
-
});
|
|
1098
|
+
const updateInfo = await hotUpdater.getArtifactInfo(bundle.id, NIL_UUID);
|
|
1532
1099
|
|
|
1533
1100
|
expect(updateInfo).not.toBeNull();
|
|
1534
1101
|
expect(updateInfo?.fileUrl).toBe(
|
|
@@ -1536,173 +1103,59 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
1536
1103
|
);
|
|
1537
1104
|
});
|
|
1538
1105
|
|
|
1539
|
-
it("
|
|
1540
|
-
const bundle: Bundle = {
|
|
1541
|
-
id: "00000000-0000-0000-0000-000000000004",
|
|
1542
|
-
platform: "ios",
|
|
1543
|
-
shouldForceUpdate: false,
|
|
1544
|
-
enabled: true,
|
|
1545
|
-
fileHash: "hashhttp",
|
|
1546
|
-
gitCommitHash: null,
|
|
1547
|
-
message: "HTTP bundle",
|
|
1548
|
-
channel: "production",
|
|
1549
|
-
storageUri: "s3://bundle/bundle.zip",
|
|
1550
|
-
targetAppVersion: "1.0.0",
|
|
1551
|
-
fingerprintHash: null,
|
|
1552
|
-
};
|
|
1553
|
-
|
|
1554
|
-
await hotUpdater.insertBundle(bundle);
|
|
1555
|
-
|
|
1556
|
-
const updateInfo = await hotUpdater.getAppUpdateInfo({
|
|
1557
|
-
appVersion: "1.0.0",
|
|
1558
|
-
bundleId: NIL_UUID,
|
|
1559
|
-
platform: "ios",
|
|
1560
|
-
_updateStrategy: "appVersion",
|
|
1561
|
-
});
|
|
1562
|
-
|
|
1563
|
-
expect(updateInfo).not.toBeNull();
|
|
1564
|
-
expect(updateInfo?.fileUrl).toBe(
|
|
1565
|
-
"https://s3.example.com/bundle/bundle.zip",
|
|
1566
|
-
);
|
|
1567
|
-
});
|
|
1568
|
-
|
|
1569
|
-
it("passes through https:// URLs without plugin resolution", async () => {
|
|
1570
|
-
const bundle: Bundle = {
|
|
1571
|
-
id: "00000000-0000-0000-0000-000000000005",
|
|
1572
|
-
platform: "ios",
|
|
1573
|
-
shouldForceUpdate: false,
|
|
1574
|
-
enabled: true,
|
|
1575
|
-
fileHash: "hashhttps",
|
|
1576
|
-
gitCommitHash: null,
|
|
1577
|
-
message: "HTTPS bundle",
|
|
1578
|
-
channel: "production",
|
|
1579
|
-
storageUri: "https://cdn.example.com/bundle.zip",
|
|
1580
|
-
targetAppVersion: "1.0.0",
|
|
1581
|
-
fingerprintHash: null,
|
|
1582
|
-
};
|
|
1583
|
-
|
|
1584
|
-
await hotUpdater.insertBundle(bundle);
|
|
1585
|
-
|
|
1586
|
-
const updateInfo = await hotUpdater.getAppUpdateInfo({
|
|
1587
|
-
appVersion: "1.0.0",
|
|
1588
|
-
bundleId: NIL_UUID,
|
|
1589
|
-
platform: "ios",
|
|
1590
|
-
_updateStrategy: "appVersion",
|
|
1591
|
-
});
|
|
1592
|
-
|
|
1593
|
-
expect(updateInfo).not.toBeNull();
|
|
1594
|
-
expect(updateInfo?.fileUrl).toBe("https://cdn.example.com/bundle.zip");
|
|
1595
|
-
});
|
|
1596
|
-
|
|
1597
|
-
it("returns null when no update is available", async () => {
|
|
1598
|
-
const updateInfo = await hotUpdater.getAppUpdateInfo({
|
|
1599
|
-
appVersion: "99.0.0",
|
|
1600
|
-
bundleId: NIL_UUID,
|
|
1601
|
-
platform: "ios",
|
|
1602
|
-
_updateStrategy: "appVersion",
|
|
1603
|
-
});
|
|
1604
|
-
|
|
1605
|
-
expect(updateInfo).toBeNull();
|
|
1606
|
-
});
|
|
1607
|
-
|
|
1608
|
-
it("works with fingerprint strategy", async () => {
|
|
1609
|
-
const bundle: Bundle = {
|
|
1610
|
-
id: "00000000-0000-0000-0000-000000000008",
|
|
1611
|
-
platform: "ios",
|
|
1612
|
-
shouldForceUpdate: false,
|
|
1613
|
-
enabled: true,
|
|
1614
|
-
fileHash: "hashfp",
|
|
1615
|
-
gitCommitHash: null,
|
|
1616
|
-
message: "Fingerprint bundle",
|
|
1617
|
-
channel: "production",
|
|
1618
|
-
storageUri: "s3://test-bucket/fp-bundle.zip",
|
|
1619
|
-
targetAppVersion: null,
|
|
1620
|
-
fingerprintHash: "fingerprint123",
|
|
1621
|
-
};
|
|
1622
|
-
|
|
1623
|
-
await hotUpdater.insertBundle(bundle);
|
|
1624
|
-
|
|
1625
|
-
const updateInfo = await hotUpdater.getAppUpdateInfo({
|
|
1626
|
-
fingerprintHash: "fingerprint123",
|
|
1627
|
-
bundleId: NIL_UUID,
|
|
1628
|
-
platform: "ios",
|
|
1629
|
-
_updateStrategy: "fingerprint",
|
|
1630
|
-
});
|
|
1631
|
-
|
|
1632
|
-
expect(updateInfo).not.toBeNull();
|
|
1633
|
-
expect(updateInfo?.fileUrl).toBe(
|
|
1634
|
-
"https://s3.example.com/test-bucket/fp-bundle.zip",
|
|
1635
|
-
);
|
|
1636
|
-
});
|
|
1637
|
-
|
|
1638
|
-
it("returns manifest metadata and hbc patch descriptors for createHotUpdater", async () => {
|
|
1106
|
+
it("returns manifest metadata and hbc patch descriptors", async () => {
|
|
1639
1107
|
const currentManifestStorageUri =
|
|
1640
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000101/manifest.json";
|
|
1108
|
+
"s3://test-bucket/releases/bundles/00000000-0000-0000-0000-000000000101/manifest.json";
|
|
1641
1109
|
const nextManifestStorageUri =
|
|
1642
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000102/manifest.json";
|
|
1110
|
+
"s3://test-bucket/releases/bundles/00000000-0000-0000-0000-000000000102/manifest.json";
|
|
1643
1111
|
const olderBundle: Bundle = {
|
|
1112
|
+
archiveByteSize: 1_024,
|
|
1644
1113
|
id: "00000000-0000-0000-0000-000000000100",
|
|
1645
1114
|
platform: "ios",
|
|
1646
|
-
shouldForceUpdate: false,
|
|
1647
|
-
enabled: true,
|
|
1648
1115
|
fileHash: "hash-older-zip",
|
|
1649
1116
|
gitCommitHash: null,
|
|
1650
|
-
message: "Older bundle",
|
|
1651
|
-
channel: "production",
|
|
1652
1117
|
storageUri:
|
|
1653
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000100/bundle.zip",
|
|
1654
|
-
targetAppVersion: "1.0.0",
|
|
1655
|
-
fingerprintHash: null,
|
|
1118
|
+
"s3://test-bucket/releases/bundles/00000000-0000-0000-0000-000000000100/bundle.zip",
|
|
1656
1119
|
};
|
|
1657
1120
|
const currentBundle: Bundle = {
|
|
1121
|
+
archiveByteSize: 1_024,
|
|
1658
1122
|
id: "00000000-0000-0000-0000-000000000101",
|
|
1659
1123
|
platform: "ios",
|
|
1660
|
-
shouldForceUpdate: false,
|
|
1661
|
-
enabled: true,
|
|
1662
1124
|
fileHash: "hash-current-zip",
|
|
1663
1125
|
gitCommitHash: null,
|
|
1664
|
-
message: "Current bundle",
|
|
1665
|
-
channel: "production",
|
|
1666
1126
|
storageUri:
|
|
1667
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000101/bundle.zip",
|
|
1668
|
-
|
|
1669
|
-
fingerprintHash: null,
|
|
1670
|
-
assetBaseStorageUri:
|
|
1671
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000101/files",
|
|
1127
|
+
"s3://test-bucket/releases/bundles/00000000-0000-0000-0000-000000000101/bundle.zip",
|
|
1128
|
+
assetBaseStorageUri: "s3://test-bucket/releases/assets",
|
|
1672
1129
|
manifestFileHash: "sig:manifest-current",
|
|
1673
1130
|
manifestStorageUri: currentManifestStorageUri,
|
|
1674
1131
|
};
|
|
1675
1132
|
const nextBundle: Bundle = {
|
|
1133
|
+
archiveByteSize: 1_024,
|
|
1676
1134
|
id: "00000000-0000-0000-0000-000000000102",
|
|
1677
1135
|
platform: "ios",
|
|
1678
|
-
shouldForceUpdate: false,
|
|
1679
|
-
enabled: true,
|
|
1680
1136
|
fileHash: "hash-next-zip",
|
|
1681
1137
|
gitCommitHash: null,
|
|
1682
|
-
message: "Next bundle",
|
|
1683
|
-
channel: "production",
|
|
1684
1138
|
storageUri:
|
|
1685
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000102/bundle.zip",
|
|
1686
|
-
|
|
1687
|
-
fingerprintHash: null,
|
|
1688
|
-
assetBaseStorageUri:
|
|
1689
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000102/files",
|
|
1139
|
+
"s3://test-bucket/releases/bundles/00000000-0000-0000-0000-000000000102/bundle.zip",
|
|
1140
|
+
assetBaseStorageUri: "s3://test-bucket/releases/assets",
|
|
1690
1141
|
manifestFileHash: "sig:manifest-next",
|
|
1691
1142
|
manifestStorageUri: nextManifestStorageUri,
|
|
1692
1143
|
patches: [
|
|
1693
1144
|
{
|
|
1694
1145
|
baseBundleId: "00000000-0000-0000-0000-000000000100",
|
|
1695
1146
|
baseFileHash: "hash-older-bundle",
|
|
1147
|
+
byteSize: 48,
|
|
1696
1148
|
patchFileHash: "hash-older-bsdiff",
|
|
1697
1149
|
patchStorageUri:
|
|
1698
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000102/patches/00000000-0000-0000-0000-000000000100/index.ios.bundle.bsdiff",
|
|
1150
|
+
"s3://test-bucket/releases/bundles/00000000-0000-0000-0000-000000000102/patches/00000000-0000-0000-0000-000000000100/index.ios.bundle.bsdiff",
|
|
1699
1151
|
},
|
|
1700
1152
|
{
|
|
1701
1153
|
baseBundleId: currentBundle.id,
|
|
1702
1154
|
baseFileHash: "hash-old-bundle",
|
|
1155
|
+
byteSize: 48,
|
|
1703
1156
|
patchFileHash: "hash-bsdiff",
|
|
1704
1157
|
patchStorageUri:
|
|
1705
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000102/patches/00000000-0000-0000-0000-000000000101/index.ios.bundle.bsdiff",
|
|
1158
|
+
"s3://test-bucket/releases/bundles/00000000-0000-0000-0000-000000000102/patches/00000000-0000-0000-0000-000000000101/index.ios.bundle.bsdiff",
|
|
1706
1159
|
},
|
|
1707
1160
|
],
|
|
1708
1161
|
};
|
|
@@ -1747,19 +1200,13 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
1747
1200
|
|
|
1748
1201
|
try {
|
|
1749
1202
|
await expect(
|
|
1750
|
-
hotUpdater.
|
|
1751
|
-
appVersion: "1.0.0",
|
|
1752
|
-
bundleId: currentBundle.id,
|
|
1753
|
-
channel: "production",
|
|
1754
|
-
platform: "ios",
|
|
1755
|
-
_updateStrategy: "appVersion",
|
|
1756
|
-
}),
|
|
1203
|
+
hotUpdater.getArtifactInfo(nextBundle.id, currentBundle.id),
|
|
1757
1204
|
).resolves.toEqual({
|
|
1758
1205
|
changedAssets: {
|
|
1759
1206
|
"index.ios.bundle": {
|
|
1760
1207
|
file: {
|
|
1761
1208
|
compression: "br",
|
|
1762
|
-
url: "https://s3.example.com/test-bucket/releases/
|
|
1209
|
+
url: "https://s3.example.com/test-bucket/releases/assets/sha256/ha/hash-new-bundle.br",
|
|
1763
1210
|
},
|
|
1764
1211
|
fileHash: "hash-new-bundle",
|
|
1765
1212
|
patch: {
|
|
@@ -1768,20 +1215,16 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
1768
1215
|
baseFileHash: "hash-old-bundle",
|
|
1769
1216
|
patchFileHash: "hash-bsdiff",
|
|
1770
1217
|
patchUrl:
|
|
1771
|
-
"https://s3.example.com/test-bucket/releases/00000000-0000-0000-0000-000000000102/patches/00000000-0000-0000-0000-000000000101/index.ios.bundle.bsdiff",
|
|
1218
|
+
"https://s3.example.com/test-bucket/releases/bundles/00000000-0000-0000-0000-000000000102/patches/00000000-0000-0000-0000-000000000101/index.ios.bundle.bsdiff",
|
|
1772
1219
|
},
|
|
1773
1220
|
},
|
|
1774
1221
|
},
|
|
1775
1222
|
fileHash: "hash-next-zip",
|
|
1776
1223
|
fileUrl:
|
|
1777
|
-
"https://s3.example.com/test-bucket/releases/00000000-0000-0000-0000-000000000102/bundle.zip",
|
|
1778
|
-
id: nextBundle.id,
|
|
1224
|
+
"https://s3.example.com/test-bucket/releases/bundles/00000000-0000-0000-0000-000000000102/bundle.zip",
|
|
1779
1225
|
manifestFileHash: "sig:manifest-next",
|
|
1780
1226
|
manifestUrl:
|
|
1781
|
-
"https://s3.example.com/test-bucket/releases/00000000-0000-0000-0000-000000000102/manifest.json",
|
|
1782
|
-
message: "Next bundle",
|
|
1783
|
-
shouldForceUpdate: false,
|
|
1784
|
-
status: "UPDATE",
|
|
1227
|
+
"https://s3.example.com/test-bucket/releases/bundles/00000000-0000-0000-0000-000000000102/manifest.json",
|
|
1785
1228
|
});
|
|
1786
1229
|
expect(fetchMock).not.toHaveBeenCalled();
|
|
1787
1230
|
} finally {
|
|
@@ -1789,24 +1232,18 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
1789
1232
|
}
|
|
1790
1233
|
});
|
|
1791
1234
|
|
|
1792
|
-
it("propagates manifest storage read failures
|
|
1235
|
+
it("propagates manifest storage read failures", async () => {
|
|
1793
1236
|
const nextManifestStorageUri =
|
|
1794
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000109/manifest.json";
|
|
1237
|
+
"s3://test-bucket/releases/bundles/00000000-0000-0000-0000-000000000109/manifest.json";
|
|
1795
1238
|
const nextBundle: Bundle = {
|
|
1239
|
+
archiveByteSize: 1_024,
|
|
1796
1240
|
id: "00000000-0000-0000-0000-000000000109",
|
|
1797
1241
|
platform: "ios",
|
|
1798
|
-
shouldForceUpdate: false,
|
|
1799
|
-
enabled: true,
|
|
1800
1242
|
fileHash: "hash-next-zip",
|
|
1801
1243
|
gitCommitHash: null,
|
|
1802
|
-
message: "Next bundle",
|
|
1803
|
-
channel: "production",
|
|
1804
1244
|
storageUri:
|
|
1805
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000109/bundle.zip",
|
|
1806
|
-
|
|
1807
|
-
fingerprintHash: null,
|
|
1808
|
-
assetBaseStorageUri:
|
|
1809
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000109/files",
|
|
1245
|
+
"s3://test-bucket/releases/bundles/00000000-0000-0000-0000-000000000109/bundle.zip",
|
|
1246
|
+
assetBaseStorageUri: "s3://test-bucket/releases/assets",
|
|
1810
1247
|
manifestFileHash: "sig:manifest-next",
|
|
1811
1248
|
manifestStorageUri: nextManifestStorageUri,
|
|
1812
1249
|
};
|
|
@@ -1818,137 +1255,8 @@ describe("server/db hotUpdater getUpdateInfo (PGlite + Kysely)", async () => {
|
|
|
1818
1255
|
);
|
|
1819
1256
|
|
|
1820
1257
|
await expect(
|
|
1821
|
-
hotUpdater.
|
|
1822
|
-
appVersion: "1.0.0",
|
|
1823
|
-
bundleId: NIL_UUID,
|
|
1824
|
-
channel: "production",
|
|
1825
|
-
platform: "ios",
|
|
1826
|
-
_updateStrategy: "appVersion",
|
|
1827
|
-
}),
|
|
1258
|
+
hotUpdater.getArtifactInfo(nextBundle.id, NIL_UUID),
|
|
1828
1259
|
).rejects.toThrow("storage read failed");
|
|
1829
1260
|
});
|
|
1830
1261
|
});
|
|
1831
|
-
|
|
1832
|
-
describe("database plugin factories", () => {
|
|
1833
|
-
it("keeps optional maintenance capabilities lazy", () => {
|
|
1834
|
-
const factory = vi.fn(() => ({
|
|
1835
|
-
async getBundleById() {
|
|
1836
|
-
return null;
|
|
1837
|
-
},
|
|
1838
|
-
async getBundles() {
|
|
1839
|
-
return {
|
|
1840
|
-
data: [],
|
|
1841
|
-
pagination: {
|
|
1842
|
-
hasNextPage: false,
|
|
1843
|
-
hasPreviousPage: false,
|
|
1844
|
-
currentPage: 1,
|
|
1845
|
-
totalPages: 1,
|
|
1846
|
-
total: 0,
|
|
1847
|
-
},
|
|
1848
|
-
};
|
|
1849
|
-
},
|
|
1850
|
-
async getChannels() {
|
|
1851
|
-
return [];
|
|
1852
|
-
},
|
|
1853
|
-
async commitBundle() {},
|
|
1854
|
-
}));
|
|
1855
|
-
createHotUpdater({
|
|
1856
|
-
database: createDatabasePlugin({
|
|
1857
|
-
name: "lazyPlugin",
|
|
1858
|
-
factory,
|
|
1859
|
-
})({}),
|
|
1860
|
-
});
|
|
1861
|
-
|
|
1862
|
-
expect(factory).not.toHaveBeenCalled();
|
|
1863
|
-
});
|
|
1864
|
-
|
|
1865
|
-
it("isolates pending mutation state between overlapping writes", async () => {
|
|
1866
|
-
const committedBundleIds: string[][] = [];
|
|
1867
|
-
const onUnmount = vi.fn(async () => undefined);
|
|
1868
|
-
let releaseFirstCommit!: () => void;
|
|
1869
|
-
let notifyFirstCommitStarted!: () => void;
|
|
1870
|
-
const firstCommitStarted = new Promise<void>((resolve) => {
|
|
1871
|
-
notifyFirstCommitStarted = resolve;
|
|
1872
|
-
});
|
|
1873
|
-
const firstCommitGate = new Promise<void>((resolve) => {
|
|
1874
|
-
releaseFirstCommit = resolve;
|
|
1875
|
-
});
|
|
1876
|
-
let commitCount = 0;
|
|
1877
|
-
|
|
1878
|
-
const isolatedHotUpdater = createHotUpdater({
|
|
1879
|
-
database: createDatabasePlugin({
|
|
1880
|
-
name: "isolatedPlugin",
|
|
1881
|
-
factory: () => ({
|
|
1882
|
-
async getBundleById() {
|
|
1883
|
-
return null;
|
|
1884
|
-
},
|
|
1885
|
-
async getBundles() {
|
|
1886
|
-
return {
|
|
1887
|
-
data: [],
|
|
1888
|
-
pagination: {
|
|
1889
|
-
hasNextPage: false,
|
|
1890
|
-
hasPreviousPage: false,
|
|
1891
|
-
currentPage: 1,
|
|
1892
|
-
totalPages: 1,
|
|
1893
|
-
total: 0,
|
|
1894
|
-
},
|
|
1895
|
-
};
|
|
1896
|
-
},
|
|
1897
|
-
async getChannels() {
|
|
1898
|
-
return [];
|
|
1899
|
-
},
|
|
1900
|
-
onUnmount,
|
|
1901
|
-
async commitBundle({ changedSets }) {
|
|
1902
|
-
commitCount += 1;
|
|
1903
|
-
committedBundleIds.push(
|
|
1904
|
-
changedSets.map((change) => change.data.id),
|
|
1905
|
-
);
|
|
1906
|
-
|
|
1907
|
-
if (commitCount === 1) {
|
|
1908
|
-
notifyFirstCommitStarted();
|
|
1909
|
-
await firstCommitGate;
|
|
1910
|
-
}
|
|
1911
|
-
},
|
|
1912
|
-
}),
|
|
1913
|
-
})({}),
|
|
1914
|
-
});
|
|
1915
|
-
|
|
1916
|
-
const firstBundleId = "00000000-0000-0000-0000-000000000030";
|
|
1917
|
-
const secondBundleId = "00000000-0000-0000-0000-000000000031";
|
|
1918
|
-
|
|
1919
|
-
const firstInsert = isolatedHotUpdater.insertBundle({
|
|
1920
|
-
id: firstBundleId,
|
|
1921
|
-
platform: "ios",
|
|
1922
|
-
shouldForceUpdate: false,
|
|
1923
|
-
enabled: true,
|
|
1924
|
-
fileHash: "hash-1",
|
|
1925
|
-
gitCommitHash: null,
|
|
1926
|
-
message: "first bundle",
|
|
1927
|
-
channel: "production",
|
|
1928
|
-
storageUri: "s3://test-bucket/first.zip",
|
|
1929
|
-
targetAppVersion: "1.0.0",
|
|
1930
|
-
fingerprintHash: null,
|
|
1931
|
-
});
|
|
1932
|
-
await firstCommitStarted;
|
|
1933
|
-
|
|
1934
|
-
const secondInsert = isolatedHotUpdater.insertBundle({
|
|
1935
|
-
id: secondBundleId,
|
|
1936
|
-
platform: "ios",
|
|
1937
|
-
shouldForceUpdate: false,
|
|
1938
|
-
enabled: true,
|
|
1939
|
-
fileHash: "hash-2",
|
|
1940
|
-
gitCommitHash: null,
|
|
1941
|
-
message: "second bundle",
|
|
1942
|
-
channel: "production",
|
|
1943
|
-
storageUri: "s3://test-bucket/second.zip",
|
|
1944
|
-
targetAppVersion: "1.0.0",
|
|
1945
|
-
fingerprintHash: null,
|
|
1946
|
-
});
|
|
1947
|
-
|
|
1948
|
-
releaseFirstCommit();
|
|
1949
|
-
await Promise.all([firstInsert, secondInsert]);
|
|
1950
|
-
|
|
1951
|
-
expect(committedBundleIds).toEqual([[firstBundleId], [secondBundleId]]);
|
|
1952
|
-
});
|
|
1953
|
-
});
|
|
1954
1262
|
});
|