@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/runtime.spec.ts
CHANGED
|
@@ -1,1027 +1,194 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
DatabasePlugin,
|
|
7
|
-
RequestEnvContext,
|
|
8
|
-
RuntimeStoragePlugin,
|
|
9
|
-
RuntimeStorageProfile,
|
|
1
|
+
import {
|
|
2
|
+
createStoragePlugin,
|
|
3
|
+
type DatabasePlugin,
|
|
4
|
+
type ConfigInput,
|
|
10
5
|
} from "@hot-updater/plugin-core";
|
|
11
|
-
import { createDatabasePlugin } from "@hot-updater/plugin-core";
|
|
12
6
|
import { describe, expect, expectTypeOf, it, vi } from "vitest";
|
|
13
7
|
|
|
14
|
-
import
|
|
8
|
+
import { createInMemoryDatabasePlugin } from "../../test-utils/test/inMemoryDatabasePlugin";
|
|
9
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
15
10
|
import { createHotUpdater } from "./index";
|
|
16
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
ClientAccessPolicy,
|
|
13
|
+
CreateHotUpdaterOptions,
|
|
14
|
+
HandlerAPI,
|
|
15
|
+
} from "./index";
|
|
16
|
+
import {
|
|
17
|
+
createRuntimeDatabase,
|
|
18
|
+
createSchemaManagedDatabase,
|
|
19
|
+
} from "./runtime.testFixtures";
|
|
20
|
+
import { HOT_UPDATER_SCHEMA_VERSION } from "./schema/types";
|
|
17
21
|
import { HOT_UPDATER_SERVER_VERSION } from "./version";
|
|
18
22
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
shouldForceUpdate: false,
|
|
23
|
-
enabled: true,
|
|
24
|
-
fileHash: "hash123",
|
|
25
|
-
gitCommitHash: null,
|
|
26
|
-
message: "Test bundle",
|
|
27
|
-
channel: "production",
|
|
28
|
-
storageUri: "s3://test-bucket/bundles/bundle.zip",
|
|
29
|
-
targetAppVersion: "1.0.0",
|
|
30
|
-
fingerprintHash: null,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
type TestEnv = {
|
|
34
|
-
assetHost: string;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
type TestContext = RequestEnvContext<TestEnv>;
|
|
38
|
-
|
|
39
|
-
const createRuntimeStorage = (
|
|
40
|
-
getDownloadUrl: RuntimeStorageProfile<TestContext>["getDownloadUrl"],
|
|
41
|
-
readText: RuntimeStorageProfile<TestContext>["readText"] = async () => null,
|
|
42
|
-
): RuntimeStoragePlugin<TestContext> => ({
|
|
43
|
-
name: "testStorage",
|
|
44
|
-
supportedProtocol: "s3",
|
|
45
|
-
profiles: {
|
|
46
|
-
runtime: {
|
|
47
|
-
getDownloadUrl,
|
|
48
|
-
readText,
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const createSchemaManagedDatabase = (
|
|
54
|
-
adapterName: string,
|
|
55
|
-
version: string | undefined,
|
|
56
|
-
): DatabasePlugin<TestContext> & DatabaseAdapterCapabilities => ({
|
|
57
|
-
name: `${adapterName}Database`,
|
|
58
|
-
adapterName,
|
|
59
|
-
createMigrator: () =>
|
|
60
|
-
({
|
|
61
|
-
async getVersion() {
|
|
62
|
-
return version;
|
|
63
|
-
},
|
|
64
|
-
async getNameVariants() {
|
|
65
|
-
return {};
|
|
66
|
-
},
|
|
67
|
-
async next() {
|
|
68
|
-
return undefined;
|
|
69
|
-
},
|
|
70
|
-
async previous() {
|
|
71
|
-
return undefined;
|
|
72
|
-
},
|
|
73
|
-
async up() {
|
|
74
|
-
throw new Error("not implemented");
|
|
75
|
-
},
|
|
76
|
-
async down() {
|
|
77
|
-
throw new Error("not implemented");
|
|
78
|
-
},
|
|
79
|
-
async migrateTo() {
|
|
80
|
-
throw new Error("not implemented");
|
|
81
|
-
},
|
|
82
|
-
async migrateToLatest() {
|
|
83
|
-
throw new Error("not implemented");
|
|
84
|
-
},
|
|
85
|
-
async migrate() {},
|
|
86
|
-
}) as Migrator,
|
|
87
|
-
async appendBundle() {},
|
|
88
|
-
async commitBundle() {},
|
|
89
|
-
async deleteBundle() {},
|
|
90
|
-
async getBundleById() {
|
|
91
|
-
throw new Error("runtime database should not be queried");
|
|
92
|
-
},
|
|
93
|
-
async getBundles() {
|
|
94
|
-
throw new Error("runtime database should not be queried");
|
|
95
|
-
},
|
|
96
|
-
async getChannels() {
|
|
97
|
-
throw new Error("runtime database should not be queried");
|
|
98
|
-
},
|
|
99
|
-
async updateBundle() {},
|
|
100
|
-
});
|
|
23
|
+
const publicClientAccess = {
|
|
24
|
+
type: "public",
|
|
25
|
+
} satisfies ClientAccessPolicy;
|
|
101
26
|
|
|
102
27
|
describe("runtime createHotUpdater", () => {
|
|
103
|
-
it("
|
|
104
|
-
const packageJson = JSON.parse(
|
|
105
|
-
await readFile(new URL("../package.json", import.meta.url), "utf-8"),
|
|
106
|
-
) as {
|
|
107
|
-
exports: Record<string, unknown>;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
expect(packageJson.exports["./db"]).toBeDefined();
|
|
111
|
-
expect(packageJson.exports["./runtime"]).toBeUndefined();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("exports runtime-safe handler types from the root entry", () => {
|
|
115
|
-
expectTypeOf<HandlerAPI>().toHaveProperty("getBundles");
|
|
116
|
-
expectTypeOf<HandlerOptions>().toHaveProperty("routes");
|
|
117
|
-
expectTypeOf<HandlerRoutes>().toEqualTypeOf<{
|
|
118
|
-
updateCheck: boolean;
|
|
119
|
-
bundles: boolean;
|
|
120
|
-
}>();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("exports the root runtime API without database capabilities", () => {
|
|
124
|
-
const database: DatabasePlugin<TestContext> = {
|
|
125
|
-
name: "testDatabase",
|
|
126
|
-
async appendBundle() {},
|
|
127
|
-
async commitBundle() {},
|
|
128
|
-
async deleteBundle() {},
|
|
129
|
-
async getBundleById() {
|
|
130
|
-
return null;
|
|
131
|
-
},
|
|
132
|
-
async getBundles() {
|
|
133
|
-
return {
|
|
134
|
-
data: [],
|
|
135
|
-
pagination: {
|
|
136
|
-
currentPage: 1,
|
|
137
|
-
hasNextPage: false,
|
|
138
|
-
hasPreviousPage: false,
|
|
139
|
-
total: 0,
|
|
140
|
-
totalPages: 0,
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
},
|
|
144
|
-
async getChannels() {
|
|
145
|
-
return [];
|
|
146
|
-
},
|
|
147
|
-
async updateBundle() {},
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
const hotUpdater = createHotUpdater({ database });
|
|
151
|
-
|
|
152
|
-
expect(hotUpdater.basePath).toBe("/api");
|
|
153
|
-
expect(hotUpdater.adapterName).toBe("testDatabase");
|
|
154
|
-
expect(hotUpdater.handler).toEqual(expect.any(Function));
|
|
155
|
-
expect("createMigrator" in hotUpdater).toBe(false);
|
|
156
|
-
expect("generateSchema" in hotUpdater).toBe(false);
|
|
157
|
-
expectTypeOf(hotUpdater).not.toHaveProperty("createMigrator");
|
|
158
|
-
expectTypeOf(hotUpdater).not.toHaveProperty("generateSchema");
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("requires storages to implement the runtime profile", () => {
|
|
162
|
-
const database: DatabasePlugin<TestContext> = {
|
|
163
|
-
name: "testDatabase",
|
|
164
|
-
async appendBundle() {},
|
|
165
|
-
async commitBundle() {},
|
|
166
|
-
async deleteBundle() {},
|
|
167
|
-
async getBundleById() {
|
|
168
|
-
return null;
|
|
169
|
-
},
|
|
170
|
-
async getBundles() {
|
|
171
|
-
return {
|
|
172
|
-
data: [],
|
|
173
|
-
pagination: {
|
|
174
|
-
currentPage: 1,
|
|
175
|
-
hasNextPage: false,
|
|
176
|
-
hasPreviousPage: false,
|
|
177
|
-
total: 0,
|
|
178
|
-
totalPages: 0,
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
},
|
|
182
|
-
async getChannels() {
|
|
183
|
-
return [];
|
|
184
|
-
},
|
|
185
|
-
async updateBundle() {},
|
|
186
|
-
};
|
|
187
|
-
const nodeOnlyStorage = {
|
|
188
|
-
name: "nodeOnlyStorage",
|
|
189
|
-
supportedProtocol: "s3",
|
|
190
|
-
profiles: {
|
|
191
|
-
node: {
|
|
192
|
-
delete: vi.fn(),
|
|
193
|
-
downloadFile: vi.fn(),
|
|
194
|
-
exists: vi.fn(async () => false),
|
|
195
|
-
upload: vi.fn(),
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
|
|
28
|
+
it.each(["authorityId", "catalogId"])("rejects a user-supplied %s", (key) => {
|
|
200
29
|
expect(() =>
|
|
201
30
|
createHotUpdater({
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
],
|
|
31
|
+
clientAccess: publicClientAccess,
|
|
32
|
+
database: createInMemoryDatabasePlugin(),
|
|
33
|
+
[key]: "user-controlled",
|
|
206
34
|
}),
|
|
207
|
-
).toThrow(
|
|
208
|
-
'nodeOnlyStorage does not implement the runtime storage profile for protocol "s3".',
|
|
209
|
-
);
|
|
35
|
+
).toThrow(`Remove ${key}`);
|
|
210
36
|
});
|
|
211
37
|
|
|
212
|
-
it("
|
|
213
|
-
const
|
|
214
|
-
database: createSchemaManagedDatabase("kysely", undefined),
|
|
215
|
-
});
|
|
38
|
+
it("publishes only the supported runtime and database subpaths", () => {
|
|
39
|
+
const packageExports = packageJson.exports;
|
|
216
40
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
);
|
|
220
|
-
});
|
|
41
|
+
const databaseEntry = packageExports["./db"];
|
|
42
|
+
const hasRuntimeEntry = Object.hasOwn(packageExports, "./runtime");
|
|
221
43
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
database: createSchemaManagedDatabase("mongodb", "0.21.0"),
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
await expect(hotUpdater.getChannels()).rejects.toThrow(
|
|
228
|
-
"Hot Updater database schema version 0.21.0 is not supported by mongodb.",
|
|
229
|
-
);
|
|
44
|
+
expect(databaseEntry).toBeDefined();
|
|
45
|
+
expect(hasRuntimeEntry).toBe(false);
|
|
230
46
|
});
|
|
231
47
|
|
|
232
|
-
it("
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
RuntimeStorageProfile<TestContext>["getDownloadUrl"]
|
|
250
|
-
>(async (_storageUri, context) => {
|
|
251
|
-
return {
|
|
252
|
-
fileUrl: new URL("/bundle.zip", context?.env?.assetHost).toString(),
|
|
253
|
-
};
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
const database: DatabasePlugin<TestContext> = {
|
|
257
|
-
name: "testDatabase",
|
|
258
|
-
async appendBundle() {},
|
|
259
|
-
async commitBundle() {},
|
|
260
|
-
async deleteBundle() {},
|
|
261
|
-
async getBundleById(id) {
|
|
262
|
-
return id === bundle.id ? bundle : null;
|
|
263
|
-
},
|
|
264
|
-
getBundles,
|
|
265
|
-
getUpdateInfo,
|
|
266
|
-
async getChannels() {
|
|
267
|
-
return ["production"];
|
|
268
|
-
},
|
|
269
|
-
async onUnmount() {},
|
|
270
|
-
async updateBundle() {},
|
|
48
|
+
it("exports runtime-safe handler types from the root entry", () => {
|
|
49
|
+
expectTypeOf<ConfigInput>().not.toHaveProperty("authorityId");
|
|
50
|
+
expectTypeOf<ConfigInput>().not.toHaveProperty("catalogId");
|
|
51
|
+
expectTypeOf<HandlerAPI>().toHaveProperty("getBundles");
|
|
52
|
+
expectTypeOf<keyof CreateHotUpdaterOptions>().toEqualTypeOf<
|
|
53
|
+
"clientAccess" | "database" | "storage"
|
|
54
|
+
>();
|
|
55
|
+
expectTypeOf<CreateHotUpdaterOptions>().toHaveProperty("clientAccess");
|
|
56
|
+
expectTypeOf<
|
|
57
|
+
CreateHotUpdaterOptions["clientAccess"]
|
|
58
|
+
>().toEqualTypeOf<ClientAccessPolicy>();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("accepts a direct v2 plugin object without exposing maintenance methods", () => {
|
|
62
|
+
const database: DatabasePlugin = {
|
|
63
|
+
...createInMemoryDatabasePlugin(),
|
|
64
|
+
name: "contextlessTestDatabase",
|
|
271
65
|
};
|
|
272
|
-
const storage =
|
|
66
|
+
const storage = createStoragePlugin({
|
|
67
|
+
name: "contextlessTestStorage",
|
|
68
|
+
protocol: "s3",
|
|
69
|
+
get: async () => ({ response: null }),
|
|
70
|
+
getDownloadUrl: async () => ({
|
|
71
|
+
url: "https://assets.example.com/bundle.zip",
|
|
72
|
+
}),
|
|
73
|
+
});
|
|
273
74
|
|
|
274
75
|
const hotUpdater = createHotUpdater({
|
|
76
|
+
clientAccess: publicClientAccess,
|
|
275
77
|
database,
|
|
276
|
-
|
|
277
|
-
basePath: "/api/check-update",
|
|
278
|
-
routes: {
|
|
279
|
-
updateCheck: true,
|
|
280
|
-
bundles: false,
|
|
281
|
-
},
|
|
78
|
+
storage: [storage],
|
|
282
79
|
});
|
|
283
80
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
message: "Test bundle",
|
|
297
|
-
shouldForceUpdate: false,
|
|
298
|
-
status: "UPDATE",
|
|
299
|
-
});
|
|
300
|
-
expect(getUpdateInfo).toHaveBeenCalledWith(
|
|
301
|
-
{
|
|
302
|
-
_updateStrategy: "appVersion",
|
|
303
|
-
appVersion: "1.0.0",
|
|
304
|
-
bundleId: NIL_UUID,
|
|
305
|
-
channel: "production",
|
|
306
|
-
cohort: undefined,
|
|
307
|
-
minBundleId: NIL_UUID,
|
|
308
|
-
platform: "ios",
|
|
309
|
-
},
|
|
310
|
-
expect.objectContaining({
|
|
311
|
-
env: {
|
|
312
|
-
assetHost: "https://assets.example.com",
|
|
313
|
-
},
|
|
314
|
-
request: expect.any(Request),
|
|
315
|
-
}),
|
|
316
|
-
);
|
|
317
|
-
expect(getBundles).not.toHaveBeenCalled();
|
|
318
|
-
expect(getDownloadUrl).toHaveBeenCalledWith(
|
|
319
|
-
"s3://test-bucket/bundles/bundle.zip",
|
|
320
|
-
expect.objectContaining({
|
|
321
|
-
env: {
|
|
322
|
-
assetHost: "https://assets.example.com",
|
|
323
|
-
},
|
|
324
|
-
request: expect.any(Request),
|
|
325
|
-
}),
|
|
326
|
-
);
|
|
81
|
+
expect(hotUpdater.adapterName).toBe("contextlessTestDatabase");
|
|
82
|
+
expect(hotUpdater.handlers.client).toEqual(expect.any(Function));
|
|
83
|
+
expect(hotUpdater.handlers.admin).toEqual(expect.any(Function));
|
|
84
|
+
expect("createMigrator" in hotUpdater).toBe(false);
|
|
85
|
+
expect("generateSchema" in hotUpdater).toBe(false);
|
|
86
|
+
expect(hotUpdater).not.toHaveProperty("authorityId");
|
|
87
|
+
expect(hotUpdater).not.toHaveProperty("catalogId");
|
|
88
|
+
expectTypeOf(hotUpdater).not.toHaveProperty("createMigrator");
|
|
89
|
+
expectTypeOf(hotUpdater).not.toHaveProperty("generateSchema");
|
|
90
|
+
expectTypeOf(hotUpdater.handlers.client)
|
|
91
|
+
.parameter(0)
|
|
92
|
+
.toEqualTypeOf<Request>();
|
|
327
93
|
});
|
|
328
94
|
|
|
329
|
-
it("
|
|
330
|
-
const request = new Request(
|
|
331
|
-
"https://updates.example.com/api/check-update/app-version/ios/1.0.0/production/" +
|
|
332
|
-
`${NIL_UUID}/${NIL_UUID}`,
|
|
333
|
-
);
|
|
334
|
-
const getBundles = vi.fn<DatabasePlugin<TestContext>["getBundles"]>(
|
|
335
|
-
async () => {
|
|
336
|
-
return {
|
|
337
|
-
data: [bundle],
|
|
338
|
-
pagination: {
|
|
339
|
-
hasNextPage: false,
|
|
340
|
-
hasPreviousPage: false,
|
|
341
|
-
currentPage: 1,
|
|
342
|
-
totalPages: 1,
|
|
343
|
-
total: 1,
|
|
344
|
-
},
|
|
345
|
-
};
|
|
346
|
-
},
|
|
347
|
-
);
|
|
348
|
-
const getDownloadUrl = vi.fn<
|
|
349
|
-
RuntimeStorageProfile<TestContext>["getDownloadUrl"]
|
|
350
|
-
>(async (_storageUri, context) => {
|
|
351
|
-
return {
|
|
352
|
-
fileUrl: new URL("/bundle.zip", context?.env?.assetHost).toString(),
|
|
353
|
-
};
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
const database: DatabasePlugin<TestContext> = {
|
|
357
|
-
name: "testDatabase",
|
|
358
|
-
async appendBundle() {},
|
|
359
|
-
async commitBundle() {},
|
|
360
|
-
async deleteBundle() {},
|
|
361
|
-
async getBundleById(id) {
|
|
362
|
-
return id === bundle.id ? bundle : null;
|
|
363
|
-
},
|
|
364
|
-
getBundles,
|
|
365
|
-
async getChannels() {
|
|
366
|
-
return ["production"];
|
|
367
|
-
},
|
|
368
|
-
async onUnmount() {},
|
|
369
|
-
async updateBundle() {},
|
|
370
|
-
};
|
|
371
|
-
const storage = createRuntimeStorage(getDownloadUrl);
|
|
372
|
-
|
|
95
|
+
it("rejects access when a managed schema is not initialized", async () => {
|
|
373
96
|
const hotUpdater = createHotUpdater({
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
basePath: "/api/check-update",
|
|
377
|
-
routes: {
|
|
378
|
-
updateCheck: true,
|
|
379
|
-
bundles: false,
|
|
380
|
-
},
|
|
97
|
+
clientAccess: publicClientAccess,
|
|
98
|
+
database: createSchemaManagedDatabase("kysely", undefined),
|
|
381
99
|
});
|
|
382
100
|
|
|
383
|
-
|
|
384
|
-
.parameter(1)
|
|
385
|
-
.toEqualTypeOf<TestContext | undefined>();
|
|
386
|
-
|
|
387
|
-
const response = await hotUpdater.handler(request, {
|
|
388
|
-
env: {
|
|
389
|
-
assetHost: "https://assets.example.com",
|
|
390
|
-
},
|
|
391
|
-
request,
|
|
392
|
-
});
|
|
101
|
+
const result = hotUpdater.getBundles({ limit: 10 });
|
|
393
102
|
|
|
394
|
-
expect(
|
|
395
|
-
|
|
396
|
-
fileHash: "hash123",
|
|
397
|
-
fileUrl: "https://assets.example.com/bundle.zip",
|
|
398
|
-
id: "00000000-0000-0000-0000-000000000001",
|
|
399
|
-
message: "Test bundle",
|
|
400
|
-
shouldForceUpdate: false,
|
|
401
|
-
status: "UPDATE",
|
|
402
|
-
});
|
|
403
|
-
expect(getBundles).toHaveBeenCalledWith(
|
|
404
|
-
expect.any(Object),
|
|
405
|
-
expect.objectContaining({
|
|
406
|
-
env: {
|
|
407
|
-
assetHost: "https://assets.example.com",
|
|
408
|
-
},
|
|
409
|
-
request: expect.any(Request),
|
|
410
|
-
}),
|
|
411
|
-
);
|
|
412
|
-
expect(getDownloadUrl).toHaveBeenCalledWith(
|
|
413
|
-
"s3://test-bucket/bundles/bundle.zip",
|
|
414
|
-
expect.objectContaining({
|
|
415
|
-
env: {
|
|
416
|
-
assetHost: "https://assets.example.com",
|
|
417
|
-
},
|
|
418
|
-
request: expect.any(Request),
|
|
419
|
-
}),
|
|
103
|
+
await expect(result).rejects.toThrow(
|
|
104
|
+
"Hot Updater database schema is not initialized for kysely.",
|
|
420
105
|
);
|
|
421
106
|
});
|
|
422
107
|
|
|
423
|
-
it("
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
id: "00000000-0000-0000-0000-000000000001",
|
|
431
|
-
assetBaseStorageUri:
|
|
432
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000001/files",
|
|
433
|
-
manifestFileHash: "sig:current-manifest",
|
|
434
|
-
manifestStorageUri: currentManifestStorageUri,
|
|
435
|
-
storageUri:
|
|
436
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000001/bundle.zip",
|
|
437
|
-
};
|
|
438
|
-
const nextBundle: Bundle = {
|
|
439
|
-
...bundle,
|
|
440
|
-
id: "00000000-0000-0000-0000-000000000002",
|
|
441
|
-
assetBaseStorageUri:
|
|
442
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000002/files",
|
|
443
|
-
manifestFileHash: "sig:next-manifest",
|
|
444
|
-
manifestStorageUri: nextManifestStorageUri,
|
|
445
|
-
patches: [
|
|
446
|
-
{
|
|
447
|
-
baseBundleId: currentBundle.id,
|
|
448
|
-
baseFileHash: "hash-old-bundle",
|
|
449
|
-
patchFileHash: "hash-bsdiff",
|
|
450
|
-
patchStorageUri:
|
|
451
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000002/patches/00000000-0000-0000-0000-000000000001/index.ios.bundle.bsdiff",
|
|
452
|
-
},
|
|
453
|
-
],
|
|
454
|
-
storageUri:
|
|
455
|
-
"s3://test-bucket/releases/00000000-0000-0000-0000-000000000002/bundle.zip",
|
|
456
|
-
};
|
|
457
|
-
const request = new Request(
|
|
458
|
-
"https://updates.example.com/api/check-update/app-version/ios/1.0.0/production/" +
|
|
459
|
-
`${NIL_UUID}/${currentBundle.id}`,
|
|
460
|
-
);
|
|
461
|
-
const getBundles = vi.fn<DatabasePlugin<TestContext>["getBundles"]>(
|
|
462
|
-
async () => ({
|
|
463
|
-
data: [nextBundle],
|
|
464
|
-
pagination: {
|
|
465
|
-
hasNextPage: false,
|
|
466
|
-
hasPreviousPage: false,
|
|
467
|
-
currentPage: 1,
|
|
468
|
-
totalPages: 1,
|
|
469
|
-
total: 1,
|
|
470
|
-
},
|
|
471
|
-
}),
|
|
472
|
-
);
|
|
473
|
-
const getDownloadUrl = vi.fn<
|
|
474
|
-
RuntimeStorageProfile<TestContext>["getDownloadUrl"]
|
|
475
|
-
>(async (storageUri, context) => {
|
|
476
|
-
if (storageUri.endsWith("/files/index.ios.bundle")) {
|
|
477
|
-
throw new Error("full asset fallback is unavailable");
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const storageUrl = new URL(storageUri);
|
|
481
|
-
return {
|
|
482
|
-
fileUrl: new URL(
|
|
483
|
-
storageUrl.pathname,
|
|
484
|
-
context?.env?.assetHost,
|
|
485
|
-
).toString(),
|
|
486
|
-
};
|
|
487
|
-
});
|
|
488
|
-
const manifests = new Map([
|
|
489
|
-
[
|
|
490
|
-
currentManifestStorageUri,
|
|
491
|
-
JSON.stringify({
|
|
492
|
-
assets: {
|
|
493
|
-
"assets/logo.png": {
|
|
494
|
-
fileHash: "hash-logo",
|
|
495
|
-
},
|
|
496
|
-
"index.ios.bundle": {
|
|
497
|
-
fileHash: "hash-old-bundle",
|
|
498
|
-
},
|
|
499
|
-
},
|
|
500
|
-
bundleId: currentBundle.id,
|
|
501
|
-
}),
|
|
502
|
-
],
|
|
503
|
-
[
|
|
504
|
-
nextManifestStorageUri,
|
|
505
|
-
JSON.stringify({
|
|
506
|
-
assets: {
|
|
507
|
-
"assets/logo.png": {
|
|
508
|
-
fileHash: "hash-logo",
|
|
509
|
-
},
|
|
510
|
-
"index.ios.bundle": {
|
|
511
|
-
fileHash: "hash-new-bundle",
|
|
512
|
-
},
|
|
513
|
-
},
|
|
514
|
-
bundleId: nextBundle.id,
|
|
515
|
-
}),
|
|
516
|
-
],
|
|
517
|
-
]);
|
|
518
|
-
const readText = vi.fn<RuntimeStorageProfile<TestContext>["readText"]>(
|
|
519
|
-
async (storageUri) => manifests.get(storageUri) ?? null,
|
|
520
|
-
);
|
|
521
|
-
const fetchMock = vi.fn<typeof fetch>(async () => {
|
|
522
|
-
return new Response("manifest fetch should not be used", {
|
|
523
|
-
status: 500,
|
|
108
|
+
it.each(["s3", "https"])(
|
|
109
|
+
"rejects registered %s storage without getDownloadUrl",
|
|
110
|
+
(protocol) => {
|
|
111
|
+
const storage = createStoragePlugin({
|
|
112
|
+
name: "deployOnlyStorage",
|
|
113
|
+
protocol,
|
|
114
|
+
get: async () => ({ response: null }),
|
|
524
115
|
});
|
|
525
|
-
});
|
|
526
|
-
vi.stubGlobal("fetch", fetchMock);
|
|
527
116
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
async getBundleById(id) {
|
|
534
|
-
if (id === currentBundle.id) {
|
|
535
|
-
return currentBundle;
|
|
536
|
-
}
|
|
537
|
-
if (id === nextBundle.id) {
|
|
538
|
-
return nextBundle;
|
|
539
|
-
}
|
|
540
|
-
return null;
|
|
541
|
-
},
|
|
542
|
-
getBundles,
|
|
543
|
-
async getChannels() {
|
|
544
|
-
return ["production"];
|
|
545
|
-
},
|
|
546
|
-
async onUnmount() {},
|
|
547
|
-
async updateBundle() {},
|
|
548
|
-
};
|
|
549
|
-
const storage = createRuntimeStorage(getDownloadUrl, readText);
|
|
550
|
-
|
|
551
|
-
try {
|
|
552
|
-
const hotUpdater = createHotUpdater({
|
|
553
|
-
database,
|
|
554
|
-
storages: [storage],
|
|
555
|
-
basePath: "/api/check-update",
|
|
556
|
-
routes: {
|
|
557
|
-
updateCheck: true,
|
|
558
|
-
bundles: false,
|
|
559
|
-
},
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
const response = await hotUpdater.handler(request, {
|
|
563
|
-
env: {
|
|
564
|
-
assetHost: "https://assets.example.com",
|
|
565
|
-
},
|
|
566
|
-
request,
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
await expect(response.json()).resolves.toEqual({
|
|
570
|
-
changedAssets: {
|
|
571
|
-
"index.ios.bundle": {
|
|
572
|
-
file: {
|
|
573
|
-
compression: "br",
|
|
574
|
-
url: "https://assets.example.com/releases/00000000-0000-0000-0000-000000000002/files/index.ios.bundle.br",
|
|
575
|
-
},
|
|
576
|
-
fileHash: "hash-new-bundle",
|
|
577
|
-
patch: {
|
|
578
|
-
algorithm: "bsdiff",
|
|
579
|
-
baseBundleId: "00000000-0000-0000-0000-000000000001",
|
|
580
|
-
baseFileHash: "hash-old-bundle",
|
|
581
|
-
patchFileHash: "hash-bsdiff",
|
|
582
|
-
patchUrl:
|
|
583
|
-
"https://assets.example.com/releases/00000000-0000-0000-0000-000000000002/patches/00000000-0000-0000-0000-000000000001/index.ios.bundle.bsdiff",
|
|
584
|
-
},
|
|
585
|
-
},
|
|
586
|
-
},
|
|
587
|
-
fileHash: "hash123",
|
|
588
|
-
fileUrl:
|
|
589
|
-
"https://assets.example.com/releases/00000000-0000-0000-0000-000000000002/bundle.zip",
|
|
590
|
-
id: "00000000-0000-0000-0000-000000000002",
|
|
591
|
-
manifestFileHash: "sig:next-manifest",
|
|
592
|
-
manifestUrl:
|
|
593
|
-
"https://assets.example.com/releases/00000000-0000-0000-0000-000000000002/manifest.json",
|
|
594
|
-
message: "Test bundle",
|
|
595
|
-
shouldForceUpdate: false,
|
|
596
|
-
status: "UPDATE",
|
|
597
|
-
});
|
|
598
|
-
expect(readText).toHaveBeenCalledWith(
|
|
599
|
-
nextManifestStorageUri,
|
|
600
|
-
expect.objectContaining({
|
|
601
|
-
env: {
|
|
602
|
-
assetHost: "https://assets.example.com",
|
|
603
|
-
},
|
|
604
|
-
request: expect.any(Request),
|
|
117
|
+
expect(() =>
|
|
118
|
+
createHotUpdater({
|
|
119
|
+
clientAccess: publicClientAccess,
|
|
120
|
+
database: createRuntimeDatabase(),
|
|
121
|
+
storage: [storage],
|
|
605
122
|
}),
|
|
123
|
+
).toThrow(
|
|
124
|
+
'Storage plugin "deployOnlyStorage" does not implement getDownloadUrl.',
|
|
606
125
|
);
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
expect.objectContaining({
|
|
610
|
-
env: {
|
|
611
|
-
assetHost: "https://assets.example.com",
|
|
612
|
-
},
|
|
613
|
-
request: expect.any(Request),
|
|
614
|
-
}),
|
|
615
|
-
);
|
|
616
|
-
expect(fetchMock).not.toHaveBeenCalled();
|
|
617
|
-
} finally {
|
|
618
|
-
vi.unstubAllGlobals();
|
|
619
|
-
}
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
it("does not inject the request into context unless explicitly provided", async () => {
|
|
623
|
-
const getBundles = vi.fn<DatabasePlugin<TestContext>["getBundles"]>(
|
|
624
|
-
async () => {
|
|
625
|
-
return {
|
|
626
|
-
data: [bundle],
|
|
627
|
-
pagination: {
|
|
628
|
-
hasNextPage: false,
|
|
629
|
-
hasPreviousPage: false,
|
|
630
|
-
currentPage: 1,
|
|
631
|
-
totalPages: 1,
|
|
632
|
-
total: 1,
|
|
633
|
-
},
|
|
634
|
-
};
|
|
635
|
-
},
|
|
636
|
-
);
|
|
637
|
-
|
|
638
|
-
const database: DatabasePlugin<TestContext> = {
|
|
639
|
-
name: "testDatabase",
|
|
640
|
-
async appendBundle() {},
|
|
641
|
-
async commitBundle() {},
|
|
642
|
-
async deleteBundle() {},
|
|
643
|
-
async getBundleById(id) {
|
|
644
|
-
return id === bundle.id ? bundle : null;
|
|
645
|
-
},
|
|
646
|
-
getBundles,
|
|
647
|
-
async getChannels() {
|
|
648
|
-
return ["production"];
|
|
649
|
-
},
|
|
650
|
-
async onUnmount() {},
|
|
651
|
-
async updateBundle() {},
|
|
652
|
-
};
|
|
653
|
-
const storage = createRuntimeStorage(async () => {
|
|
654
|
-
return { fileUrl: "https://assets.example.com/bundle.zip" };
|
|
655
|
-
});
|
|
126
|
+
},
|
|
127
|
+
);
|
|
656
128
|
|
|
129
|
+
it("rejects access when a managed schema is stale", async () => {
|
|
657
130
|
const hotUpdater = createHotUpdater({
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
basePath: "/api/check-update",
|
|
661
|
-
routes: {
|
|
662
|
-
updateCheck: true,
|
|
663
|
-
bundles: false,
|
|
664
|
-
},
|
|
131
|
+
clientAccess: publicClientAccess,
|
|
132
|
+
database: createSchemaManagedDatabase("mongodb", "0.21.0"),
|
|
665
133
|
});
|
|
666
134
|
|
|
667
|
-
const
|
|
668
|
-
new Request(
|
|
669
|
-
"https://updates.example.com/api/check-update/app-version/ios/1.0.0/production/" +
|
|
670
|
-
`${NIL_UUID}/${NIL_UUID}`,
|
|
671
|
-
),
|
|
672
|
-
{
|
|
673
|
-
env: {
|
|
674
|
-
assetHost: "https://assets.example.com",
|
|
675
|
-
},
|
|
676
|
-
},
|
|
677
|
-
);
|
|
135
|
+
const result = hotUpdater.getChannels();
|
|
678
136
|
|
|
679
|
-
expect(
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
assetHost: "https://assets.example.com",
|
|
683
|
-
},
|
|
684
|
-
});
|
|
137
|
+
await expect(result).rejects.toThrow(
|
|
138
|
+
"Hot Updater v1 cannot migrate schema 0.21.0 in place.",
|
|
139
|
+
);
|
|
685
140
|
});
|
|
686
141
|
|
|
687
|
-
it("
|
|
688
|
-
const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
data: [bundle],
|
|
692
|
-
pagination: {
|
|
693
|
-
hasNextPage: false,
|
|
694
|
-
hasPreviousPage: false,
|
|
695
|
-
currentPage: 1,
|
|
696
|
-
totalPages: 1,
|
|
697
|
-
total: 1,
|
|
698
|
-
},
|
|
699
|
-
};
|
|
700
|
-
},
|
|
142
|
+
it("checks a ready managed schema only once", async () => {
|
|
143
|
+
const database = createSchemaManagedDatabase(
|
|
144
|
+
"kysely",
|
|
145
|
+
HOT_UPDATER_SCHEMA_VERSION,
|
|
701
146
|
);
|
|
702
|
-
|
|
703
|
-
const database: DatabasePlugin<TestContext> = {
|
|
704
|
-
name: "testDatabase",
|
|
705
|
-
async appendBundle() {},
|
|
706
|
-
async commitBundle() {},
|
|
707
|
-
async deleteBundle() {},
|
|
708
|
-
async getBundleById(id) {
|
|
709
|
-
return id === bundle.id ? bundle : null;
|
|
710
|
-
},
|
|
711
|
-
getBundles,
|
|
712
|
-
async getChannels() {
|
|
713
|
-
return ["production"];
|
|
714
|
-
},
|
|
715
|
-
async onUnmount() {},
|
|
716
|
-
async updateBundle() {},
|
|
717
|
-
};
|
|
718
|
-
const storage = createRuntimeStorage(async () => {
|
|
719
|
-
return { fileUrl: "https://assets.example.com/bundle.zip" };
|
|
720
|
-
});
|
|
721
|
-
|
|
147
|
+
const createMigrator = vi.spyOn(database, "createMigrator");
|
|
722
148
|
const hotUpdater = createHotUpdater({
|
|
149
|
+
clientAccess: publicClientAccess,
|
|
723
150
|
database,
|
|
724
|
-
storages: [storage],
|
|
725
|
-
basePath: "/api/check-update",
|
|
726
|
-
routes: {
|
|
727
|
-
updateCheck: true,
|
|
728
|
-
bundles: false,
|
|
729
|
-
},
|
|
730
151
|
});
|
|
731
152
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
context?: unknown,
|
|
735
|
-
executionCtx?: unknown,
|
|
736
|
-
) => Promise<Response>;
|
|
737
|
-
const response = await mountStyleHandler(
|
|
738
|
-
new Request(
|
|
739
|
-
"https://updates.example.com/app-version/ios/1.0.0/production/" +
|
|
740
|
-
`${NIL_UUID}/${NIL_UUID}`,
|
|
741
|
-
),
|
|
742
|
-
{ someBinding: "ignored" },
|
|
743
|
-
{ waitUntil: () => undefined },
|
|
744
|
-
);
|
|
153
|
+
await hotUpdater.getChannels();
|
|
154
|
+
await hotUpdater.getChannels();
|
|
745
155
|
|
|
746
|
-
expect(
|
|
747
|
-
expect(getBundles).toHaveBeenCalledWith(expect.any(Object), undefined);
|
|
156
|
+
expect(createMigrator).toHaveBeenCalledOnce();
|
|
748
157
|
});
|
|
749
158
|
|
|
750
|
-
it("keeps the version route mounted
|
|
751
|
-
const database = createDatabasePlugin({
|
|
752
|
-
name: "version-enabled-plugin",
|
|
753
|
-
factory: () => ({
|
|
754
|
-
async getBundleById() {
|
|
755
|
-
return null;
|
|
756
|
-
},
|
|
757
|
-
async getBundles() {
|
|
758
|
-
return {
|
|
759
|
-
data: [],
|
|
760
|
-
pagination: {
|
|
761
|
-
hasNextPage: false,
|
|
762
|
-
hasPreviousPage: false,
|
|
763
|
-
currentPage: 1,
|
|
764
|
-
totalPages: 1,
|
|
765
|
-
total: 0,
|
|
766
|
-
},
|
|
767
|
-
};
|
|
768
|
-
},
|
|
769
|
-
async getChannels() {
|
|
770
|
-
return [];
|
|
771
|
-
},
|
|
772
|
-
async commitBundle() {},
|
|
773
|
-
}),
|
|
774
|
-
})({});
|
|
775
|
-
|
|
159
|
+
it("keeps the version route mounted on the client handler", async () => {
|
|
776
160
|
const hotUpdater = createHotUpdater({
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
routes: {
|
|
780
|
-
updateCheck: true,
|
|
781
|
-
bundles: false,
|
|
782
|
-
},
|
|
161
|
+
clientAccess: publicClientAccess,
|
|
162
|
+
database: createRuntimeDatabase(),
|
|
783
163
|
});
|
|
784
164
|
|
|
785
|
-
const response = await hotUpdater.
|
|
786
|
-
new Request("https://updates.example.com/
|
|
165
|
+
const response = await hotUpdater.handlers.client(
|
|
166
|
+
new Request("https://updates.example.com/version"),
|
|
787
167
|
);
|
|
788
168
|
|
|
789
169
|
expect(response.status).toBe(200);
|
|
790
170
|
await expect(response.json()).resolves.toEqual({
|
|
171
|
+
infrastructureGeneration: 1,
|
|
791
172
|
version: HOT_UPDATER_SERVER_VERSION,
|
|
792
173
|
});
|
|
793
174
|
});
|
|
794
175
|
|
|
795
|
-
it("
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
},
|
|
802
|
-
async getBundles() {
|
|
803
|
-
return {
|
|
804
|
-
data: [],
|
|
805
|
-
pagination: {
|
|
806
|
-
hasNextPage: false,
|
|
807
|
-
hasPreviousPage: false,
|
|
808
|
-
currentPage: 1,
|
|
809
|
-
totalPages: 1,
|
|
810
|
-
total: 0,
|
|
811
|
-
},
|
|
812
|
-
};
|
|
813
|
-
},
|
|
814
|
-
async getChannels() {
|
|
815
|
-
return [];
|
|
816
|
-
},
|
|
817
|
-
async commitBundle() {},
|
|
818
|
-
}),
|
|
819
|
-
})({});
|
|
820
|
-
|
|
821
|
-
const hotUpdater = createHotUpdater({
|
|
822
|
-
database,
|
|
823
|
-
basePath: "/api/check-update",
|
|
824
|
-
routes: {
|
|
825
|
-
updateCheck: false,
|
|
826
|
-
bundles: false,
|
|
827
|
-
},
|
|
828
|
-
});
|
|
829
|
-
|
|
830
|
-
const response = await hotUpdater.handler(
|
|
831
|
-
new Request("https://updates.example.com/api/check-update/version"),
|
|
832
|
-
);
|
|
833
|
-
|
|
834
|
-
expect(response.status).toBe(200);
|
|
835
|
-
await expect(response.json()).resolves.toEqual({
|
|
836
|
-
version: HOT_UPDATER_SERVER_VERSION,
|
|
837
|
-
});
|
|
838
|
-
});
|
|
839
|
-
|
|
840
|
-
it("keeps optional maintenance capabilities lazy", () => {
|
|
841
|
-
const factory = vi.fn(() => ({
|
|
842
|
-
async getBundleById() {
|
|
843
|
-
return null;
|
|
844
|
-
},
|
|
845
|
-
async getBundles() {
|
|
846
|
-
return {
|
|
847
|
-
data: [],
|
|
848
|
-
pagination: {
|
|
849
|
-
hasNextPage: false,
|
|
850
|
-
hasPreviousPage: false,
|
|
851
|
-
currentPage: 1,
|
|
852
|
-
totalPages: 1,
|
|
853
|
-
total: 0,
|
|
854
|
-
},
|
|
855
|
-
};
|
|
856
|
-
},
|
|
857
|
-
async getChannels() {
|
|
858
|
-
return [];
|
|
859
|
-
},
|
|
860
|
-
async commitBundle() {},
|
|
861
|
-
}));
|
|
862
|
-
const database = createDatabasePlugin({
|
|
863
|
-
name: "lazyRuntimePlugin",
|
|
864
|
-
factory,
|
|
865
|
-
})({});
|
|
866
|
-
|
|
867
|
-
createHotUpdater({
|
|
868
|
-
database,
|
|
869
|
-
basePath: "/api/check-update",
|
|
870
|
-
});
|
|
871
|
-
|
|
872
|
-
expect(factory).not.toHaveBeenCalled();
|
|
873
|
-
});
|
|
874
|
-
|
|
875
|
-
it("clears pending plugin changes after a failed mutation commit", async () => {
|
|
876
|
-
const committedBundles = new Map<string, Bundle>();
|
|
877
|
-
let commitAttempt = 0;
|
|
878
|
-
|
|
879
|
-
const database = createDatabasePlugin({
|
|
880
|
-
name: "failingPlugin",
|
|
881
|
-
factory: () => ({
|
|
882
|
-
async getBundleById(bundleId) {
|
|
883
|
-
return committedBundles.get(bundleId) ?? null;
|
|
884
|
-
},
|
|
885
|
-
async getBundles() {
|
|
886
|
-
return {
|
|
887
|
-
data: Array.from(committedBundles.values()),
|
|
888
|
-
pagination: {
|
|
889
|
-
hasNextPage: false,
|
|
890
|
-
hasPreviousPage: false,
|
|
891
|
-
currentPage: 1,
|
|
892
|
-
totalPages: 1,
|
|
893
|
-
total: committedBundles.size,
|
|
894
|
-
},
|
|
895
|
-
};
|
|
896
|
-
},
|
|
897
|
-
async getChannels() {
|
|
898
|
-
return [];
|
|
899
|
-
},
|
|
900
|
-
async commitBundle({ changedSets }) {
|
|
901
|
-
commitAttempt += 1;
|
|
902
|
-
|
|
903
|
-
if (commitAttempt === 1) {
|
|
904
|
-
throw new Error("commit failed");
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
for (const change of changedSets) {
|
|
908
|
-
if (change.operation === "delete") {
|
|
909
|
-
committedBundles.delete(change.data.id);
|
|
910
|
-
continue;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
committedBundles.set(change.data.id, change.data);
|
|
914
|
-
}
|
|
915
|
-
},
|
|
916
|
-
}),
|
|
917
|
-
})({});
|
|
918
|
-
|
|
919
|
-
const hotUpdater = createHotUpdater({
|
|
920
|
-
database,
|
|
921
|
-
basePath: "/api/check-update",
|
|
922
|
-
routes: {
|
|
923
|
-
updateCheck: false,
|
|
924
|
-
bundles: false,
|
|
925
|
-
},
|
|
926
|
-
});
|
|
927
|
-
|
|
928
|
-
const failedBundle: Bundle = {
|
|
929
|
-
...bundle,
|
|
930
|
-
id: "00000000-0000-0000-0000-000000000010",
|
|
931
|
-
message: "failed bundle",
|
|
932
|
-
};
|
|
933
|
-
const succeedingBundle: Bundle = {
|
|
934
|
-
...bundle,
|
|
935
|
-
id: "00000000-0000-0000-0000-000000000011",
|
|
936
|
-
message: "succeeding bundle",
|
|
937
|
-
};
|
|
938
|
-
|
|
939
|
-
await expect(hotUpdater.insertBundle(failedBundle)).rejects.toThrow(
|
|
940
|
-
"commit failed",
|
|
941
|
-
);
|
|
942
|
-
await hotUpdater.insertBundle(succeedingBundle);
|
|
943
|
-
|
|
944
|
-
expect(await hotUpdater.getBundleById(failedBundle.id)).toBeNull();
|
|
945
|
-
await expect(
|
|
946
|
-
hotUpdater.getBundleById(succeedingBundle.id),
|
|
947
|
-
).resolves.toEqual(succeedingBundle);
|
|
176
|
+
it("rejects a missing client access policy", () => {
|
|
177
|
+
expect(() =>
|
|
178
|
+
createHotUpdater({
|
|
179
|
+
database: createRuntimeDatabase(),
|
|
180
|
+
} as unknown as CreateHotUpdaterOptions),
|
|
181
|
+
).toThrow("clientAccess must be an object.");
|
|
948
182
|
});
|
|
949
183
|
|
|
950
|
-
it("
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const firstCommitStarted = new Promise<void>((resolve) => {
|
|
956
|
-
notifyFirstCommitStarted = resolve;
|
|
957
|
-
});
|
|
958
|
-
const firstCommitGate = new Promise<void>((resolve) => {
|
|
959
|
-
releaseFirstCommit = resolve;
|
|
960
|
-
});
|
|
961
|
-
let commitCount = 0;
|
|
962
|
-
|
|
963
|
-
const database = createDatabasePlugin({
|
|
964
|
-
name: "isolatedPlugin",
|
|
965
|
-
factory: () => ({
|
|
966
|
-
async getBundleById() {
|
|
967
|
-
return null;
|
|
968
|
-
},
|
|
969
|
-
async getBundles() {
|
|
970
|
-
return {
|
|
971
|
-
data: [],
|
|
972
|
-
pagination: {
|
|
973
|
-
hasNextPage: false,
|
|
974
|
-
hasPreviousPage: false,
|
|
975
|
-
currentPage: 1,
|
|
976
|
-
totalPages: 1,
|
|
977
|
-
total: 0,
|
|
978
|
-
},
|
|
979
|
-
};
|
|
980
|
-
},
|
|
981
|
-
async getChannels() {
|
|
982
|
-
return [];
|
|
983
|
-
},
|
|
984
|
-
onUnmount,
|
|
985
|
-
async commitBundle({ changedSets }) {
|
|
986
|
-
commitCount += 1;
|
|
987
|
-
committedBundleIds.push(changedSets.map((change) => change.data.id));
|
|
988
|
-
|
|
989
|
-
if (commitCount === 1) {
|
|
990
|
-
notifyFirstCommitStarted();
|
|
991
|
-
await firstCommitGate;
|
|
992
|
-
}
|
|
184
|
+
it("rejects an unsupported client access policy", () => {
|
|
185
|
+
expect(() =>
|
|
186
|
+
createHotUpdater({
|
|
187
|
+
clientAccess: {
|
|
188
|
+
type: "private" as unknown as "public",
|
|
993
189
|
},
|
|
190
|
+
database: createRuntimeDatabase(),
|
|
994
191
|
}),
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
const hotUpdater = createHotUpdater({
|
|
998
|
-
database,
|
|
999
|
-
basePath: "/api/check-update",
|
|
1000
|
-
routes: {
|
|
1001
|
-
updateCheck: false,
|
|
1002
|
-
bundles: false,
|
|
1003
|
-
},
|
|
1004
|
-
});
|
|
1005
|
-
|
|
1006
|
-
const firstBundleId = "00000000-0000-0000-0000-000000000020";
|
|
1007
|
-
const secondBundleId = "00000000-0000-0000-0000-000000000021";
|
|
1008
|
-
|
|
1009
|
-
const firstInsert = hotUpdater.insertBundle({
|
|
1010
|
-
...bundle,
|
|
1011
|
-
id: firstBundleId,
|
|
1012
|
-
message: "first bundle",
|
|
1013
|
-
});
|
|
1014
|
-
await firstCommitStarted;
|
|
1015
|
-
|
|
1016
|
-
const secondInsert = hotUpdater.insertBundle({
|
|
1017
|
-
...bundle,
|
|
1018
|
-
id: secondBundleId,
|
|
1019
|
-
message: "second bundle",
|
|
1020
|
-
});
|
|
1021
|
-
|
|
1022
|
-
releaseFirstCommit();
|
|
1023
|
-
await Promise.all([firstInsert, secondInsert]);
|
|
1024
|
-
|
|
1025
|
-
expect(committedBundleIds).toEqual([[firstBundleId], [secondBundleId]]);
|
|
192
|
+
).toThrow('clientAccess.type must be either "public" or "api-key".');
|
|
1026
193
|
});
|
|
1027
194
|
});
|