@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.
Files changed (291) hide show
  1. package/dist/adapters/databaseConstraintErrors.cjs +23 -0
  2. package/dist/adapters/databaseConstraintErrors.mjs +21 -0
  3. package/dist/adapters/databasePluginUtils.cjs +117 -0
  4. package/dist/adapters/databasePluginUtils.mjs +106 -0
  5. package/dist/adapters/drizzle.cjs +53 -176
  6. package/dist/adapters/drizzle.d.cts +6 -4
  7. package/dist/adapters/drizzle.d.mts +6 -4
  8. package/dist/adapters/drizzle.mjs +54 -177
  9. package/dist/adapters/drizzleCrud.cjs +250 -0
  10. package/dist/adapters/drizzleCrud.mjs +249 -0
  11. package/dist/adapters/drizzleLazyDB.cjs +84 -36
  12. package/dist/adapters/drizzleLazyDB.mjs +84 -36
  13. package/dist/adapters/drizzleQuery.cjs +68 -0
  14. package/dist/adapters/drizzleQuery.mjs +67 -0
  15. package/dist/adapters/kysely.cjs +18 -138
  16. package/dist/adapters/kysely.d.cts +3 -12
  17. package/dist/adapters/kysely.d.mts +3 -12
  18. package/dist/adapters/kysely.mjs +19 -139
  19. package/dist/adapters/kyselyCrud.cjs +254 -0
  20. package/dist/adapters/kyselyCrud.mjs +253 -0
  21. package/dist/adapters/kyselyQuery.cjs +56 -0
  22. package/dist/adapters/kyselyQuery.mjs +55 -0
  23. package/dist/adapters/mongodb.cjs +21 -174
  24. package/dist/adapters/mongodb.d.cts +3 -2
  25. package/dist/adapters/mongodb.d.mts +3 -2
  26. package/dist/adapters/mongodb.mjs +21 -174
  27. package/dist/adapters/mongodbCollections.cjs +36 -0
  28. package/dist/adapters/mongodbCollections.mjs +30 -0
  29. package/dist/adapters/mongodbQuery.cjs +77 -0
  30. package/dist/adapters/mongodbQuery.mjs +70 -0
  31. package/dist/adapters/mongodbReads.cjs +148 -0
  32. package/dist/adapters/mongodbReads.mjs +147 -0
  33. package/dist/adapters/mongodbWrites.cjs +134 -0
  34. package/dist/adapters/mongodbWrites.mjs +133 -0
  35. package/dist/adapters/prisma.cjs +205 -180
  36. package/dist/adapters/prisma.d.cts +3 -3
  37. package/dist/adapters/prisma.d.mts +3 -3
  38. package/dist/adapters/prisma.mjs +206 -181
  39. package/dist/adapters/prismaQuery.cjs +58 -0
  40. package/dist/adapters/prismaQuery.mjs +56 -0
  41. package/dist/adapters/prismaRows.cjs +198 -0
  42. package/dist/adapters/prismaRows.mjs +188 -0
  43. package/dist/analytics/bounded/activeOverview.cjs +87 -0
  44. package/dist/analytics/bounded/activeOverview.mjs +85 -0
  45. package/dist/analytics/bounded/installationSearch.cjs +42 -0
  46. package/dist/analytics/bounded/installationSearch.mjs +42 -0
  47. package/dist/analytics/bounded/persistence.cjs +39 -0
  48. package/dist/analytics/bounded/persistence.mjs +38 -0
  49. package/dist/analytics/bounded/provider.cjs +170 -0
  50. package/dist/analytics/bounded/provider.d.cts +7 -0
  51. package/dist/analytics/bounded/provider.d.mts +7 -0
  52. package/dist/analytics/bounded/provider.mjs +170 -0
  53. package/dist/analytics/bounded/scan.cjs +144 -0
  54. package/dist/analytics/bounded/scan.mjs +138 -0
  55. package/dist/analytics/domain.d.cts +121 -0
  56. package/dist/analytics/domain.d.mts +121 -0
  57. package/dist/analytics/errors.cjs +22 -0
  58. package/dist/analytics/errors.mjs +20 -0
  59. package/dist/analytics/eventInput.cjs +111 -0
  60. package/dist/analytics/eventInput.mjs +111 -0
  61. package/dist/analytics/persistence.d.cts +6 -0
  62. package/dist/analytics/persistence.d.mts +6 -0
  63. package/dist/analytics/queryInput.cjs +58 -0
  64. package/dist/analytics/queryInput.mjs +55 -0
  65. package/dist/analytics/routes.cjs +63 -0
  66. package/dist/analytics/routes.mjs +61 -0
  67. package/dist/analytics/types.d.cts +17 -0
  68. package/dist/analytics/types.d.mts +17 -0
  69. package/dist/apiKeys.cjs +139 -0
  70. package/dist/apiKeys.d.cts +50 -0
  71. package/dist/apiKeys.d.mts +50 -0
  72. package/dist/apiKeys.mjs +133 -0
  73. package/dist/createHotUpdaterCore.cjs +48 -26
  74. package/dist/createHotUpdaterCore.d.cts +49 -17
  75. package/dist/createHotUpdaterCore.d.mts +49 -17
  76. package/dist/createHotUpdaterCore.mjs +51 -29
  77. package/dist/db/createBundleDiff.cjs +45 -68
  78. package/dist/db/createBundleDiff.d.cts +3 -3
  79. package/dist/db/createBundleDiff.d.mts +3 -3
  80. package/dist/db/createBundleDiff.mjs +47 -68
  81. package/dist/db/databasePluginCore.cjs +138 -0
  82. package/dist/db/databasePluginCore.mjs +137 -0
  83. package/dist/db/fixedMigrator.cjs +36 -93
  84. package/dist/db/fixedMigrator.mjs +36 -93
  85. package/dist/db/fixedMigratorMongo.cjs +319 -0
  86. package/dist/db/fixedMigratorMongo.mjs +319 -0
  87. package/dist/db/fixedMigratorShared.cjs +23 -0
  88. package/dist/db/fixedMigratorShared.mjs +18 -0
  89. package/dist/db/mongoMigrationExecution.cjs +11 -0
  90. package/dist/db/mongoMigrationExecution.mjs +11 -0
  91. package/dist/db/releaseCatalog.cjs +66 -0
  92. package/dist/db/releaseCatalog.d.cts +17 -0
  93. package/dist/db/releaseCatalog.d.mts +17 -0
  94. package/dist/db/releaseCatalog.mjs +64 -0
  95. package/dist/db/schema/mongodb.cjs +14 -6
  96. package/dist/db/schema/mongodb.mjs +14 -6
  97. package/dist/db/schema/registry.cjs +0 -2
  98. package/dist/db/schema/registry.mjs +1 -2
  99. package/dist/db/schema/sql.cjs +31 -14
  100. package/dist/db/schema/sql.mjs +32 -13
  101. package/dist/db/schema/sqlOperations.cjs +4 -4
  102. package/dist/db/schema/sqlOperations.mjs +5 -5
  103. package/dist/db/schemaGenerators.cjs +52 -18
  104. package/dist/db/schemaGenerators.mjs +52 -18
  105. package/dist/db/schemaReadiness.cjs +4 -3
  106. package/dist/db/schemaReadiness.mjs +4 -3
  107. package/dist/db/sqlMigrationExecution.cjs +67 -0
  108. package/dist/db/sqlMigrationExecution.mjs +66 -0
  109. package/dist/db/types.cjs +2 -6
  110. package/dist/db/types.d.cts +60 -16
  111. package/dist/db/types.d.mts +60 -16
  112. package/dist/db/types.mjs +3 -6
  113. package/dist/db/updateArtifacts.cjs +114 -60
  114. package/dist/db/updateArtifacts.mjs +117 -62
  115. package/dist/handler.cjs +108 -282
  116. package/dist/handler.d.cts +3 -46
  117. package/dist/handler.d.mts +3 -46
  118. package/dist/handler.mjs +107 -281
  119. package/dist/handlerBundleRoutes.cjs +155 -0
  120. package/dist/handlerBundleRoutes.mjs +155 -0
  121. package/dist/handlerErrors.cjs +9 -0
  122. package/dist/handlerErrors.mjs +9 -0
  123. package/dist/handlerParameters.cjs +39 -0
  124. package/dist/handlerParameters.mjs +34 -0
  125. package/dist/handlerReleaseCatalogRoutes.cjs +111 -0
  126. package/dist/handlerReleaseCatalogRoutes.mjs +110 -0
  127. package/dist/handlerReleaseManagementRoutes.cjs +125 -0
  128. package/dist/handlerReleaseManagementRoutes.mjs +124 -0
  129. package/dist/handlerTypes.d.cts +63 -0
  130. package/dist/handlerTypes.d.mts +63 -0
  131. package/dist/handlerVersionRoutes.cjs +10 -0
  132. package/dist/handlerVersionRoutes.d.cts +4 -0
  133. package/dist/handlerVersionRoutes.d.mts +4 -0
  134. package/dist/handlerVersionRoutes.mjs +9 -0
  135. package/dist/index.cjs +10 -1
  136. package/dist/index.d.cts +9 -3
  137. package/dist/index.d.mts +9 -3
  138. package/dist/index.mjs +5 -2
  139. package/dist/node.cjs +19 -12
  140. package/dist/node.d.cts +15 -10
  141. package/dist/node.d.mts +15 -10
  142. package/dist/node.mjs +19 -13
  143. package/dist/package.cjs +1 -1
  144. package/dist/package.mjs +1 -1
  145. package/dist/schema/dsl.cjs +19 -2
  146. package/dist/schema/dsl.mjs +17 -3
  147. package/dist/schema/index.cjs +1 -8
  148. package/dist/schema/index.mjs +2 -8
  149. package/dist/schema/types.cjs +3 -1
  150. package/dist/schema/types.mjs +3 -2
  151. package/dist/schema/v1_0_0.cjs +364 -0
  152. package/dist/schema/v1_0_0.mjs +357 -0
  153. package/dist/storageAccess.cjs +69 -25
  154. package/dist/storageAccess.mjs +68 -25
  155. package/dist/types/index.d.cts +4 -3
  156. package/dist/types/index.d.mts +5 -4
  157. package/package.json +8 -7
  158. package/src/adapters/databaseConstraintErrors.ts +25 -0
  159. package/src/adapters/databasePluginTestDatabase.ts +106 -0
  160. package/src/adapters/databasePluginUtils.spec.ts +29 -0
  161. package/src/adapters/databasePluginUtils.ts +214 -0
  162. package/src/adapters/drizzle.spec.ts +208 -47
  163. package/src/adapters/drizzle.ts +106 -293
  164. package/src/adapters/drizzleCrud.ts +419 -0
  165. package/src/adapters/drizzleLazyDB.ts +206 -100
  166. package/src/adapters/drizzleQuery.ts +164 -0
  167. package/src/adapters/kysely.spec.ts +126 -88
  168. package/src/adapters/kysely.ts +76 -334
  169. package/src/adapters/kyselyCrud.ts +588 -0
  170. package/src/adapters/kyselyQuery.ts +128 -0
  171. package/src/adapters/mongodb.spec.ts +141 -0
  172. package/src/adapters/mongodb.ts +60 -288
  173. package/src/adapters/mongodbCollections.ts +62 -0
  174. package/src/adapters/mongodbQuery.ts +226 -0
  175. package/src/adapters/mongodbReads.ts +311 -0
  176. package/src/adapters/mongodbRows.spec.ts +64 -0
  177. package/src/adapters/mongodbRows.ts +118 -0
  178. package/src/adapters/mongodbTestClient.ts +342 -0
  179. package/src/adapters/mongodbTestFilter.ts +166 -0
  180. package/src/adapters/mongodbWrites.ts +260 -0
  181. package/src/adapters/prisma.spec.ts +196 -0
  182. package/src/adapters/prisma.ts +417 -292
  183. package/src/adapters/prismaQuery.ts +129 -0
  184. package/src/adapters/prismaRows.spec.ts +98 -0
  185. package/src/adapters/prismaRows.ts +301 -0
  186. package/src/adapters/prismaTestClient.ts +396 -0
  187. package/src/analytics/analytics.integration.spec.ts +144 -0
  188. package/src/analytics/bounded/activeOverview.spec.ts +189 -0
  189. package/src/analytics/bounded/activeOverview.ts +148 -0
  190. package/src/analytics/bounded/installationSearch.spec.ts +130 -0
  191. package/src/analytics/bounded/installationSearch.ts +69 -0
  192. package/src/analytics/bounded/persistence.ts +43 -0
  193. package/src/analytics/bounded/provider.spec.ts +219 -0
  194. package/src/analytics/bounded/provider.ts +229 -0
  195. package/src/analytics/bounded/scan.ts +235 -0
  196. package/src/analytics/domain.ts +139 -0
  197. package/src/analytics/errors.ts +18 -0
  198. package/src/analytics/eventInput.ts +168 -0
  199. package/src/analytics/persistence.ts +10 -0
  200. package/src/analytics/queryInput.ts +102 -0
  201. package/src/analytics/routes.ts +123 -0
  202. package/src/analytics/types.ts +44 -0
  203. package/src/apiKeys.integration.spec.ts +192 -0
  204. package/src/apiKeys.spec.ts +162 -0
  205. package/src/apiKeys.ts +220 -0
  206. package/src/createHotUpdaterCore.ts +164 -100
  207. package/src/db/bundleRows.spec.ts +164 -0
  208. package/src/db/bundleRows.ts +10 -140
  209. package/src/db/createBundleDiff.spec.ts +172 -127
  210. package/src/db/createBundleDiff.ts +91 -110
  211. package/src/db/databasePluginCore.spec.ts +62 -0
  212. package/src/db/databasePluginCore.testFixtures.ts +76 -0
  213. package/src/db/databasePluginCore.ts +185 -0
  214. package/src/db/databasePluginUpdateCheck.bench.ts +107 -0
  215. package/src/db/fixedMigrator.spec.ts +184 -42
  216. package/src/db/fixedMigrator.ts +64 -205
  217. package/src/db/fixedMigratorMongo.spec.ts +192 -0
  218. package/src/db/fixedMigratorMongo.ts +425 -0
  219. package/src/db/fixedMigratorShared.ts +36 -0
  220. package/src/db/hotUpdaterSchema.ts +0 -1
  221. package/src/db/index.spec.ts +333 -1025
  222. package/src/db/mongoMigrationExecution.ts +23 -0
  223. package/src/db/node-sqlite.d.ts +25 -0
  224. package/src/db/releaseCatalog.ts +148 -0
  225. package/src/db/schema/mongodb.ts +20 -6
  226. package/src/db/schema/sql.ts +52 -11
  227. package/src/db/schema/sqlOperations.ts +5 -4
  228. package/src/db/schemaGenerators.ts +107 -23
  229. package/src/db/schemaReadiness.ts +2 -1
  230. package/src/db/sqlMigrationExecution.spec.ts +65 -0
  231. package/src/db/sqlMigrationExecution.ts +108 -0
  232. package/src/db/types.spec.ts +55 -0
  233. package/src/db/types.ts +129 -65
  234. package/src/db/updateArtifacts.spec.ts +359 -0
  235. package/src/db/updateArtifacts.ts +290 -126
  236. package/src/handler-standalone.integration.spec.ts +401 -364
  237. package/src/handler.spec.ts +137 -582
  238. package/src/handler.testFixtures.ts +30 -0
  239. package/src/handler.ts +218 -616
  240. package/src/handlerAuth.integration.spec.ts +33 -0
  241. package/src/handlerBundleRoutes.ts +259 -0
  242. package/src/handlerErrors.ts +7 -0
  243. package/src/handlerManagement.spec.ts +262 -0
  244. package/src/handlerParameters.ts +105 -0
  245. package/src/handlerReleaseCatalogRoutes.ts +154 -0
  246. package/src/handlerReleaseManagementRoutes.ts +213 -0
  247. package/src/handlerTypes.ts +96 -0
  248. package/src/handlerVersionRoutes.ts +12 -0
  249. package/src/index.ts +22 -2
  250. package/src/node.spec.ts +79 -10
  251. package/src/node.ts +20 -17
  252. package/src/releaseCatalogRoutes.integration.spec.ts +238 -0
  253. package/src/runtime.spec.ts +126 -959
  254. package/src/runtime.testFixtures.ts +71 -0
  255. package/src/schema/dsl-all-versions.spec.ts +26 -17
  256. package/src/schema/dsl.ts +41 -2
  257. package/src/schema/index.ts +3 -9
  258. package/src/schema/types.ts +9 -3
  259. package/src/schema/v1_0_0.spec.ts +146 -0
  260. package/src/schema/v1_0_0.ts +465 -0
  261. package/src/storageAccess.spec.ts +127 -32
  262. package/src/storageAccess.ts +106 -55
  263. package/src/types/index.ts +2 -1
  264. package/dist/db/bundleRows.cjs +0 -82
  265. package/dist/db/bundleRows.d.cts +0 -33
  266. package/dist/db/bundleRows.d.mts +0 -33
  267. package/dist/db/bundleRows.mjs +0 -79
  268. package/dist/db/pluginCore.cjs +0 -242
  269. package/dist/db/pluginCore.mjs +0 -241
  270. package/dist/db/schema/sqlMigrations.cjs +0 -74
  271. package/dist/db/schema/sqlMigrations.mjs +0 -73
  272. package/dist/db/schemaEnhancements.cjs +0 -20
  273. package/dist/db/schemaEnhancements.mjs +0 -19
  274. package/dist/route.cjs +0 -7
  275. package/dist/route.mjs +0 -7
  276. package/dist/schema/v0_21_0.cjs +0 -38
  277. package/dist/schema/v0_21_0.mjs +0 -37
  278. package/dist/schema/v0_29_0.cjs +0 -45
  279. package/dist/schema/v0_29_0.mjs +0 -44
  280. package/dist/schema/v0_31_0.cjs +0 -82
  281. package/dist/schema/v0_31_0.mjs +0 -80
  282. package/src/db/pluginCore.spec.ts +0 -1095
  283. package/src/db/pluginCore.ts +0 -475
  284. package/src/db/pluginUpdateCheck.bench.ts +0 -254
  285. package/src/db/schema/sqlMigrations.ts +0 -219
  286. package/src/db/schemaEnhancements.ts +0 -55
  287. package/src/route.ts +0 -7
  288. package/src/schema/v0_21_0.ts +0 -52
  289. package/src/schema/v0_29_0.ts +0 -62
  290. package/src/schema/v0_31_0.spec.ts +0 -73
  291. package/src/schema/v0_31_0.ts +0 -120
@@ -1,49 +1,31 @@
1
- import type { Kysely } from "kysely";
2
- import type { MongoClient } from "mongodb";
1
+ import { sql, type QueryExecutorProvider } from "kysely";
3
2
 
4
3
  import {
4
+ HOT_UPDATER_CORE_SCHEMA_KEY,
5
5
  HOT_UPDATER_SCHEMA_VERSION,
6
6
  HOT_UPDATER_SETTINGS_TABLE,
7
7
  } from "../schema/types";
8
- import { createMongoMigrationOperations } from "./schema/mongodb";
9
8
  import {
10
- hotUpdaterSchema,
11
- schemaIndexAppliesToProvider,
12
- } from "./schema/registry";
9
+ assertCurrentOrEmptySchemaVersion,
10
+ assertSupportedMigrationMode,
11
+ getEmptyMigrationResult,
12
+ inferLegacyCoreSchemaVersion,
13
+ isCurrentSchemaVersion,
14
+ } from "./fixedMigratorShared";
13
15
  import { createTableSql } from "./schema/sql";
14
- import { createV029AlterSql, createV031AlterSql } from "./schema/sqlMigrations";
15
16
  import {
16
17
  createSqlCreateOperations,
17
18
  getSettingsInsertSql,
18
19
  } from "./schema/sqlOperations";
20
+ import { executeMigrationStatements } from "./sqlMigrationExecution";
19
21
  import type {
20
22
  MigrateOptions,
21
- MigrationOperation,
22
23
  MigrationResult,
23
24
  Migrator,
24
25
  ORMSQLProvider,
25
26
  RelationMode,
26
27
  } from "./types";
27
28
 
28
- interface SettingsDatabase {
29
- readonly private_hot_updater_settings: {
30
- readonly key: string;
31
- readonly value: string;
32
- };
33
- }
34
-
35
- const getEmptyResult = (): MigrationResult => ({
36
- operations: [],
37
- execute: async () => {},
38
- getSQL: () => "",
39
- });
40
-
41
- const assertSupportedMigrationMode = (options: MigrateOptions): void => {
42
- if (options.mode === "from-database") {
43
- throw new Error("Hot Updater migrations support only mode: 'from-schema'.");
44
- }
45
- };
46
-
47
29
  const isMissingSettingsTableError = (error: unknown): boolean => {
48
30
  if (!(error instanceof Error)) return false;
49
31
  const message = error.message.toLowerCase();
@@ -56,222 +38,97 @@ const isMissingSettingsTableError = (error: unknown): boolean => {
56
38
  );
57
39
  };
58
40
 
59
- const isMongoNamespaceExistsError = (error: unknown): boolean => {
60
- if (!(error instanceof Error)) return false;
61
- const mongoError = error as { code?: unknown; codeName?: unknown };
62
- return mongoError.code === 48 || mongoError.codeName === "NamespaceExists";
63
- };
64
-
65
- const ignoreExistingCollection = (error: unknown): undefined => {
66
- if (isMongoNamespaceExistsError(error)) {
67
- return undefined;
68
- }
69
- throw error;
70
- };
71
-
72
- const toCustomOperations = (
73
- statements: readonly string[],
74
- settingsOperation?: MigrationOperation,
75
- ): MigrationOperation[] => [
76
- ...statements.map(
77
- (statement): MigrationOperation => ({
78
- type: "custom",
79
- sql: statement,
80
- }),
81
- ),
82
- ...(settingsOperation ? [settingsOperation] : []),
83
- ];
84
-
85
- const assertSupportedSchemaVersion = (
86
- currentVersion: string | undefined,
87
- ): void => {
88
- if (
89
- currentVersion !== undefined &&
90
- currentVersion !== "0.21.0" &&
91
- currentVersion !== "0.29.0"
92
- ) {
93
- throw new Error(
94
- `Unsupported Hot Updater schema version: ${currentVersion}`,
95
- );
96
- }
97
- };
98
-
99
41
  export const createKyselyMigrator = ({
100
42
  db,
101
43
  provider,
102
44
  relationMode = "foreign-keys",
103
45
  }: {
104
- db: Kysely<SettingsDatabase>;
46
+ db: QueryExecutorProvider;
105
47
  provider: ORMSQLProvider;
106
48
  relationMode?: RelationMode;
107
49
  }): Migrator => {
108
- const getVersion = async (): Promise<string | undefined> => {
50
+ const getSetting = async (key: string): Promise<string | undefined> => {
109
51
  try {
110
- const row = await db
111
- .selectFrom(HOT_UPDATER_SETTINGS_TABLE)
112
- .select("value")
113
- .where("key", "=", "version")
114
- .executeTakeFirst();
115
- return typeof row?.value === "string" ? row.value : undefined;
52
+ const result = await sql<{ readonly value: unknown }>`select ${sql.ref(
53
+ "value",
54
+ )} from ${sql.table(HOT_UPDATER_SETTINGS_TABLE)} where ${sql.ref(
55
+ "key",
56
+ )} = ${key} limit 1`.execute(db);
57
+ const row = result.rows[0];
58
+ if (!row) return undefined;
59
+ if (typeof row.value !== "string") {
60
+ throw new Error(`Invalid Hot Updater schema setting: ${key}`);
61
+ }
62
+ return row.value;
116
63
  } catch (error) {
117
64
  if (!isMissingSettingsTableError(error)) throw error;
118
65
  return undefined;
119
66
  }
120
67
  };
121
68
 
69
+ const getCoreVersion = (): Promise<string | undefined> =>
70
+ getSetting(HOT_UPDATER_CORE_SCHEMA_KEY);
71
+
72
+ const getSchemaVersions = async (): Promise<{
73
+ readonly coreVersion: string | undefined;
74
+ readonly legacyCoreVersion: string | undefined;
75
+ }> => {
76
+ const coreVersion = await getCoreVersion();
77
+ const legacyCoreVersion = inferLegacyCoreSchemaVersion(
78
+ await getSetting("version"),
79
+ );
80
+ return { coreVersion, legacyCoreVersion };
81
+ };
82
+
83
+ const getVersion = async (): Promise<string | undefined> => {
84
+ const { coreVersion, legacyCoreVersion } = await getSchemaVersions();
85
+ return coreVersion ?? legacyCoreVersion;
86
+ };
87
+
122
88
  const makeResult = async (
123
89
  options: MigrateOptions = {},
124
90
  ): Promise<MigrationResult> => {
125
91
  assertSupportedMigrationMode(options);
126
92
 
127
- const currentVersion = await getVersion();
128
- if (currentVersion === HOT_UPDATER_SCHEMA_VERSION) {
129
- return getEmptyResult();
93
+ const { coreVersion, legacyCoreVersion } = await getSchemaVersions();
94
+ if (isCurrentSchemaVersion(coreVersion)) {
95
+ return getEmptyMigrationResult();
130
96
  }
131
- assertSupportedSchemaVersion(currentVersion);
97
+ assertCurrentOrEmptySchemaVersion(coreVersion ?? legacyCoreVersion);
132
98
 
133
99
  const settingsStatement = getSettingsInsertSql(provider);
134
100
  const settingsOperation =
135
101
  options.updateSettings === false
136
102
  ? undefined
137
- : ({
138
- type: "custom",
103
+ : {
104
+ type: "custom" as const,
139
105
  sql: settingsStatement,
140
- } satisfies MigrationOperation);
141
- const executableSettingsStatements =
142
- options.updateSettings === false ? [] : [settingsStatement];
143
- const statements =
144
- currentVersion === undefined
145
- ? [...createTableSql(provider, relationMode), settingsStatement]
146
- : [
147
- ...(currentVersion === "0.21.0"
148
- ? createV029AlterSql(provider)
149
- : []),
150
- ...(currentVersion === "0.21.0" || currentVersion === "0.29.0"
151
- ? createV031AlterSql(provider, relationMode)
152
- : []),
153
- ...executableSettingsStatements,
154
- ];
155
- const operations =
156
- currentVersion === undefined
157
- ? createSqlCreateOperations(provider, relationMode, settingsOperation)
158
- : toCustomOperations(
159
- [
160
- ...(currentVersion === "0.21.0"
161
- ? createV029AlterSql(provider)
162
- : []),
163
- ...(currentVersion === "0.21.0" || currentVersion === "0.29.0"
164
- ? createV031AlterSql(provider, relationMode)
165
- : []),
166
- ],
167
- settingsOperation,
168
- );
106
+ };
107
+ const statements = [
108
+ ...createTableSql(provider, relationMode),
109
+ settingsStatement,
110
+ ];
169
111
 
170
112
  return {
171
- operations,
113
+ operations: createSqlCreateOperations(
114
+ provider,
115
+ relationMode,
116
+ settingsOperation,
117
+ ),
172
118
  getSQL: () => statements.map((statement) => `${statement};`).join("\n\n"),
173
- execute: async () => {
174
- const { sql } = await import("kysely");
175
- for (const statement of statements) {
176
- await sql.raw(statement).execute(db);
177
- }
178
- },
119
+ execute: () => executeMigrationStatements({ db, provider, statements }),
179
120
  };
180
121
  };
181
122
 
182
123
  return {
183
124
  getVersion,
184
125
  getNameVariants: async () => undefined,
185
- next: async () =>
186
- (await getVersion()) === HOT_UPDATER_SCHEMA_VERSION
126
+ next: async () => {
127
+ const { coreVersion } = await getSchemaVersions();
128
+ return isCurrentSchemaVersion(coreVersion)
187
129
  ? undefined
188
- : { version: HOT_UPDATER_SCHEMA_VERSION },
189
- previous: async () => undefined,
190
- up: makeResult,
191
- down: async () => {
192
- throw new Error("No previous schema to migrate to.");
130
+ : { version: HOT_UPDATER_SCHEMA_VERSION };
193
131
  },
194
- migrateTo: async (version, options) => {
195
- if (version !== HOT_UPDATER_SCHEMA_VERSION) {
196
- throw new Error(`Invalid version ${version}`);
197
- }
198
- return makeResult(options);
199
- },
200
- migrateToLatest: makeResult,
201
- };
202
- };
203
-
204
- export const createMongoMigrator = (client: MongoClient): Migrator => {
205
- const settings = client
206
- .db()
207
- .collection<{ key: string; value: unknown }>(HOT_UPDATER_SETTINGS_TABLE);
208
- const getVersion = async (): Promise<string | undefined> => {
209
- const row = await settings.findOne({ key: "version" });
210
- return typeof row?.value === "string" ? row.value : undefined;
211
- };
212
- const makeResult = async (
213
- options: MigrateOptions = {},
214
- ): Promise<MigrationResult> => {
215
- assertSupportedMigrationMode(options);
216
-
217
- if ((await getVersion()) === HOT_UPDATER_SCHEMA_VERSION) {
218
- return getEmptyResult();
219
- }
220
- const settingsOperation =
221
- options.updateSettings === false
222
- ? undefined
223
- : ({
224
- type: "custom",
225
- key: "version",
226
- value: HOT_UPDATER_SCHEMA_VERSION,
227
- } satisfies MigrationOperation);
228
- return {
229
- operations: createMongoMigrationOperations(settingsOperation),
230
- execute: async () => {
231
- const db = client.db();
232
- for (const table of hotUpdaterSchema.tables) {
233
- if (table.internal) continue;
234
- await db
235
- .createCollection(table.ormName)
236
- .catch(ignoreExistingCollection);
237
- }
238
- await db.collection("bundles").createIndex(
239
- { id: 1 },
240
- {
241
- name: "bundles_id_idx",
242
- },
243
- );
244
- for (const table of hotUpdaterSchema.tables) {
245
- if (table.internal) continue;
246
- for (const index of (table.indexes ?? []).filter((item) =>
247
- schemaIndexAppliesToProvider(item, "mongodb"),
248
- )) {
249
- await db
250
- .collection(table.ormName)
251
- .createIndex(
252
- Object.fromEntries(index.columns.map((column) => [column, 1])),
253
- { name: index.name },
254
- );
255
- }
256
- }
257
- if (options.updateSettings !== false) {
258
- await settings.updateOne(
259
- { key: "version" },
260
- { $set: { value: HOT_UPDATER_SCHEMA_VERSION } },
261
- { upsert: true },
262
- );
263
- }
264
- },
265
- };
266
- };
267
-
268
- return {
269
- getVersion,
270
- getNameVariants: async () => undefined,
271
- next: async () =>
272
- (await getVersion()) === HOT_UPDATER_SCHEMA_VERSION
273
- ? undefined
274
- : { version: HOT_UPDATER_SCHEMA_VERSION },
275
132
  previous: async () => undefined,
276
133
  up: makeResult,
277
134
  down: async () => {
@@ -286,3 +143,5 @@ export const createMongoMigrator = (client: MongoClient): Migrator => {
286
143
  migrateToLatest: makeResult,
287
144
  };
288
145
  };
146
+
147
+ export { createMongoMigrator } from "./fixedMigratorMongo";
@@ -0,0 +1,192 @@
1
+ import { MongoClient } from "mongodb";
2
+ import { describe, expect, it, vi } from "vitest";
3
+
4
+ import { createInMemoryDatabasePlugin } from "../../../test-utils/test/inMemoryDatabasePlugin";
5
+ import { createDatabasePluginCore } from "./databasePluginCore";
6
+ import { createMongoMigrator } from "./fixedMigrator";
7
+ import { createSchemaReadinessChecker } from "./schemaReadiness";
8
+
9
+ function createSettingsMongoClient(
10
+ values: Readonly<Record<string, unknown>>,
11
+ ): MongoClient {
12
+ return {
13
+ db: () => ({
14
+ collection: () => ({
15
+ find: ({ key }: { readonly key: string }) => ({
16
+ limit: () => ({
17
+ toArray: async () => {
18
+ const value = values[key];
19
+ return value === undefined ? [] : [{ key, value }];
20
+ },
21
+ }),
22
+ }),
23
+ listIndexes: () => ({
24
+ toArray: async () => [{ key: { key: 1 }, unique: true }],
25
+ }),
26
+ }),
27
+ }),
28
+ } as unknown as MongoClient;
29
+ }
30
+
31
+ describe("MongoDB migration", () => {
32
+ it("creates schema 1.0.0 from an empty database", async () => {
33
+ const settings = new Map<string, unknown>();
34
+ const settingsCollection = {
35
+ find: ({ key }: { readonly key: string }) => ({
36
+ limit: () => ({
37
+ toArray: async () => {
38
+ const value = settings.get(key);
39
+ return value === undefined ? [] : [{ key, value }];
40
+ },
41
+ }),
42
+ }),
43
+ createIndex: vi.fn(async (): Promise<string> => "key_1"),
44
+ listIndexes: () => ({ toArray: async () => [] }),
45
+ updateOne: async (
46
+ { key }: { readonly key: string },
47
+ update: { readonly $set: { readonly value: unknown } },
48
+ ) => void settings.set(key, update.$set.value),
49
+ };
50
+ const modelCollection = {
51
+ find: () => ({ toArray: async () => [] }),
52
+ listIndexes: () => ({ toArray: async () => [] }),
53
+ createIndex: async () => "created",
54
+ };
55
+ const command = vi.fn(
56
+ async (_input: Record<string, unknown>): Promise<void> => undefined,
57
+ );
58
+ const database = {
59
+ command,
60
+ createCollection: async () => undefined,
61
+ collection: (name: string) =>
62
+ name === "private_hot_updater_settings"
63
+ ? settingsCollection
64
+ : modelCollection,
65
+ };
66
+ const client = { db: () => database } as unknown as MongoClient;
67
+
68
+ await (
69
+ await createMongoMigrator(client).migrateToLatest({
70
+ mode: "from-schema",
71
+ updateSettings: true,
72
+ })
73
+ ).execute();
74
+
75
+ expect(settingsCollection.createIndex).toHaveBeenCalledWith(
76
+ { key: 1 },
77
+ { unique: true },
78
+ );
79
+ expect(settings.get("schema.core")).toBe("1.0.0");
80
+ const commands = command.mock.calls.map(([input]) => input);
81
+ expect(commands.find(({ collMod }) => collMod === "bundles")).toMatchObject(
82
+ {
83
+ validationAction: "error",
84
+ validationLevel: "strict",
85
+ validator: {
86
+ $and: [
87
+ {
88
+ $jsonSchema: {
89
+ properties: {
90
+ archive_byte_size: {
91
+ bsonType: ["double", "int", "long"],
92
+ maximum: Number.MAX_SAFE_INTEGER,
93
+ minimum: 0,
94
+ },
95
+ },
96
+ required: expect.arrayContaining(["archive_byte_size"]),
97
+ },
98
+ },
99
+ {
100
+ $expr: {
101
+ $eq: [{ $trunc: "$archive_byte_size" }, "$archive_byte_size"],
102
+ },
103
+ },
104
+ ],
105
+ },
106
+ },
107
+ );
108
+ expect(
109
+ commands.find(({ collMod }) => collMod === "bundle_patches"),
110
+ ).toMatchObject({
111
+ validationAction: "error",
112
+ validationLevel: "strict",
113
+ validator: {
114
+ $and: [
115
+ {
116
+ $jsonSchema: {
117
+ properties: {
118
+ byte_size: {
119
+ bsonType: ["double", "int", "long"],
120
+ maximum: Number.MAX_SAFE_INTEGER,
121
+ minimum: 0,
122
+ },
123
+ },
124
+ required: expect.arrayContaining(["byte_size"]),
125
+ },
126
+ },
127
+ {
128
+ $expr: {
129
+ $and: expect.arrayContaining([
130
+ {
131
+ $eq: [{ $trunc: "$byte_size" }, "$byte_size"],
132
+ },
133
+ ]),
134
+ },
135
+ },
136
+ ],
137
+ },
138
+ });
139
+ });
140
+
141
+ it("is a no-op when schema.core is already 1.0.0", async () => {
142
+ const client = createSettingsMongoClient({ "schema.core": "1.0.0" });
143
+ const migrator = createMongoMigrator(client);
144
+
145
+ await expect(migrator.getVersion()).resolves.toBe("1.0.0");
146
+ await expect(
147
+ migrator.migrateToLatest({ mode: "from-schema" }),
148
+ ).resolves.toMatchObject({ operations: [] });
149
+ });
150
+
151
+ it("ignores a leftover version marker when schema.core is 1.0.0", async () => {
152
+ const client = createSettingsMongoClient({
153
+ "schema.core": "1.0.0",
154
+ version: "0.36.0",
155
+ });
156
+ const migrator = createMongoMigrator(client);
157
+
158
+ await expect(migrator.getVersion()).resolves.toBe("1.0.0");
159
+ await expect(
160
+ migrator.migrateToLatest({ mode: "from-schema" }),
161
+ ).resolves.toMatchObject({ operations: [] });
162
+ });
163
+
164
+ it("rejects in-place upgrade from a v0 schema marker", async () => {
165
+ const client = createSettingsMongoClient({ "schema.core": "0.38.0" });
166
+ const migrator = createMongoMigrator(client);
167
+
168
+ await expect(
169
+ migrator.migrateToLatest({ mode: "from-schema" }),
170
+ ).rejects.toThrow("Hot Updater v1 cannot migrate schema 0.38.0");
171
+ });
172
+
173
+ it("blocks v0 schema readiness before reading bundle data", async () => {
174
+ const client = createSettingsMongoClient({ version: "0.36.0" });
175
+ const migrator = createMongoMigrator(client);
176
+ const plugin = createInMemoryDatabasePlugin();
177
+ const count = vi.spyOn(plugin.models.bundles, "count");
178
+ const findMany = vi.spyOn(plugin.models.bundles, "findMany");
179
+ const core = createDatabasePluginCore(plugin, async () => null, {
180
+ beforeOperation: createSchemaReadinessChecker(
181
+ "v0-mongodb",
182
+ () => migrator,
183
+ ),
184
+ });
185
+
186
+ await expect(core.api.getBundles({ limit: 1 })).rejects.toThrow(
187
+ "Hot Updater v1 cannot migrate schema 0.36.0",
188
+ );
189
+ expect(count).not.toHaveBeenCalled();
190
+ expect(findMany).not.toHaveBeenCalled();
191
+ });
192
+ });