@hot-updater/server 0.36.6 → 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
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { DatabaseRowReferencedError } from "@hot-updater/plugin-core/internal";
|
|
2
|
+
|
|
3
|
+
const readErrorCode = (error: unknown): string | undefined => {
|
|
4
|
+
if (typeof error !== "object" || error === null) return undefined;
|
|
5
|
+
const code = Reflect.get(error, "code");
|
|
6
|
+
return typeof code === "string" ? code : undefined;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const isChannelDeleteReferencedError = (error: unknown): boolean =>
|
|
10
|
+
error instanceof DatabaseRowReferencedError ||
|
|
11
|
+
[
|
|
12
|
+
"23503",
|
|
13
|
+
"SQLITE_CONSTRAINT_FOREIGNKEY",
|
|
14
|
+
"SQLITE_CONSTRAINT",
|
|
15
|
+
"ER_ROW_IS_REFERENCED",
|
|
16
|
+
"ER_ROW_IS_REFERENCED_2",
|
|
17
|
+
"P2003",
|
|
18
|
+
].includes(readErrorCode(error) ?? "");
|
|
19
|
+
|
|
20
|
+
export const translateChannelDeleteError = (error: unknown): never => {
|
|
21
|
+
if (isChannelDeleteReferencedError(error)) {
|
|
22
|
+
throw new DatabaseRowReferencedError();
|
|
23
|
+
}
|
|
24
|
+
throw error;
|
|
25
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export const DATABASE_PLUGIN_TEST_SCHEMA_SQL = `
|
|
2
|
+
create table channels (
|
|
3
|
+
id text primary key,
|
|
4
|
+
name text not null unique
|
|
5
|
+
);
|
|
6
|
+
create table bundles (
|
|
7
|
+
id text primary key,
|
|
8
|
+
platform text not null,
|
|
9
|
+
file_hash text not null,
|
|
10
|
+
git_commit_hash text,
|
|
11
|
+
storage_uri text not null,
|
|
12
|
+
archive_byte_size double precision not null check (
|
|
13
|
+
archive_byte_size >= 0 and archive_byte_size <= 9007199254740991
|
|
14
|
+
),
|
|
15
|
+
metadata jsonb not null default '{}'::jsonb,
|
|
16
|
+
manifest_storage_uri text,
|
|
17
|
+
manifest_file_hash text,
|
|
18
|
+
asset_base_storage_uri text
|
|
19
|
+
);
|
|
20
|
+
create table bundle_patches (
|
|
21
|
+
id varchar(255) primary key,
|
|
22
|
+
bundle_id text not null references bundles(id) on delete cascade,
|
|
23
|
+
base_bundle_id text not null references bundles(id) on delete cascade,
|
|
24
|
+
base_file_hash text not null,
|
|
25
|
+
patch_file_hash text not null,
|
|
26
|
+
patch_storage_uri text not null,
|
|
27
|
+
byte_size double precision not null check (
|
|
28
|
+
byte_size >= 0 and byte_size <= 9007199254740991
|
|
29
|
+
),
|
|
30
|
+
order_index integer not null default 0
|
|
31
|
+
);
|
|
32
|
+
create table releases (
|
|
33
|
+
id text primary key,
|
|
34
|
+
revision integer not null,
|
|
35
|
+
scope_key text not null,
|
|
36
|
+
channel_id text not null references channels(id),
|
|
37
|
+
platform text not null,
|
|
38
|
+
kind text not null,
|
|
39
|
+
bundle_id text references bundles(id),
|
|
40
|
+
strategy text not null,
|
|
41
|
+
target_app_version text,
|
|
42
|
+
fingerprint_hash text,
|
|
43
|
+
enabled boolean not null,
|
|
44
|
+
should_force_update boolean not null,
|
|
45
|
+
message text,
|
|
46
|
+
rollout_cohort_count integer not null default 1000,
|
|
47
|
+
target_cohorts jsonb not null default '[]'::jsonb,
|
|
48
|
+
operation text not null,
|
|
49
|
+
source_release_id text references releases(id) on delete set null,
|
|
50
|
+
created_at_ms integer not null,
|
|
51
|
+
updated_at_ms integer not null
|
|
52
|
+
);
|
|
53
|
+
create table release_catalogs (
|
|
54
|
+
scope_key text primary key,
|
|
55
|
+
catalog_id text not null,
|
|
56
|
+
strategy text not null,
|
|
57
|
+
channel_id text not null references channels(id),
|
|
58
|
+
channel_key text not null,
|
|
59
|
+
platform text not null,
|
|
60
|
+
fingerprint_hash text,
|
|
61
|
+
generation integer not null,
|
|
62
|
+
payload text not null,
|
|
63
|
+
catalog_hash text not null,
|
|
64
|
+
byte_size integer not null,
|
|
65
|
+
is_tombstone boolean not null,
|
|
66
|
+
updated_at_ms integer not null
|
|
67
|
+
);
|
|
68
|
+
create table bundle_events (
|
|
69
|
+
id text primary key,
|
|
70
|
+
type text not null,
|
|
71
|
+
install_id text not null,
|
|
72
|
+
user_id text,
|
|
73
|
+
username text,
|
|
74
|
+
from_release_id text,
|
|
75
|
+
from_bundle_id text,
|
|
76
|
+
to_release_id text,
|
|
77
|
+
to_bundle_id text not null,
|
|
78
|
+
platform text not null,
|
|
79
|
+
app_version text not null,
|
|
80
|
+
channel text not null,
|
|
81
|
+
cohort text not null,
|
|
82
|
+
update_strategy text,
|
|
83
|
+
fingerprint_hash text,
|
|
84
|
+
sdk_version text,
|
|
85
|
+
received_at_ms integer not null
|
|
86
|
+
);
|
|
87
|
+
create table api_keys (
|
|
88
|
+
id text primary key,
|
|
89
|
+
hash text not null unique,
|
|
90
|
+
name text not null,
|
|
91
|
+
prefix text not null,
|
|
92
|
+
role text not null,
|
|
93
|
+
created_at_ms integer not null,
|
|
94
|
+
revoked_at_ms integer
|
|
95
|
+
);
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
export const DATABASE_PLUGIN_TEST_RESET_SQL = `
|
|
99
|
+
delete from bundle_events;
|
|
100
|
+
delete from api_keys;
|
|
101
|
+
delete from bundle_patches;
|
|
102
|
+
delete from release_catalogs;
|
|
103
|
+
delete from releases;
|
|
104
|
+
delete from bundles;
|
|
105
|
+
delete from channels;
|
|
106
|
+
`;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { createBundleRowFixture } from "../../../test-utils/src/databaseTestFixtures";
|
|
4
|
+
import { fromStoredBundleRow } from "./databasePluginUtils";
|
|
5
|
+
|
|
6
|
+
describe("fromStoredBundleRow", () => {
|
|
7
|
+
it("parses JSON-object metadata from SQL text storage", () => {
|
|
8
|
+
const row = fromStoredBundleRow({
|
|
9
|
+
...createBundleRowFixture("valid-metadata"),
|
|
10
|
+
metadata: '{"release":{"flags":[true,null,3]}}',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
expect(row.metadata).toEqual({
|
|
14
|
+
release: { flags: [true, null, 3] },
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it.each(["null", "[]", "1", '"metadata"', "not-json"])(
|
|
19
|
+
"rejects non-object SQL metadata %s",
|
|
20
|
+
(metadata) => {
|
|
21
|
+
expect(() =>
|
|
22
|
+
fromStoredBundleRow({
|
|
23
|
+
...createBundleRowFixture("invalid-metadata"),
|
|
24
|
+
metadata,
|
|
25
|
+
}),
|
|
26
|
+
).toThrow("Invalid metadata");
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
});
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BundleRow,
|
|
3
|
+
ReleaseCatalogRow,
|
|
4
|
+
ReleaseRow,
|
|
5
|
+
ReleaseRowUpdate,
|
|
6
|
+
} from "@hot-updater/plugin-core";
|
|
7
|
+
import { isDatabaseMetadataObject } from "@hot-updater/plugin-core";
|
|
8
|
+
import type {
|
|
9
|
+
DatabaseModel,
|
|
10
|
+
DatabaseOrderBy,
|
|
11
|
+
DatabaseSortBy,
|
|
12
|
+
} from "@hot-updater/plugin-core/internal";
|
|
13
|
+
|
|
14
|
+
import type { ORMSQLProvider } from "../db/types";
|
|
15
|
+
|
|
16
|
+
export type StoredBundleRow = Omit<BundleRow, "metadata"> & {
|
|
17
|
+
readonly metadata: unknown;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type StoredReleaseRow = Omit<ReleaseRow, "target_cohorts"> & {
|
|
21
|
+
readonly target_cohorts: unknown;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type StoredReleaseCatalogRow = Omit<
|
|
25
|
+
ReleaseCatalogRow,
|
|
26
|
+
"is_tombstone"
|
|
27
|
+
> & {
|
|
28
|
+
readonly is_tombstone: unknown;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type AnyDatabaseSortBy = {
|
|
32
|
+
readonly [TModel in DatabaseModel]: DatabaseSortBy<TModel>;
|
|
33
|
+
}[DatabaseModel];
|
|
34
|
+
type AnyDatabaseOrderBy = {
|
|
35
|
+
readonly [TModel in DatabaseModel]: DatabaseOrderBy<TModel>;
|
|
36
|
+
}[DatabaseModel];
|
|
37
|
+
type AnyDatabaseOrderClause = AnyDatabaseSortBy;
|
|
38
|
+
|
|
39
|
+
class StoredBundleRowError extends Error {
|
|
40
|
+
readonly name = "StoredBundleRowError";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const parseStoredBoolean = (value: unknown, field: string): boolean => {
|
|
44
|
+
if (value === true || value === 1) return true;
|
|
45
|
+
if (value === false || value === 0) return false;
|
|
46
|
+
throw new StoredBundleRowError(`Invalid boolean value for ${field}.`);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const parseJson = (value: string): unknown => {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(value);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error instanceof SyntaxError) return value;
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const parseTargetCohorts = (value: unknown): readonly string[] | null => {
|
|
59
|
+
if (value === null) return null;
|
|
60
|
+
const parsed = typeof value === "string" ? parseJson(value) : value;
|
|
61
|
+
if (
|
|
62
|
+
!Array.isArray(parsed) ||
|
|
63
|
+
!parsed.every((item) => typeof item === "string")
|
|
64
|
+
) {
|
|
65
|
+
throw new StoredBundleRowError("Invalid target_cohorts field.");
|
|
66
|
+
}
|
|
67
|
+
return parsed;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const parseMetadata = (value: unknown) => {
|
|
71
|
+
const parsed = typeof value === "string" ? parseJson(value) : value;
|
|
72
|
+
if (!isDatabaseMetadataObject(parsed)) {
|
|
73
|
+
throw new StoredBundleRowError("Invalid metadata field.");
|
|
74
|
+
}
|
|
75
|
+
return parsed;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const compareOrderValues = (left: unknown, right: unknown): number => {
|
|
79
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
80
|
+
return left - right;
|
|
81
|
+
}
|
|
82
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
83
|
+
return left.localeCompare(right);
|
|
84
|
+
}
|
|
85
|
+
if (typeof left === "boolean" && typeof right === "boolean") {
|
|
86
|
+
return Number(left) - Number(right);
|
|
87
|
+
}
|
|
88
|
+
if (left === right) {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
return String(left).localeCompare(String(right));
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const normalizeOrderBy = (
|
|
95
|
+
orderBy: AnyDatabaseOrderBy | readonly AnyDatabaseSortBy[] | undefined,
|
|
96
|
+
): readonly AnyDatabaseOrderClause[] => {
|
|
97
|
+
if (orderBy === undefined) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
return (
|
|
101
|
+
Array.isArray(orderBy) ? orderBy : [orderBy]
|
|
102
|
+
) as readonly AnyDatabaseOrderClause[];
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export const hasNullOrderOverrides = (
|
|
106
|
+
orderBy: AnyDatabaseOrderBy | readonly AnyDatabaseSortBy[] | undefined,
|
|
107
|
+
): boolean =>
|
|
108
|
+
normalizeOrderBy(orderBy).some((clause) => clause.nulls !== undefined);
|
|
109
|
+
|
|
110
|
+
export const sortRowsByOrder = <TRow extends object>(
|
|
111
|
+
rows: readonly TRow[],
|
|
112
|
+
orderBy: AnyDatabaseOrderBy | readonly AnyDatabaseSortBy[] | undefined,
|
|
113
|
+
): TRow[] => {
|
|
114
|
+
const clauses = normalizeOrderBy(orderBy);
|
|
115
|
+
if (clauses.length === 0) {
|
|
116
|
+
return [...rows];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return [...rows].toSorted((left, right) => {
|
|
120
|
+
for (const clause of clauses) {
|
|
121
|
+
const leftValue = Reflect.get(left, clause.field);
|
|
122
|
+
const rightValue = Reflect.get(right, clause.field);
|
|
123
|
+
|
|
124
|
+
if (leftValue == null || rightValue == null) {
|
|
125
|
+
if (leftValue == null && rightValue == null) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const nulls =
|
|
129
|
+
clause.nulls ?? (clause.direction === "asc" ? "last" : "first");
|
|
130
|
+
const order = leftValue == null ? -1 : 1;
|
|
131
|
+
return nulls === "first" ? order : -order;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const result = compareOrderValues(leftValue, rightValue);
|
|
135
|
+
if (result !== 0) {
|
|
136
|
+
return clause.direction === "desc" ? -result : result;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return 0;
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const fromStoredBundleRow = (row: StoredBundleRow): BundleRow => ({
|
|
144
|
+
...row,
|
|
145
|
+
metadata: parseMetadata(row.metadata),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
export const toStoredBundleRow = (
|
|
149
|
+
row: BundleRow,
|
|
150
|
+
provider: ORMSQLProvider,
|
|
151
|
+
): StoredBundleRow => {
|
|
152
|
+
if (provider !== "mysql" && provider !== "sqlite") return row;
|
|
153
|
+
return {
|
|
154
|
+
...row,
|
|
155
|
+
metadata: JSON.stringify(row.metadata),
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const toStoredBundleUpdate = (
|
|
160
|
+
update: Partial<Omit<BundleRow, "id">>,
|
|
161
|
+
provider: ORMSQLProvider,
|
|
162
|
+
): Partial<Omit<StoredBundleRow, "id">> => {
|
|
163
|
+
if (provider !== "mysql" && provider !== "sqlite") return update;
|
|
164
|
+
return {
|
|
165
|
+
...update,
|
|
166
|
+
...("metadata" in update
|
|
167
|
+
? { metadata: JSON.stringify(update.metadata) }
|
|
168
|
+
: {}),
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const fromStoredReleaseRow = (row: StoredReleaseRow): ReleaseRow => ({
|
|
173
|
+
...row,
|
|
174
|
+
enabled: parseStoredBoolean(row.enabled, "enabled"),
|
|
175
|
+
should_force_update: parseStoredBoolean(
|
|
176
|
+
row.should_force_update,
|
|
177
|
+
"should_force_update",
|
|
178
|
+
),
|
|
179
|
+
target_cohorts: parseTargetCohorts(row.target_cohorts) ?? [],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
export const toStoredReleaseRow = (
|
|
183
|
+
row: ReleaseRow,
|
|
184
|
+
provider: ORMSQLProvider,
|
|
185
|
+
): StoredReleaseRow => {
|
|
186
|
+
if (provider !== "mysql" && provider !== "sqlite") return row;
|
|
187
|
+
return { ...row, target_cohorts: JSON.stringify(row.target_cohorts) };
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export const toStoredReleaseUpdate = (
|
|
191
|
+
update: ReleaseRowUpdate,
|
|
192
|
+
provider: ORMSQLProvider,
|
|
193
|
+
): Partial<Omit<StoredReleaseRow, "id">> => {
|
|
194
|
+
if (provider !== "mysql" && provider !== "sqlite") return update;
|
|
195
|
+
return {
|
|
196
|
+
...update,
|
|
197
|
+
...(update.target_cohorts === undefined
|
|
198
|
+
? {}
|
|
199
|
+
: { target_cohorts: JSON.stringify(update.target_cohorts) }),
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export const fromStoredReleaseCatalogRow = (
|
|
204
|
+
row: StoredReleaseCatalogRow,
|
|
205
|
+
): ReleaseCatalogRow => ({
|
|
206
|
+
...row,
|
|
207
|
+
is_tombstone: parseStoredBoolean(row.is_tombstone, "is_tombstone"),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
export const escapeLikePattern = (value: string): string =>
|
|
211
|
+
value.replaceAll("\\", "\\\\").replaceAll("%", "\\%").replaceAll("_", "\\_");
|
|
212
|
+
|
|
213
|
+
export const escapeGlobPattern = (value: string): string =>
|
|
214
|
+
value.replaceAll("[", "[[]").replaceAll("*", "[*]").replaceAll("?", "[?]");
|
|
@@ -1,76 +1,237 @@
|
|
|
1
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
2
|
+
import {
|
|
3
|
+
boolean,
|
|
4
|
+
doublePrecision,
|
|
5
|
+
integer,
|
|
6
|
+
jsonb,
|
|
7
|
+
pgTable,
|
|
8
|
+
text,
|
|
9
|
+
varchar,
|
|
10
|
+
} from "drizzle-orm/pg-core";
|
|
11
|
+
import { drizzle } from "drizzle-orm/pglite";
|
|
1
12
|
import { describe, expect, it, vi } from "vitest";
|
|
2
13
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
14
|
+
import { setupDatabasePluginTestSuite } from "../../../test-utils/src/setupDatabasePluginTestSuite";
|
|
15
|
+
import type { DatabaseAdapterWithCapabilities } from "../db/types";
|
|
16
|
+
import {
|
|
17
|
+
DATABASE_PLUGIN_TEST_RESET_SQL,
|
|
18
|
+
DATABASE_PLUGIN_TEST_SCHEMA_SQL,
|
|
19
|
+
} from "./databasePluginTestDatabase";
|
|
5
20
|
import { drizzleAdapter } from "./drizzle";
|
|
6
21
|
|
|
7
|
-
|
|
8
|
-
|
|
22
|
+
const bundles = pgTable("bundles", {
|
|
23
|
+
id: text("id").primaryKey(),
|
|
24
|
+
platform: text("platform").notNull(),
|
|
25
|
+
file_hash: text("file_hash").notNull(),
|
|
26
|
+
git_commit_hash: text("git_commit_hash"),
|
|
27
|
+
storage_uri: text("storage_uri").notNull(),
|
|
28
|
+
archive_byte_size: doublePrecision("archive_byte_size").notNull(),
|
|
29
|
+
metadata: jsonb("metadata").notNull(),
|
|
30
|
+
manifest_storage_uri: text("manifest_storage_uri"),
|
|
31
|
+
manifest_file_hash: text("manifest_file_hash"),
|
|
32
|
+
asset_base_storage_uri: text("asset_base_storage_uri"),
|
|
33
|
+
});
|
|
34
|
+
const channels = pgTable("channels", {
|
|
35
|
+
id: text("id").primaryKey(),
|
|
36
|
+
name: text("name").notNull().unique(),
|
|
37
|
+
});
|
|
38
|
+
const releases = pgTable("releases", {
|
|
39
|
+
id: text("id").primaryKey(),
|
|
40
|
+
revision: integer("revision").notNull(),
|
|
41
|
+
scope_key: text("scope_key").notNull(),
|
|
42
|
+
channel_id: text("channel_id").notNull(),
|
|
43
|
+
platform: text("platform").notNull(),
|
|
44
|
+
kind: text("kind").notNull(),
|
|
45
|
+
bundle_id: text("bundle_id"),
|
|
46
|
+
strategy: text("strategy").notNull(),
|
|
47
|
+
target_app_version: text("target_app_version"),
|
|
48
|
+
fingerprint_hash: text("fingerprint_hash"),
|
|
49
|
+
enabled: boolean("enabled").notNull(),
|
|
50
|
+
should_force_update: boolean("should_force_update").notNull(),
|
|
51
|
+
message: text("message"),
|
|
52
|
+
rollout_cohort_count: integer("rollout_cohort_count").notNull(),
|
|
53
|
+
target_cohorts: jsonb("target_cohorts").notNull(),
|
|
54
|
+
operation: text("operation").notNull(),
|
|
55
|
+
source_release_id: text("source_release_id"),
|
|
56
|
+
created_at_ms: integer("created_at_ms").notNull(),
|
|
57
|
+
updated_at_ms: integer("updated_at_ms").notNull(),
|
|
58
|
+
});
|
|
59
|
+
const releaseCatalogs = pgTable("release_catalogs", {
|
|
60
|
+
scope_key: text("scope_key").primaryKey(),
|
|
61
|
+
catalog_id: text("catalog_id").notNull(),
|
|
62
|
+
strategy: text("strategy").notNull(),
|
|
63
|
+
channel_id: text("channel_id").notNull(),
|
|
64
|
+
channel_key: text("channel_key").notNull(),
|
|
65
|
+
platform: text("platform").notNull(),
|
|
66
|
+
fingerprint_hash: text("fingerprint_hash"),
|
|
67
|
+
generation: integer("generation").notNull(),
|
|
68
|
+
payload: text("payload").notNull(),
|
|
69
|
+
catalog_hash: text("catalog_hash").notNull(),
|
|
70
|
+
byte_size: integer("byte_size").notNull(),
|
|
71
|
+
is_tombstone: boolean("is_tombstone").notNull(),
|
|
72
|
+
updated_at_ms: integer("updated_at_ms").notNull(),
|
|
73
|
+
});
|
|
74
|
+
const bundlePatches = pgTable("bundle_patches", {
|
|
75
|
+
id: varchar("id", { length: 255 }).primaryKey(),
|
|
76
|
+
bundle_id: text("bundle_id").notNull(),
|
|
77
|
+
base_bundle_id: text("base_bundle_id").notNull(),
|
|
78
|
+
base_file_hash: text("base_file_hash").notNull(),
|
|
79
|
+
patch_file_hash: text("patch_file_hash").notNull(),
|
|
80
|
+
patch_storage_uri: text("patch_storage_uri").notNull(),
|
|
81
|
+
byte_size: doublePrecision("byte_size").notNull(),
|
|
82
|
+
order_index: integer("order_index").notNull(),
|
|
83
|
+
});
|
|
84
|
+
const bundleEvents = pgTable("bundle_events", {
|
|
85
|
+
id: text("id").primaryKey(),
|
|
86
|
+
type: text("type").notNull(),
|
|
87
|
+
install_id: text("install_id").notNull(),
|
|
88
|
+
user_id: text("user_id"),
|
|
89
|
+
username: text("username"),
|
|
90
|
+
from_release_id: text("from_release_id"),
|
|
91
|
+
from_bundle_id: text("from_bundle_id"),
|
|
92
|
+
to_release_id: text("to_release_id"),
|
|
93
|
+
to_bundle_id: text("to_bundle_id").notNull(),
|
|
94
|
+
platform: text("platform").notNull(),
|
|
95
|
+
app_version: text("app_version").notNull(),
|
|
96
|
+
channel: text("channel").notNull(),
|
|
97
|
+
cohort: text("cohort").notNull(),
|
|
98
|
+
update_strategy: text("update_strategy"),
|
|
99
|
+
fingerprint_hash: text("fingerprint_hash"),
|
|
100
|
+
sdk_version: text("sdk_version"),
|
|
101
|
+
received_at_ms: integer("received_at_ms").notNull(),
|
|
102
|
+
});
|
|
103
|
+
const apiKeys = pgTable("api_keys", {
|
|
104
|
+
id: text("id").primaryKey(),
|
|
105
|
+
hash: text("hash").notNull().unique(),
|
|
106
|
+
name: text("name").notNull(),
|
|
107
|
+
prefix: text("prefix").notNull(),
|
|
108
|
+
role: text("role").notNull(),
|
|
109
|
+
created_at_ms: integer("created_at_ms").notNull(),
|
|
110
|
+
revoked_at_ms: integer("revoked_at_ms"),
|
|
111
|
+
});
|
|
112
|
+
const schema = {
|
|
113
|
+
bundle_events: bundleEvents,
|
|
114
|
+
bundle_patches: bundlePatches,
|
|
115
|
+
bundles,
|
|
116
|
+
channels,
|
|
117
|
+
api_keys: apiKeys,
|
|
118
|
+
release_catalogs: releaseCatalogs,
|
|
119
|
+
releases,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
class DrizzleTestStateError extends Error {
|
|
123
|
+
readonly name = "DrizzleTestStateError";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let client: PGlite | undefined;
|
|
127
|
+
let database: ReturnType<typeof drizzle<typeof schema>> | undefined;
|
|
128
|
+
|
|
129
|
+
const getClient = (): PGlite => {
|
|
130
|
+
if (client === undefined) throw new DrizzleTestStateError();
|
|
131
|
+
return client;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const getDatabase = (): ReturnType<typeof drizzle<typeof schema>> => {
|
|
135
|
+
if (database === undefined) throw new DrizzleTestStateError();
|
|
136
|
+
return database;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
setupDatabasePluginTestSuite({
|
|
140
|
+
name: "drizzleAdapter PostgreSQL",
|
|
141
|
+
migrate: async () => {
|
|
142
|
+
client = new PGlite();
|
|
143
|
+
await client.exec(DATABASE_PLUGIN_TEST_SCHEMA_SQL);
|
|
144
|
+
database = drizzle(client, { schema });
|
|
145
|
+
},
|
|
146
|
+
createPlugin: (): DatabaseAdapterWithCapabilities =>
|
|
147
|
+
drizzleAdapter({ db: getDatabase(), provider: "postgresql" }),
|
|
148
|
+
reset: async () => {
|
|
149
|
+
await getClient().exec(DATABASE_PLUGIN_TEST_RESET_SQL);
|
|
150
|
+
},
|
|
151
|
+
dispose: async () => {
|
|
152
|
+
await getClient().close();
|
|
153
|
+
database = undefined;
|
|
154
|
+
client = undefined;
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("drizzleAdapter schema requirements", () => {
|
|
159
|
+
it("does not resolve a lazy database while generating a schema", () => {
|
|
9
160
|
const getDB = vi.fn(() => {
|
|
10
|
-
throw new
|
|
161
|
+
throw new DrizzleTestStateError();
|
|
11
162
|
});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
provider: "postgresql",
|
|
17
|
-
}),
|
|
163
|
+
const plugin = drizzleAdapter({
|
|
164
|
+
db: getDB,
|
|
165
|
+
provider: "postgresql",
|
|
166
|
+
schema,
|
|
18
167
|
});
|
|
19
168
|
|
|
20
|
-
const
|
|
169
|
+
const generated = plugin.generateSchema?.("latest");
|
|
21
170
|
|
|
22
|
-
expect(
|
|
23
|
-
expect(schema.code).toContain("pgTable");
|
|
171
|
+
expect(generated?.code).toContain("pgTable");
|
|
24
172
|
expect(getDB).not.toHaveBeenCalled();
|
|
25
173
|
});
|
|
26
174
|
|
|
27
|
-
it("resolves a lazy
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
$count: vi.fn(),
|
|
33
|
-
delete: vi.fn(),
|
|
34
|
-
insert: vi.fn(),
|
|
35
|
-
query: {
|
|
36
|
-
bundle_patches: {
|
|
37
|
-
findMany: vi.fn(),
|
|
38
|
-
},
|
|
39
|
-
bundles: {
|
|
40
|
-
findFirst: vi.fn(),
|
|
41
|
-
findMany: vi.fn(async () => [{ channel: "production" }]),
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
select: vi.fn(),
|
|
45
|
-
update: vi.fn(),
|
|
46
|
-
};
|
|
47
|
-
const getDB = vi.fn(async () => db);
|
|
48
|
-
|
|
175
|
+
it("resolves a lazy database on the first database operation", async () => {
|
|
176
|
+
const lazyClient = new PGlite();
|
|
177
|
+
await lazyClient.exec(DATABASE_PLUGIN_TEST_SCHEMA_SQL);
|
|
178
|
+
const lazyDatabase = drizzle(lazyClient, { schema });
|
|
179
|
+
const getDB = vi.fn(async () => lazyDatabase);
|
|
49
180
|
const plugin = drizzleAdapter({
|
|
50
181
|
db: getDB,
|
|
51
182
|
provider: "postgresql",
|
|
52
|
-
schema
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
expect(getDB).not.toHaveBeenCalled();
|
|
183
|
+
schema,
|
|
184
|
+
});
|
|
56
185
|
|
|
57
|
-
|
|
58
|
-
|
|
186
|
+
try {
|
|
187
|
+
expect(getDB).not.toHaveBeenCalled();
|
|
188
|
+
await expect(plugin.models.channels.list({})).resolves.toEqual({
|
|
189
|
+
channels: [],
|
|
190
|
+
});
|
|
191
|
+
expect(getDB).toHaveBeenCalledOnce();
|
|
192
|
+
} finally {
|
|
193
|
+
await lazyClient.close();
|
|
194
|
+
}
|
|
59
195
|
});
|
|
60
196
|
|
|
61
|
-
it("
|
|
197
|
+
it("rejects a lazy database without a schema on first use", () => {
|
|
62
198
|
const getDB = vi.fn(() => {
|
|
63
|
-
throw new
|
|
199
|
+
throw new DrizzleTestStateError();
|
|
64
200
|
});
|
|
201
|
+
const plugin = drizzleAdapter({ db: getDB, provider: "postgresql" });
|
|
202
|
+
|
|
203
|
+
expect(() => plugin.models.channels.list({})).toThrow(
|
|
204
|
+
"[hot-updater] Drizzle adapter requires schema when db is lazy.",
|
|
205
|
+
);
|
|
206
|
+
expect(getDB).not.toHaveBeenCalled();
|
|
207
|
+
});
|
|
65
208
|
|
|
209
|
+
it("rejects an invalid lazy database schema on first use", () => {
|
|
210
|
+
const getDB = vi.fn(() => {
|
|
211
|
+
throw new DrizzleTestStateError();
|
|
212
|
+
});
|
|
66
213
|
const plugin = drizzleAdapter({
|
|
67
214
|
db: getDB,
|
|
68
215
|
provider: "postgresql",
|
|
69
|
-
|
|
216
|
+
schema: { ...schema, bundles: null },
|
|
217
|
+
});
|
|
70
218
|
|
|
71
|
-
|
|
72
|
-
|
|
219
|
+
expect(() => plugin.models.channels.list({})).toThrow(
|
|
220
|
+
'[hot-updater] Drizzle schema table "bundles" is invalid.',
|
|
73
221
|
);
|
|
74
222
|
expect(getDB).not.toHaveBeenCalled();
|
|
75
223
|
});
|
|
224
|
+
|
|
225
|
+
it("requires all fixed table objects on first use", () => {
|
|
226
|
+
const incompleteSchema = { bundles };
|
|
227
|
+
const plugin = drizzleAdapter({
|
|
228
|
+
db: () => getDatabase(),
|
|
229
|
+
provider: "postgresql",
|
|
230
|
+
schema: incompleteSchema,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
expect(() => plugin.models.channels.list({})).toThrow(
|
|
234
|
+
'Drizzle schema is missing table "bundle_patches".',
|
|
235
|
+
);
|
|
236
|
+
});
|
|
76
237
|
});
|