@hot-updater/server 0.36.7 → 1.0.0-rc.1

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/apiKeys.cjs +139 -0
  44. package/dist/apiKeys.d.cts +50 -0
  45. package/dist/apiKeys.d.mts +50 -0
  46. package/dist/apiKeys.mjs +133 -0
  47. package/dist/createHotUpdaterCore.cjs +48 -26
  48. package/dist/createHotUpdaterCore.d.cts +49 -17
  49. package/dist/createHotUpdaterCore.d.mts +49 -17
  50. package/dist/createHotUpdaterCore.mjs +51 -29
  51. package/dist/db/createBundleDiff.cjs +45 -68
  52. package/dist/db/createBundleDiff.d.cts +3 -3
  53. package/dist/db/createBundleDiff.d.mts +3 -3
  54. package/dist/db/createBundleDiff.mjs +47 -68
  55. package/dist/db/databasePluginCore.cjs +138 -0
  56. package/dist/db/databasePluginCore.mjs +137 -0
  57. package/dist/db/fixedMigrator.cjs +36 -93
  58. package/dist/db/fixedMigrator.mjs +36 -93
  59. package/dist/db/fixedMigratorMongo.cjs +319 -0
  60. package/dist/db/fixedMigratorMongo.mjs +319 -0
  61. package/dist/db/fixedMigratorShared.cjs +23 -0
  62. package/dist/db/fixedMigratorShared.mjs +18 -0
  63. package/dist/db/mongoMigrationExecution.cjs +11 -0
  64. package/dist/db/mongoMigrationExecution.mjs +11 -0
  65. package/dist/db/releaseCatalog.cjs +66 -0
  66. package/dist/db/releaseCatalog.d.cts +17 -0
  67. package/dist/db/releaseCatalog.d.mts +17 -0
  68. package/dist/db/releaseCatalog.mjs +64 -0
  69. package/dist/db/schema/mongodb.cjs +14 -6
  70. package/dist/db/schema/mongodb.mjs +14 -6
  71. package/dist/db/schema/registry.cjs +0 -2
  72. package/dist/db/schema/registry.mjs +1 -2
  73. package/dist/db/schema/sql.cjs +31 -14
  74. package/dist/db/schema/sql.mjs +32 -13
  75. package/dist/db/schema/sqlOperations.cjs +4 -4
  76. package/dist/db/schema/sqlOperations.mjs +5 -5
  77. package/dist/db/schemaGenerators.cjs +52 -18
  78. package/dist/db/schemaGenerators.mjs +52 -18
  79. package/dist/db/schemaReadiness.cjs +4 -3
  80. package/dist/db/schemaReadiness.mjs +4 -3
  81. package/dist/db/sqlMigrationExecution.cjs +67 -0
  82. package/dist/db/sqlMigrationExecution.mjs +66 -0
  83. package/dist/db/types.cjs +2 -6
  84. package/dist/db/types.d.cts +60 -16
  85. package/dist/db/types.d.mts +60 -16
  86. package/dist/db/types.mjs +3 -6
  87. package/dist/db/updateArtifacts.cjs +114 -60
  88. package/dist/db/updateArtifacts.mjs +117 -62
  89. package/dist/handler.cjs +108 -282
  90. package/dist/handler.d.cts +3 -46
  91. package/dist/handler.d.mts +3 -46
  92. package/dist/handler.mjs +107 -281
  93. package/dist/handlerBundleRoutes.cjs +155 -0
  94. package/dist/handlerBundleRoutes.mjs +155 -0
  95. package/dist/handlerErrors.cjs +9 -0
  96. package/dist/handlerErrors.mjs +9 -0
  97. package/dist/handlerParameters.cjs +39 -0
  98. package/dist/handlerParameters.mjs +34 -0
  99. package/dist/handlerReleaseCatalogRoutes.cjs +111 -0
  100. package/dist/handlerReleaseCatalogRoutes.mjs +110 -0
  101. package/dist/handlerReleaseManagementRoutes.cjs +125 -0
  102. package/dist/handlerReleaseManagementRoutes.mjs +124 -0
  103. package/dist/handlerTypes.d.cts +63 -0
  104. package/dist/handlerTypes.d.mts +63 -0
  105. package/dist/handlerVersionRoutes.cjs +10 -0
  106. package/dist/handlerVersionRoutes.d.cts +4 -0
  107. package/dist/handlerVersionRoutes.d.mts +4 -0
  108. package/dist/handlerVersionRoutes.mjs +9 -0
  109. package/dist/index.cjs +10 -1
  110. package/dist/index.d.cts +9 -3
  111. package/dist/index.d.mts +9 -3
  112. package/dist/index.mjs +5 -2
  113. package/dist/insights/bounded/activeOverview.cjs +87 -0
  114. package/dist/insights/bounded/activeOverview.mjs +85 -0
  115. package/dist/insights/bounded/installationSearch.cjs +42 -0
  116. package/dist/insights/bounded/installationSearch.mjs +42 -0
  117. package/dist/insights/bounded/persistence.cjs +39 -0
  118. package/dist/insights/bounded/persistence.mjs +38 -0
  119. package/dist/insights/bounded/provider.cjs +170 -0
  120. package/dist/insights/bounded/provider.d.cts +7 -0
  121. package/dist/insights/bounded/provider.d.mts +7 -0
  122. package/dist/insights/bounded/provider.mjs +170 -0
  123. package/dist/insights/bounded/scan.cjs +144 -0
  124. package/dist/insights/bounded/scan.mjs +138 -0
  125. package/dist/insights/domain.d.cts +121 -0
  126. package/dist/insights/domain.d.mts +121 -0
  127. package/dist/insights/errors.cjs +22 -0
  128. package/dist/insights/errors.mjs +20 -0
  129. package/dist/insights/eventInput.cjs +111 -0
  130. package/dist/insights/eventInput.mjs +111 -0
  131. package/dist/insights/persistence.d.cts +6 -0
  132. package/dist/insights/persistence.d.mts +6 -0
  133. package/dist/insights/queryInput.cjs +58 -0
  134. package/dist/insights/queryInput.mjs +55 -0
  135. package/dist/insights/routes.cjs +63 -0
  136. package/dist/insights/routes.mjs +61 -0
  137. package/dist/insights/types.d.cts +17 -0
  138. package/dist/insights/types.d.mts +17 -0
  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/apiKeys.integration.spec.ts +192 -0
  188. package/src/apiKeys.spec.ts +162 -0
  189. package/src/apiKeys.ts +220 -0
  190. package/src/createHotUpdaterCore.ts +164 -100
  191. package/src/db/bundleRows.spec.ts +164 -0
  192. package/src/db/bundleRows.ts +10 -140
  193. package/src/db/createBundleDiff.spec.ts +172 -127
  194. package/src/db/createBundleDiff.ts +91 -110
  195. package/src/db/databasePluginCore.spec.ts +62 -0
  196. package/src/db/databasePluginCore.testFixtures.ts +76 -0
  197. package/src/db/databasePluginCore.ts +185 -0
  198. package/src/db/databasePluginUpdateCheck.bench.ts +107 -0
  199. package/src/db/fixedMigrator.spec.ts +184 -42
  200. package/src/db/fixedMigrator.ts +64 -205
  201. package/src/db/fixedMigratorMongo.spec.ts +192 -0
  202. package/src/db/fixedMigratorMongo.ts +425 -0
  203. package/src/db/fixedMigratorShared.ts +36 -0
  204. package/src/db/hotUpdaterSchema.ts +0 -1
  205. package/src/db/index.spec.ts +333 -1025
  206. package/src/db/mongoMigrationExecution.ts +23 -0
  207. package/src/db/node-sqlite.d.ts +25 -0
  208. package/src/db/releaseCatalog.ts +148 -0
  209. package/src/db/schema/mongodb.ts +20 -6
  210. package/src/db/schema/sql.ts +52 -11
  211. package/src/db/schema/sqlOperations.ts +5 -4
  212. package/src/db/schemaGenerators.ts +107 -23
  213. package/src/db/schemaReadiness.ts +2 -1
  214. package/src/db/sqlMigrationExecution.spec.ts +65 -0
  215. package/src/db/sqlMigrationExecution.ts +108 -0
  216. package/src/db/types.spec.ts +55 -0
  217. package/src/db/types.ts +129 -65
  218. package/src/db/updateArtifacts.spec.ts +359 -0
  219. package/src/db/updateArtifacts.ts +290 -126
  220. package/src/handler-standalone.integration.spec.ts +401 -364
  221. package/src/handler.spec.ts +137 -582
  222. package/src/handler.testFixtures.ts +30 -0
  223. package/src/handler.ts +218 -616
  224. package/src/handlerAuth.integration.spec.ts +33 -0
  225. package/src/handlerBundleRoutes.ts +259 -0
  226. package/src/handlerErrors.ts +7 -0
  227. package/src/handlerManagement.spec.ts +262 -0
  228. package/src/handlerParameters.ts +105 -0
  229. package/src/handlerReleaseCatalogRoutes.ts +154 -0
  230. package/src/handlerReleaseManagementRoutes.ts +213 -0
  231. package/src/handlerTypes.ts +96 -0
  232. package/src/handlerVersionRoutes.ts +12 -0
  233. package/src/index.ts +22 -2
  234. package/src/insights/bounded/activeOverview.spec.ts +189 -0
  235. package/src/insights/bounded/activeOverview.ts +148 -0
  236. package/src/insights/bounded/installationSearch.spec.ts +130 -0
  237. package/src/insights/bounded/installationSearch.ts +69 -0
  238. package/src/insights/bounded/persistence.ts +43 -0
  239. package/src/insights/bounded/provider.spec.ts +219 -0
  240. package/src/insights/bounded/provider.ts +229 -0
  241. package/src/insights/bounded/scan.ts +235 -0
  242. package/src/insights/domain.ts +139 -0
  243. package/src/insights/errors.ts +18 -0
  244. package/src/insights/eventInput.ts +168 -0
  245. package/src/insights/insights.integration.spec.ts +144 -0
  246. package/src/insights/persistence.ts +10 -0
  247. package/src/insights/queryInput.ts +102 -0
  248. package/src/insights/routes.ts +123 -0
  249. package/src/insights/types.ts +44 -0
  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
@@ -0,0 +1,425 @@
1
+ import type { MongoClient } from "mongodb";
2
+
3
+ import {
4
+ HOT_UPDATER_CORE_SCHEMA_KEY,
5
+ HOT_UPDATER_SCHEMA_VERSION,
6
+ HOT_UPDATER_SETTINGS_TABLE,
7
+ } from "../schema/types";
8
+ import {
9
+ assertCurrentOrEmptySchemaVersion,
10
+ assertSupportedMigrationMode,
11
+ getEmptyMigrationResult,
12
+ inferLegacyCoreSchemaVersion,
13
+ isCurrentSchemaVersion,
14
+ } from "./fixedMigratorShared";
15
+ import { executeMongoMigration } from "./mongoMigrationExecution";
16
+ import { createMongoMigrationOperations } from "./schema/mongodb";
17
+ import {
18
+ hotUpdaterSchema,
19
+ schemaIndexAppliesToProvider,
20
+ } from "./schema/registry";
21
+ import type {
22
+ MigrateOptions,
23
+ MigrationOperation,
24
+ MigrationResult,
25
+ Migrator,
26
+ } from "./types";
27
+
28
+ const isMongoNamespaceExistsError = (error: unknown): boolean => {
29
+ if (!(error instanceof Error)) return false;
30
+ const mongoError = error as { code?: unknown; codeName?: unknown };
31
+ return mongoError.code === 48 || mongoError.codeName === "NamespaceExists";
32
+ };
33
+
34
+ const isMongoNamespaceNotFoundError = (error: unknown): boolean => {
35
+ if (!(error instanceof Error)) return false;
36
+ const mongoError = error as { code?: unknown; codeName?: unknown };
37
+ return mongoError.code === 26 || mongoError.codeName === "NamespaceNotFound";
38
+ };
39
+
40
+ const ignoreExistingCollection = (error: unknown): undefined => {
41
+ if (isMongoNamespaceExistsError(error)) {
42
+ return undefined;
43
+ }
44
+ throw error;
45
+ };
46
+
47
+ const mongoNullableString = { bsonType: ["string", "null"] } as const;
48
+ const mongoByteSize = {
49
+ bsonType: ["double", "int", "long"],
50
+ maximum: Number.MAX_SAFE_INTEGER,
51
+ minimum: 0,
52
+ } as const;
53
+
54
+ const mongoBundleValidator = {
55
+ $and: [
56
+ {
57
+ $jsonSchema: {
58
+ bsonType: "object",
59
+ properties: {
60
+ archive_byte_size: mongoByteSize,
61
+ asset_base_storage_uri: mongoNullableString,
62
+ file_hash: { bsonType: "string" },
63
+ git_commit_hash: mongoNullableString,
64
+ id: { bsonType: "string" },
65
+ manifest_file_hash: mongoNullableString,
66
+ manifest_storage_uri: mongoNullableString,
67
+ metadata: { bsonType: "object" },
68
+ platform: { enum: ["ios", "android"] },
69
+ storage_uri: { bsonType: "string" },
70
+ },
71
+ required: [
72
+ "id",
73
+ "platform",
74
+ "file_hash",
75
+ "git_commit_hash",
76
+ "storage_uri",
77
+ "archive_byte_size",
78
+ "metadata",
79
+ "manifest_storage_uri",
80
+ "manifest_file_hash",
81
+ "asset_base_storage_uri",
82
+ ],
83
+ },
84
+ },
85
+ {
86
+ $expr: {
87
+ $eq: [{ $trunc: "$archive_byte_size" }, "$archive_byte_size"],
88
+ },
89
+ },
90
+ ],
91
+ } as const;
92
+
93
+ const mongoPatchValidator = {
94
+ $and: [
95
+ {
96
+ $jsonSchema: {
97
+ bsonType: "object",
98
+ properties: {
99
+ base_bundle_id: { bsonType: "string" },
100
+ base_file_hash: { bsonType: "string" },
101
+ bundle_id: { bsonType: "string" },
102
+ id: { bsonType: "string" },
103
+ order_index: mongoByteSize,
104
+ byte_size: mongoByteSize,
105
+ patch_file_hash: { bsonType: "string" },
106
+ patch_storage_uri: { bsonType: "string" },
107
+ },
108
+ required: [
109
+ "id",
110
+ "bundle_id",
111
+ "base_bundle_id",
112
+ "base_file_hash",
113
+ "patch_file_hash",
114
+ "patch_storage_uri",
115
+ "byte_size",
116
+ "order_index",
117
+ ],
118
+ },
119
+ },
120
+ {
121
+ $expr: {
122
+ $and: [
123
+ { $eq: [{ $trunc: "$order_index" }, "$order_index"] },
124
+ { $eq: [{ $trunc: "$byte_size" }, "$byte_size"] },
125
+ ],
126
+ },
127
+ },
128
+ ],
129
+ } as const;
130
+
131
+ const mongoReleaseValidator = {
132
+ $jsonSchema: {
133
+ bsonType: "object",
134
+ properties: {
135
+ bundle_id: mongoNullableString,
136
+ channel_id: { bsonType: "string" },
137
+ created_at_ms: { bsonType: ["double", "int", "long"] },
138
+ enabled: { bsonType: "bool" },
139
+ fingerprint_hash: mongoNullableString,
140
+ id: { bsonType: "string" },
141
+ kind: { enum: ["BUNDLE", "EMBEDDED"] },
142
+ message: mongoNullableString,
143
+ operation: { enum: ["DEPLOY", "PROMOTE", "ROLLBACK"] },
144
+ platform: { enum: ["ios", "android"] },
145
+ revision: { bsonType: ["int", "long"] },
146
+ rollout_cohort_count: { bsonType: ["int", "long"] },
147
+ scope_key: { bsonType: "string" },
148
+ should_force_update: { bsonType: "bool" },
149
+ source_release_id: mongoNullableString,
150
+ strategy: { enum: ["APP_VERSION", "FINGERPRINT"] },
151
+ target_app_version: mongoNullableString,
152
+ target_cohorts: { bsonType: "array", items: { bsonType: "string" } },
153
+ updated_at_ms: { bsonType: ["double", "int", "long"] },
154
+ },
155
+ required: [
156
+ "id",
157
+ "revision",
158
+ "scope_key",
159
+ "channel_id",
160
+ "platform",
161
+ "kind",
162
+ "bundle_id",
163
+ "strategy",
164
+ "target_app_version",
165
+ "fingerprint_hash",
166
+ "enabled",
167
+ "should_force_update",
168
+ "message",
169
+ "rollout_cohort_count",
170
+ "target_cohorts",
171
+ "operation",
172
+ "source_release_id",
173
+ "created_at_ms",
174
+ "updated_at_ms",
175
+ ],
176
+ },
177
+ } as const;
178
+
179
+ const mongoReleaseCatalogValidator = {
180
+ $jsonSchema: {
181
+ bsonType: "object",
182
+ properties: {
183
+ catalog_id: { bsonType: "string" },
184
+ byte_size: { bsonType: ["int", "long"] },
185
+ catalog_hash: { bsonType: "string" },
186
+ channel_id: { bsonType: "string" },
187
+ channel_key: { bsonType: "string" },
188
+ fingerprint_hash: mongoNullableString,
189
+ generation: { bsonType: ["double", "int", "long"] },
190
+ is_tombstone: { bsonType: "bool" },
191
+ payload: { bsonType: "string" },
192
+ platform: { enum: ["ios", "android"] },
193
+ scope_key: { bsonType: "string" },
194
+ strategy: { enum: ["APP_VERSION", "FINGERPRINT"] },
195
+ updated_at_ms: { bsonType: ["double", "int", "long"] },
196
+ },
197
+ required: [
198
+ "scope_key",
199
+ "catalog_id",
200
+ "strategy",
201
+ "channel_id",
202
+ "channel_key",
203
+ "platform",
204
+ "fingerprint_hash",
205
+ "generation",
206
+ "payload",
207
+ "catalog_hash",
208
+ "byte_size",
209
+ "is_tombstone",
210
+ "updated_at_ms",
211
+ ],
212
+ },
213
+ } as const;
214
+
215
+ const createSettingsKeyIndexOperation = (): MigrationOperation => ({
216
+ description: `Ensure unique MongoDB index: ${HOT_UPDATER_SETTINGS_TABLE}(key)`,
217
+ type: "custom",
218
+ });
219
+
220
+ export const createMongoMigrator = (client: MongoClient): Migrator => {
221
+ const settings = client
222
+ .db()
223
+ .collection<{ key: string; value: unknown }>(HOT_UPDATER_SETTINGS_TABLE);
224
+ const getSetting = async (key: string): Promise<string | undefined> => {
225
+ const rows = await settings.find({ key }).limit(2).toArray();
226
+ const [row, duplicate] = rows;
227
+ if (!row) return undefined;
228
+ if (duplicate) {
229
+ throw new Error(`Duplicate Hot Updater schema setting: ${key}`);
230
+ }
231
+ if (typeof row.value !== "string") {
232
+ throw new Error(`Invalid Hot Updater schema setting: ${key}`);
233
+ }
234
+ return row.value;
235
+ };
236
+ const getCoreVersion = (): Promise<string | undefined> =>
237
+ getSetting(HOT_UPDATER_CORE_SCHEMA_KEY);
238
+ const getSchemaVersions = async (): Promise<{
239
+ readonly coreVersion: string | undefined;
240
+ readonly legacyCoreVersion: string | undefined;
241
+ }> => {
242
+ const coreVersion = await getCoreVersion();
243
+ const legacyCoreVersion = inferLegacyCoreSchemaVersion(
244
+ await getSetting("version"),
245
+ );
246
+ return { coreVersion, legacyCoreVersion };
247
+ };
248
+ const getVersion = async (): Promise<string | undefined> => {
249
+ const { coreVersion, legacyCoreVersion } = await getSchemaVersions();
250
+ return coreVersion ?? legacyCoreVersion;
251
+ };
252
+ const getSettingsKeyIndexState = async (): Promise<
253
+ "missing" | "non-unique" | "unique"
254
+ > => {
255
+ const indexes = await settings
256
+ .listIndexes()
257
+ .toArray()
258
+ .catch((error: unknown) => {
259
+ if (isMongoNamespaceNotFoundError(error)) return [];
260
+ throw error;
261
+ });
262
+ const index = indexes.find(({ key }) => {
263
+ const fields = Object.entries(key);
264
+ return (
265
+ fields.length === 1 && fields[0]?.[0] === "key" && fields[0][1] === 1
266
+ );
267
+ });
268
+ if (!index) return "missing";
269
+ return index.unique === true &&
270
+ index.sparse !== true &&
271
+ index.partialFilterExpression === undefined
272
+ ? "unique"
273
+ : "non-unique";
274
+ };
275
+ const rejectNonUniqueSettingsKeyIndex = (): never => {
276
+ throw new Error(
277
+ "Hot Updater settings key index must enforce uniqueness for every key.",
278
+ );
279
+ };
280
+ const ensureSettingsKeyIndex = async (): Promise<void> => {
281
+ const state = await getSettingsKeyIndexState();
282
+ if (state === "unique") return;
283
+ if (state === "non-unique") rejectNonUniqueSettingsKeyIndex();
284
+ await settings.createIndex({ key: 1 }, { unique: true });
285
+ };
286
+ const makeResult = async (
287
+ options: MigrateOptions = {},
288
+ ): Promise<MigrationResult> => {
289
+ assertSupportedMigrationMode(options);
290
+
291
+ const { coreVersion, legacyCoreVersion } = await getSchemaVersions();
292
+ const settingsKeyIndexState = await getSettingsKeyIndexState();
293
+ if (settingsKeyIndexState === "non-unique") {
294
+ rejectNonUniqueSettingsKeyIndex();
295
+ }
296
+ if (isCurrentSchemaVersion(coreVersion)) {
297
+ if (settingsKeyIndexState === "unique") {
298
+ return getEmptyMigrationResult();
299
+ }
300
+ return {
301
+ ...getEmptyMigrationResult(),
302
+ execute: ensureSettingsKeyIndex,
303
+ operations: [createSettingsKeyIndexOperation()],
304
+ };
305
+ }
306
+ assertCurrentOrEmptySchemaVersion(coreVersion ?? legacyCoreVersion);
307
+ const settingsOperation =
308
+ options.updateSettings === false
309
+ ? undefined
310
+ : ({
311
+ type: "custom",
312
+ key: HOT_UPDATER_CORE_SCHEMA_KEY,
313
+ value: HOT_UPDATER_SCHEMA_VERSION,
314
+ } satisfies MigrationOperation);
315
+ return {
316
+ operations: [
317
+ createSettingsKeyIndexOperation(),
318
+ ...createMongoMigrationOperations(settingsOperation),
319
+ ],
320
+ execute: async () => {
321
+ const db = client.db();
322
+ await executeMongoMigration({
323
+ updateSettings: options.updateSettings !== false,
324
+ backend: {
325
+ ensureCollections: async () => {
326
+ for (const table of hotUpdaterSchema.tables) {
327
+ if (table.internal) continue;
328
+ await db
329
+ .createCollection(table.ormName)
330
+ .catch(ignoreExistingCollection);
331
+ }
332
+ },
333
+ ensureIndexes: async () => {
334
+ await ensureSettingsKeyIndex();
335
+ for (const table of hotUpdaterSchema.tables) {
336
+ if (table.internal) continue;
337
+ const collection = db.collection(table.ormName);
338
+ const primaryKey = table.columns.find(
339
+ (column) => column.primaryKey,
340
+ );
341
+ if (!primaryKey) {
342
+ throw new Error(
343
+ `MongoDB table ${table.ormName} does not define a primary key.`,
344
+ );
345
+ }
346
+ const idIndexName = `${table.ormName}_${primaryKey.ormName}_idx`;
347
+ const existingIndexes = await collection
348
+ .listIndexes()
349
+ .toArray();
350
+ const existingIdIndex = existingIndexes.find(
351
+ ({ name }) => name === idIndexName,
352
+ );
353
+ if (existingIdIndex && existingIdIndex.unique !== true) {
354
+ await collection.dropIndex(idIndexName);
355
+ }
356
+ await collection.createIndex(
357
+ { [primaryKey.ormName]: 1 },
358
+ { name: idIndexName, unique: true },
359
+ );
360
+ for (const index of (table.indexes ?? []).filter((item) =>
361
+ schemaIndexAppliesToProvider(item, "mongodb"),
362
+ )) {
363
+ await collection.createIndex(
364
+ Object.fromEntries(
365
+ index.columns.map((column) => [column, 1]),
366
+ ),
367
+ {
368
+ name: index.name,
369
+ ...(index.unique ? { unique: true } : {}),
370
+ },
371
+ );
372
+ }
373
+ }
374
+ },
375
+ enforceSchema: async () => {
376
+ for (const [collection, validator] of [
377
+ ["bundles", mongoBundleValidator],
378
+ ["bundle_patches", mongoPatchValidator],
379
+ ["releases", mongoReleaseValidator],
380
+ ["release_catalogs", mongoReleaseCatalogValidator],
381
+ ] as const) {
382
+ await db.command({
383
+ collMod: collection,
384
+ validationAction: "error",
385
+ validationLevel: "strict",
386
+ validator,
387
+ });
388
+ }
389
+ },
390
+ updateVersion: async () => {
391
+ await settings.updateOne(
392
+ { key: HOT_UPDATER_CORE_SCHEMA_KEY },
393
+ { $set: { value: HOT_UPDATER_SCHEMA_VERSION } },
394
+ { upsert: true },
395
+ );
396
+ },
397
+ },
398
+ });
399
+ },
400
+ };
401
+ };
402
+
403
+ return {
404
+ getVersion,
405
+ getNameVariants: async () => undefined,
406
+ next: async () => {
407
+ const { coreVersion } = await getSchemaVersions();
408
+ return isCurrentSchemaVersion(coreVersion)
409
+ ? undefined
410
+ : { version: HOT_UPDATER_SCHEMA_VERSION };
411
+ },
412
+ previous: async () => undefined,
413
+ up: makeResult,
414
+ down: async () => {
415
+ throw new Error("No previous schema to migrate to.");
416
+ },
417
+ migrateTo: async (version, options) => {
418
+ if (version !== HOT_UPDATER_SCHEMA_VERSION) {
419
+ throw new Error(`Invalid version ${version}`);
420
+ }
421
+ return makeResult(options);
422
+ },
423
+ migrateToLatest: makeResult,
424
+ };
425
+ };
@@ -0,0 +1,36 @@
1
+ import { HOT_UPDATER_SCHEMA_VERSION } from "../schema/types";
2
+ import type { MigrateOptions, MigrationResult } from "./types";
3
+
4
+ export const getEmptyMigrationResult = (): MigrationResult => ({
5
+ operations: [],
6
+ execute: async () => {},
7
+ getSQL: () => "",
8
+ });
9
+
10
+ export const assertSupportedMigrationMode = (options: MigrateOptions): void => {
11
+ if (options.mode === "from-database") {
12
+ throw new Error("Hot Updater migrations support only mode: 'from-schema'.");
13
+ }
14
+ };
15
+
16
+ export const unsupportedSchemaUpgradeMessage = (version: string): string =>
17
+ `Hot Updater v1 cannot migrate schema ${version} in place. Create a new empty database and run migrate or generate against schema ${HOT_UPDATER_SCHEMA_VERSION}.`;
18
+
19
+ export const assertCurrentOrEmptySchemaVersion = (
20
+ currentVersion: string | undefined,
21
+ ): void => {
22
+ if (
23
+ currentVersion !== undefined &&
24
+ currentVersion !== HOT_UPDATER_SCHEMA_VERSION
25
+ ) {
26
+ throw new Error(unsupportedSchemaUpgradeMessage(currentVersion));
27
+ }
28
+ };
29
+
30
+ export const inferLegacyCoreSchemaVersion = (
31
+ legacyVersion: string | undefined,
32
+ ): string | undefined => legacyVersion;
33
+
34
+ export const isCurrentSchemaVersion = (
35
+ currentVersion: string | undefined,
36
+ ): boolean => currentVersion === HOT_UPDATER_SCHEMA_VERSION;
@@ -2,5 +2,4 @@ export * from "../schema";
2
2
  export * from "./schema/mongodb";
3
3
  export * from "./schema/registry";
4
4
  export * from "./schema/sql";
5
- export * from "./schema/sqlMigrations";
6
5
  export * from "./schema/sqlOperations";