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

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 (542) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +1 -1
  2. package/dist/adapters/drizzle.cjs +35 -69
  3. package/dist/adapters/drizzle.d.cts +13 -6
  4. package/dist/adapters/drizzle.d.mts +13 -6
  5. package/dist/adapters/drizzle.mjs +34 -69
  6. package/dist/adapters/drizzleExecutor.cjs +114 -0
  7. package/dist/adapters/drizzleExecutor.d.cts +9 -0
  8. package/dist/adapters/drizzleExecutor.d.mts +9 -0
  9. package/dist/adapters/drizzleExecutor.mjs +112 -0
  10. package/dist/adapters/kysely.cjs +25 -31
  11. package/dist/adapters/kysely.d.cts +10 -7
  12. package/dist/adapters/kysely.d.mts +10 -7
  13. package/dist/adapters/kysely.mjs +25 -31
  14. package/dist/adapters/kyselyExecutor.cjs +32 -0
  15. package/dist/adapters/kyselyExecutor.d.cts +12 -0
  16. package/dist/adapters/kyselyExecutor.d.mts +12 -0
  17. package/dist/adapters/kyselyExecutor.mjs +32 -0
  18. package/dist/adapters/mongodb.cjs +28 -27
  19. package/dist/adapters/mongodb.d.cts +10 -5
  20. package/dist/adapters/mongodb.d.mts +10 -5
  21. package/dist/adapters/mongodb.mjs +28 -27
  22. package/dist/adapters/mongodbAdapter.cjs +174 -0
  23. package/dist/adapters/mongodbAdapter.d.cts +9 -0
  24. package/dist/adapters/mongodbAdapter.d.mts +9 -0
  25. package/dist/adapters/mongodbAdapter.mjs +173 -0
  26. package/dist/adapters/prisma.cjs +38 -220
  27. package/dist/adapters/prisma.d.cts +12 -8
  28. package/dist/adapters/prisma.d.mts +12 -8
  29. package/dist/adapters/prisma.mjs +38 -219
  30. package/dist/adapters/prismaExecutor.cjs +64 -0
  31. package/dist/adapters/prismaExecutor.mjs +64 -0
  32. package/dist/adapters/sqlProviders.cjs +9 -0
  33. package/dist/adapters/sqlProviders.mjs +9 -0
  34. package/dist/apiKeys.cjs +8 -8
  35. package/dist/apiKeys.d.cts +1 -24
  36. package/dist/apiKeys.d.mts +1 -24
  37. package/dist/apiKeys.mjs +5 -9
  38. package/dist/assembly/assemblePlugins.cjs +147 -0
  39. package/dist/assembly/assemblePlugins.d.cts +12 -0
  40. package/dist/assembly/assemblePlugins.d.mts +12 -0
  41. package/dist/assembly/assemblePlugins.mjs +146 -0
  42. package/dist/assembly/databasePlugins.cjs +23 -0
  43. package/dist/assembly/databasePlugins.d.cts +43 -0
  44. package/dist/assembly/databasePlugins.d.mts +43 -0
  45. package/dist/assembly/databasePlugins.mjs +22 -0
  46. package/dist/core/api.cjs +91 -0
  47. package/dist/core/api.d.cts +65 -0
  48. package/dist/core/api.d.mts +65 -0
  49. package/dist/core/api.mjs +87 -0
  50. package/dist/core/baseCandidates.cjs +71 -0
  51. package/dist/core/baseCandidates.d.cts +17 -0
  52. package/dist/core/baseCandidates.d.mts +17 -0
  53. package/dist/core/baseCandidates.mjs +69 -0
  54. package/dist/core/operations.cjs +350 -0
  55. package/dist/core/operations.mjs +350 -0
  56. package/dist/core/reads.cjs +273 -0
  57. package/dist/core/reads.d.cts +48 -0
  58. package/dist/core/reads.d.mts +48 -0
  59. package/dist/core/reads.mjs +267 -0
  60. package/dist/core/releases.cjs +193 -0
  61. package/dist/core/releases.mjs +190 -0
  62. package/dist/core/schema.cjs +279 -0
  63. package/dist/core/schema.d.cts +307 -0
  64. package/dist/core/schema.d.mts +307 -0
  65. package/dist/core/schema.mjs +277 -0
  66. package/dist/core/writes.cjs +96 -0
  67. package/dist/core/writes.mjs +91 -0
  68. package/dist/createHotUpdaterCore.cjs +61 -63
  69. package/dist/createHotUpdaterCore.d.cts +53 -45
  70. package/dist/createHotUpdaterCore.d.mts +53 -45
  71. package/dist/createHotUpdaterCore.mjs +61 -63
  72. package/dist/database/cursor.cjs +44 -0
  73. package/dist/database/cursor.d.cts +7 -0
  74. package/dist/database/cursor.d.mts +7 -0
  75. package/dist/database/cursor.mjs +40 -0
  76. package/dist/database/database.cjs +44 -0
  77. package/dist/database/database.d.cts +123 -0
  78. package/dist/database/database.d.mts +123 -0
  79. package/dist/database/database.mjs +44 -0
  80. package/dist/database/definitions.d.cts +68 -0
  81. package/dist/database/definitions.d.mts +68 -0
  82. package/dist/database/engine.cjs +37 -0
  83. package/dist/database/engine.d.cts +40 -0
  84. package/dist/database/engine.d.mts +40 -0
  85. package/dist/database/engine.mjs +37 -0
  86. package/dist/database/engineAggregates.cjs +115 -0
  87. package/dist/database/engineAggregates.mjs +112 -0
  88. package/dist/database/engineReads.cjs +180 -0
  89. package/dist/database/engineReads.d.cts +35 -0
  90. package/dist/database/engineReads.d.mts +35 -0
  91. package/dist/database/engineReads.mjs +179 -0
  92. package/dist/database/engineTransaction.cjs +403 -0
  93. package/dist/database/engineTransaction.d.cts +26 -0
  94. package/dist/database/engineTransaction.d.mts +26 -0
  95. package/dist/database/engineTransaction.mjs +402 -0
  96. package/dist/database/errors.cjs +27 -0
  97. package/dist/database/errors.d.cts +21 -0
  98. package/dist/database/errors.d.mts +21 -0
  99. package/dist/database/errors.mjs +24 -0
  100. package/dist/database/fence.cjs +124 -0
  101. package/dist/database/fence.d.cts +42 -0
  102. package/dist/database/fence.d.mts +42 -0
  103. package/dist/database/fence.mjs +116 -0
  104. package/dist/database/index.cjs +127 -0
  105. package/dist/database/index.d.cts +17 -0
  106. package/dist/database/index.d.mts +17 -0
  107. package/dist/database/index.mjs +15 -0
  108. package/dist/database/kv/kvAdapter.cjs +273 -0
  109. package/dist/database/kv/kvAdapter.d.cts +101 -0
  110. package/dist/database/kv/kvAdapter.d.mts +101 -0
  111. package/dist/database/kv/kvAdapter.mjs +272 -0
  112. package/dist/database/resolveSchema.cjs +196 -0
  113. package/dist/database/resolveSchema.d.cts +42 -0
  114. package/dist/database/resolveSchema.d.mts +42 -0
  115. package/dist/database/resolveSchema.mjs +193 -0
  116. package/dist/database/schema.cjs +21 -0
  117. package/dist/database/schema.d.cts +61 -0
  118. package/dist/database/schema.d.mts +61 -0
  119. package/dist/database/schema.mjs +20 -0
  120. package/dist/database/sql/sqlAdapter.cjs +282 -0
  121. package/dist/database/sql/sqlAdapter.d.cts +46 -0
  122. package/dist/database/sql/sqlAdapter.d.mts +46 -0
  123. package/dist/database/sql/sqlAdapter.mjs +279 -0
  124. package/dist/database/sql/sqlSchema.cjs +115 -0
  125. package/dist/database/sql/sqlSchema.d.cts +8 -0
  126. package/dist/database/sql/sqlSchema.d.mts +8 -0
  127. package/dist/database/sql/sqlSchema.mjs +111 -0
  128. package/dist/db/builtInDatabase.cjs +104 -0
  129. package/dist/db/builtInDatabase.d.cts +38 -0
  130. package/dist/db/builtInDatabase.d.mts +38 -0
  131. package/dist/db/builtInDatabase.mjs +97 -0
  132. package/dist/db/createBundleDiff.cjs +6 -6
  133. package/dist/db/createBundleDiff.d.cts +4 -7
  134. package/dist/db/createBundleDiff.d.mts +4 -7
  135. package/dist/db/createBundleDiff.mjs +5 -5
  136. package/dist/db/engineDrizzleSchema.cjs +54 -0
  137. package/dist/db/engineDrizzleSchema.mjs +54 -0
  138. package/dist/db/engineMigrator.cjs +53 -0
  139. package/dist/db/engineMigrator.mjs +53 -0
  140. package/dist/db/enginePrismaSchema.cjs +85 -0
  141. package/dist/db/enginePrismaSchema.d.cts +5 -0
  142. package/dist/db/enginePrismaSchema.d.mts +5 -0
  143. package/dist/db/enginePrismaSchema.mjs +84 -0
  144. package/dist/db/engineSql.cjs +26 -0
  145. package/dist/db/engineSql.d.cts +17 -0
  146. package/dist/db/engineSql.d.mts +17 -0
  147. package/dist/db/engineSql.mjs +25 -0
  148. package/dist/db/engineSqlMigrator.cjs +66 -0
  149. package/dist/db/engineSqlMigrator.mjs +66 -0
  150. package/dist/db/fixedMigratorShared.cjs +0 -11
  151. package/dist/db/fixedMigratorShared.mjs +1 -8
  152. package/dist/db/index.cjs +23 -8
  153. package/dist/db/index.d.cts +10 -4
  154. package/dist/db/index.d.mts +10 -4
  155. package/dist/db/index.mjs +16 -8
  156. package/dist/db/releaseCatalog.cjs +20 -44
  157. package/dist/db/releaseCatalog.d.cts +2 -3
  158. package/dist/db/releaseCatalog.d.mts +2 -3
  159. package/dist/db/releaseCatalog.mjs +20 -44
  160. package/dist/db/schemaSettings.cjs +63 -0
  161. package/dist/db/schemaSettings.d.cts +12 -0
  162. package/dist/db/schemaSettings.d.mts +12 -0
  163. package/dist/db/schemaSettings.mjs +62 -0
  164. package/dist/db/settingsMigrator.cjs +92 -0
  165. package/dist/db/settingsMigrator.mjs +89 -0
  166. package/dist/db/types.cjs +3 -12
  167. package/dist/db/types.d.cts +26 -69
  168. package/dist/db/types.d.mts +26 -69
  169. package/dist/db/types.mjs +2 -11
  170. package/dist/db/updateArtifacts.cjs +38 -68
  171. package/dist/db/updateArtifacts.mjs +40 -69
  172. package/dist/handler.cjs +72 -43
  173. package/dist/handler.d.cts +3 -6
  174. package/dist/handler.d.mts +3 -6
  175. package/dist/handler.mjs +73 -43
  176. package/dist/handlerAdminRoutes.cjs +375 -0
  177. package/dist/handlerAdminRoutes.mjs +373 -0
  178. package/dist/handlerParameters.cjs +6 -11
  179. package/dist/handlerParameters.mjs +6 -10
  180. package/dist/handlerReleaseCatalogRoutes.cjs +11 -14
  181. package/dist/handlerReleaseCatalogRoutes.mjs +11 -13
  182. package/dist/handlerTypes.d.cts +2 -56
  183. package/dist/handlerTypes.d.mts +2 -56
  184. package/dist/handlerVersionRoutes.cjs +20 -4
  185. package/dist/handlerVersionRoutes.d.cts +1 -0
  186. package/dist/handlerVersionRoutes.d.mts +1 -0
  187. package/dist/handlerVersionRoutes.mjs +20 -4
  188. package/dist/index.cjs +4 -7
  189. package/dist/index.d.cts +10 -9
  190. package/dist/index.d.mts +10 -9
  191. package/dist/index.mjs +4 -4
  192. package/dist/insights/domain.d.cts +53 -70
  193. package/dist/insights/domain.d.mts +53 -70
  194. package/dist/insights/errors.cjs +35 -8
  195. package/dist/insights/errors.mjs +35 -8
  196. package/dist/insights/eventInput.cjs +64 -24
  197. package/dist/insights/eventInput.mjs +63 -25
  198. package/dist/insights/provider.cjs +356 -0
  199. package/dist/insights/provider.d.cts +6 -0
  200. package/dist/insights/provider.d.mts +6 -0
  201. package/dist/insights/provider.mjs +355 -0
  202. package/dist/insights/queryInput.cjs +78 -43
  203. package/dist/insights/queryInput.mjs +76 -40
  204. package/dist/insights/routes.cjs +94 -34
  205. package/dist/insights/routes.mjs +93 -35
  206. package/dist/insights/types.d.cts +27 -12
  207. package/dist/insights/types.d.mts +27 -12
  208. package/dist/node.d.cts +1 -2
  209. package/dist/node.d.mts +1 -2
  210. package/dist/package.cjs +1 -1
  211. package/dist/package.mjs +1 -1
  212. package/dist/plugins/api-keys/index.cjs +50 -0
  213. package/dist/plugins/api-keys/index.d.cts +85 -0
  214. package/dist/plugins/api-keys/index.d.mts +85 -0
  215. package/dist/plugins/api-keys/index.mjs +47 -0
  216. package/dist/plugins/api-keys/model.cjs +58 -0
  217. package/dist/plugins/api-keys/model.d.cts +8 -0
  218. package/dist/plugins/api-keys/model.d.mts +8 -0
  219. package/dist/plugins/api-keys/model.mjs +58 -0
  220. package/dist/plugins/api-keys/schema.cjs +38 -0
  221. package/dist/plugins/api-keys/schema.d.cts +44 -0
  222. package/dist/plugins/api-keys/schema.d.mts +44 -0
  223. package/dist/plugins/api-keys/schema.mjs +39 -0
  224. package/dist/plugins/builtIn.cjs +7 -0
  225. package/dist/plugins/builtIn.mjs +6 -0
  226. package/dist/plugins/definePlugin.cjs +5 -0
  227. package/dist/plugins/definePlugin.d.cts +73 -0
  228. package/dist/plugins/definePlugin.d.mts +73 -0
  229. package/dist/plugins/definePlugin.mjs +5 -0
  230. package/dist/plugins/index.cjs +17 -0
  231. package/dist/plugins/index.d.cts +9 -0
  232. package/dist/plugins/index.d.mts +9 -0
  233. package/dist/plugins/index.mjs +7 -0
  234. package/dist/plugins/insights/index.cjs +48 -0
  235. package/dist/plugins/insights/index.d.cts +295 -0
  236. package/dist/plugins/insights/index.d.mts +295 -0
  237. package/dist/plugins/insights/index.mjs +44 -0
  238. package/dist/plugins/insights/model.cjs +17 -0
  239. package/dist/plugins/insights/model.d.cts +10 -0
  240. package/dist/plugins/insights/model.d.mts +10 -0
  241. package/dist/plugins/insights/model.mjs +17 -0
  242. package/dist/plugins/insights/reads.cjs +471 -0
  243. package/dist/plugins/insights/reads.mjs +466 -0
  244. package/dist/plugins/insights/recordEvent.cjs +135 -0
  245. package/dist/plugins/insights/recordEvent.d.cts +13 -0
  246. package/dist/plugins/insights/recordEvent.d.mts +13 -0
  247. package/dist/plugins/insights/recordEvent.mjs +132 -0
  248. package/dist/plugins/insights/schema.cjs +347 -0
  249. package/dist/plugins/insights/schema.d.cts +258 -0
  250. package/dist/plugins/insights/schema.d.mts +258 -0
  251. package/dist/plugins/insights/schema.mjs +344 -0
  252. package/dist/storageAccess.cjs +0 -1
  253. package/dist/types/index.d.cts +2 -29
  254. package/dist/types/index.d.mts +2 -29
  255. package/package.json +30 -7
  256. package/src/adapters/drizzle.conformance.integration.spec.ts +34 -0
  257. package/src/adapters/drizzle.readBudgets.integration.spec.ts +41 -0
  258. package/src/adapters/drizzle.spec.ts +277 -208
  259. package/src/adapters/drizzle.ts +48 -128
  260. package/src/adapters/drizzleExecutor.ts +166 -0
  261. package/src/adapters/kysely.conformance.integration.spec.ts +36 -0
  262. package/src/adapters/kysely.readBudgets.integration.spec.ts +42 -0
  263. package/src/adapters/kysely.spec.ts +212 -117
  264. package/src/adapters/kysely.ts +32 -87
  265. package/src/adapters/kyselyExecutor.ts +53 -0
  266. package/src/adapters/mongodb.integration.spec.ts +180 -0
  267. package/src/adapters/mongodb.readBudgets.integration.spec.ts +61 -0
  268. package/src/adapters/mongodb.ts +36 -65
  269. package/src/adapters/mongodbAdapter.ts +293 -0
  270. package/src/adapters/prisma.conformance.integration.spec.ts +36 -0
  271. package/src/adapters/prisma.integration.spec.ts +138 -0
  272. package/src/adapters/prisma.readBudgets.integration.spec.ts +40 -0
  273. package/src/adapters/prisma.spec.ts +226 -173
  274. package/src/adapters/prisma.ts +60 -451
  275. package/src/adapters/prismaExecutor.ts +107 -0
  276. package/src/adapters/prismaTestClients.ts +175 -0
  277. package/src/adapters/sqlProviders.ts +14 -0
  278. package/src/apiKeys.integration.spec.ts +19 -119
  279. package/src/apiKeys.spec.ts +39 -35
  280. package/src/apiKeys.ts +2 -8
  281. package/src/assembly/assemblePlugins.ts +269 -0
  282. package/src/assembly/assembly.spec.ts +325 -0
  283. package/src/assembly/assembly.types.spec.ts +155 -0
  284. package/src/assembly/builtInRoutes.spec.ts +213 -0
  285. package/src/assembly/databasePlugins.spec.ts +71 -0
  286. package/src/assembly/databasePlugins.ts +96 -0
  287. package/src/core/api.ts +139 -0
  288. package/src/core/baseCandidates.spec.ts +378 -0
  289. package/src/core/baseCandidates.ts +136 -0
  290. package/src/core/index.ts +44 -0
  291. package/src/core/operations.spec.ts +415 -0
  292. package/src/core/operations.ts +551 -0
  293. package/src/core/reads.spec.ts +300 -0
  294. package/src/core/reads.ts +417 -0
  295. package/src/core/releases.spec.ts +308 -0
  296. package/src/core/releases.ts +345 -0
  297. package/src/core/schema.ts +173 -0
  298. package/src/core/writes.ts +160 -0
  299. package/src/createHotUpdaterCore.ts +191 -167
  300. package/src/database/cursor.ts +65 -0
  301. package/src/database/database.ts +335 -0
  302. package/src/database/database.types.spec.ts +135 -0
  303. package/src/database/definitions.ts +72 -0
  304. package/src/database/engine.ts +62 -0
  305. package/src/database/engineAggregates.spec.ts +539 -0
  306. package/src/database/engineAggregates.ts +193 -0
  307. package/src/database/engineReads.spec.ts +219 -0
  308. package/src/database/engineReads.ts +302 -0
  309. package/src/database/engineTransaction.spec.ts +710 -0
  310. package/src/database/engineTransaction.ts +588 -0
  311. package/src/database/errors.ts +30 -0
  312. package/src/database/fence.spec.ts +223 -0
  313. package/src/database/fence.ts +171 -0
  314. package/src/database/index.ts +154 -0
  315. package/src/database/kv/kvAdapter.conformance.integration.spec.ts +17 -0
  316. package/src/database/kv/kvAdapter.readBudgets.integration.spec.ts +17 -0
  317. package/src/database/kv/kvAdapter.spec.ts +468 -0
  318. package/src/database/kv/kvAdapter.ts +450 -0
  319. package/src/database/kv/kvTestStore.ts +91 -0
  320. package/src/database/memory.readBudgets.integration.spec.ts +13 -0
  321. package/src/database/resolveSchema.spec.ts +230 -0
  322. package/src/database/resolveSchema.ts +372 -0
  323. package/src/database/schema.ts +169 -0
  324. package/src/database/schema.types.spec.ts +94 -0
  325. package/src/database/sql/docker-compose.yml +36 -0
  326. package/src/database/sql/sqlAdapter.integration.spec.ts +148 -0
  327. package/src/database/sql/sqlAdapter.readBudgets.integration.spec.ts +109 -0
  328. package/src/database/sql/sqlAdapter.spec.ts +255 -0
  329. package/src/database/sql/sqlAdapter.ts +532 -0
  330. package/src/database/sql/sqlSchema.ts +205 -0
  331. package/src/database/sql/sqlTestExecutors.ts +183 -0
  332. package/src/databaseTestSuite.spec.ts +39 -0
  333. package/src/db/builtInDatabase.ts +149 -0
  334. package/src/db/bundleRows.spec.ts +3 -3
  335. package/src/db/createBundleDiff.spec.ts +43 -29
  336. package/src/db/createBundleDiff.ts +13 -7
  337. package/src/db/engineDrizzleSchema.ts +72 -0
  338. package/src/db/engineMigrator.ts +105 -0
  339. package/src/db/enginePrismaSchema.ts +147 -0
  340. package/src/db/engineSql.spec.ts +129 -0
  341. package/src/db/engineSql.ts +48 -0
  342. package/src/db/engineSqlMigrator.ts +116 -0
  343. package/src/db/fixedMigratorShared.ts +1 -20
  344. package/src/db/index.spec.ts +116 -824
  345. package/src/db/index.ts +43 -14
  346. package/src/db/pluginTables.spec.ts +218 -0
  347. package/src/db/releaseCatalog.ts +48 -90
  348. package/src/db/schemaSettings.ts +104 -0
  349. package/src/db/settingsMigrator.spec.ts +65 -0
  350. package/src/db/settingsMigrator.ts +152 -0
  351. package/src/db/types.ts +25 -165
  352. package/src/db/updateArtifacts.spec.ts +170 -294
  353. package/src/db/updateArtifacts.ts +74 -142
  354. package/src/db/updateCheck.bench.ts +47 -0
  355. package/src/handler-standalone.integration.spec.ts +204 -412
  356. package/src/handler.spec.ts +134 -51
  357. package/src/handler.testFixtures.ts +14 -19
  358. package/src/handler.ts +163 -75
  359. package/src/handlerAdminRoutes.spec.ts +183 -0
  360. package/src/handlerAdminRoutes.ts +536 -0
  361. package/src/handlerAuth.integration.spec.ts +3 -3
  362. package/src/handlerManagement.spec.ts +136 -196
  363. package/src/handlerReleaseCatalogRoutes.ts +15 -17
  364. package/src/handlerTypes.ts +3 -80
  365. package/src/handlerVersionRoutes.ts +20 -7
  366. package/src/index.ts +7 -14
  367. package/src/insights/domain.ts +56 -77
  368. package/src/insights/errors.spec.ts +48 -0
  369. package/src/insights/errors.ts +50 -6
  370. package/src/insights/eventInput.ts +80 -27
  371. package/src/insights/insights.integration.spec.ts +525 -44
  372. package/src/insights/provider.spec.ts +574 -0
  373. package/src/insights/provider.ts +667 -0
  374. package/src/insights/queryInput.ts +113 -73
  375. package/src/insights/routes.ts +125 -69
  376. package/src/insights/types.ts +47 -36
  377. package/src/lintRules.spec.ts +76 -0
  378. package/src/plugins/api-keys/apiKeysPlugin.spec.ts +172 -0
  379. package/src/plugins/api-keys/index.ts +66 -0
  380. package/src/plugins/api-keys/model.ts +69 -0
  381. package/src/plugins/api-keys/schema.ts +22 -0
  382. package/src/plugins/builtIn.ts +5 -0
  383. package/src/plugins/definePlugin.ts +100 -0
  384. package/src/plugins/index.ts +55 -0
  385. package/src/plugins/insights/index.ts +86 -0
  386. package/src/plugins/insights/insightsRollout.integration.spec.ts +187 -0
  387. package/src/plugins/insights/model.spec.ts +66 -0
  388. package/src/plugins/insights/model.ts +19 -0
  389. package/src/plugins/insights/reads.spec.ts +725 -0
  390. package/src/plugins/insights/reads.ts +707 -0
  391. package/src/plugins/insights/recordEvent.spec.ts +443 -0
  392. package/src/plugins/insights/recordEvent.ts +238 -0
  393. package/src/plugins/insights/schema.ts +229 -0
  394. package/src/plugins/pluginTestHarness.spec.ts +56 -0
  395. package/src/readBudgets.testFixtures.ts +15 -0
  396. package/src/releaseCatalogRoutes.integration.spec.ts +47 -92
  397. package/src/runtime.spec.ts +91 -77
  398. package/src/runtime.testFixtures.ts +28 -44
  399. package/src/runtimeBundles.integration.spec.ts +112 -0
  400. package/src/testUtilsMutation.integration.spec.ts +215 -0
  401. package/src/testUtilsPackage.integration.spec.ts +176 -0
  402. package/src/types/index.ts +0 -32
  403. package/dist/adapters/databaseConstraintErrors.cjs +0 -23
  404. package/dist/adapters/databaseConstraintErrors.mjs +0 -21
  405. package/dist/adapters/databasePluginUtils.cjs +0 -117
  406. package/dist/adapters/databasePluginUtils.mjs +0 -106
  407. package/dist/adapters/drizzleCrud.cjs +0 -250
  408. package/dist/adapters/drizzleCrud.mjs +0 -249
  409. package/dist/adapters/drizzleLazyDB.cjs +0 -101
  410. package/dist/adapters/drizzleLazyDB.mjs +0 -101
  411. package/dist/adapters/drizzleQuery.cjs +0 -68
  412. package/dist/adapters/drizzleQuery.mjs +0 -67
  413. package/dist/adapters/kyselyCrud.cjs +0 -254
  414. package/dist/adapters/kyselyCrud.mjs +0 -253
  415. package/dist/adapters/kyselyQuery.cjs +0 -56
  416. package/dist/adapters/kyselyQuery.mjs +0 -55
  417. package/dist/adapters/mongodbCollections.cjs +0 -36
  418. package/dist/adapters/mongodbCollections.mjs +0 -30
  419. package/dist/adapters/mongodbQuery.cjs +0 -77
  420. package/dist/adapters/mongodbQuery.mjs +0 -70
  421. package/dist/adapters/mongodbReads.cjs +0 -148
  422. package/dist/adapters/mongodbReads.mjs +0 -147
  423. package/dist/adapters/mongodbWrites.cjs +0 -134
  424. package/dist/adapters/mongodbWrites.mjs +0 -133
  425. package/dist/adapters/prismaQuery.cjs +0 -58
  426. package/dist/adapters/prismaQuery.mjs +0 -56
  427. package/dist/adapters/prismaRows.cjs +0 -198
  428. package/dist/adapters/prismaRows.mjs +0 -188
  429. package/dist/db/databasePluginCore.cjs +0 -138
  430. package/dist/db/databasePluginCore.mjs +0 -137
  431. package/dist/db/fixedMigrator.cjs +0 -79
  432. package/dist/db/fixedMigrator.mjs +0 -78
  433. package/dist/db/fixedMigratorMongo.cjs +0 -319
  434. package/dist/db/fixedMigratorMongo.mjs +0 -319
  435. package/dist/db/mongoMigrationExecution.cjs +0 -11
  436. package/dist/db/mongoMigrationExecution.mjs +0 -11
  437. package/dist/db/schema/mongodb.cjs +0 -25
  438. package/dist/db/schema/mongodb.mjs +0 -25
  439. package/dist/db/schema/registry.cjs +0 -21
  440. package/dist/db/schema/registry.mjs +0 -17
  441. package/dist/db/schema/sql.cjs +0 -101
  442. package/dist/db/schema/sql.mjs +0 -97
  443. package/dist/db/schema/sqlOperations.cjs +0 -27
  444. package/dist/db/schema/sqlOperations.mjs +0 -26
  445. package/dist/db/schemaGenerators.cjs +0 -256
  446. package/dist/db/schemaGenerators.mjs +0 -254
  447. package/dist/db/schemaReadiness.cjs +0 -24
  448. package/dist/db/schemaReadiness.d.cts +0 -8
  449. package/dist/db/schemaReadiness.d.mts +0 -8
  450. package/dist/db/schemaReadiness.mjs +0 -23
  451. package/dist/db/sqlMigrationExecution.cjs +0 -67
  452. package/dist/db/sqlMigrationExecution.mjs +0 -66
  453. package/dist/handlerBundleRoutes.cjs +0 -155
  454. package/dist/handlerBundleRoutes.mjs +0 -155
  455. package/dist/handlerReleaseManagementRoutes.cjs +0 -125
  456. package/dist/handlerReleaseManagementRoutes.mjs +0 -124
  457. package/dist/insights/bounded/activeOverview.cjs +0 -87
  458. package/dist/insights/bounded/activeOverview.mjs +0 -85
  459. package/dist/insights/bounded/installationSearch.cjs +0 -42
  460. package/dist/insights/bounded/installationSearch.mjs +0 -42
  461. package/dist/insights/bounded/persistence.cjs +0 -39
  462. package/dist/insights/bounded/persistence.mjs +0 -38
  463. package/dist/insights/bounded/provider.cjs +0 -170
  464. package/dist/insights/bounded/provider.d.cts +0 -7
  465. package/dist/insights/bounded/provider.d.mts +0 -7
  466. package/dist/insights/bounded/provider.mjs +0 -170
  467. package/dist/insights/bounded/scan.cjs +0 -144
  468. package/dist/insights/bounded/scan.mjs +0 -138
  469. package/dist/insights/persistence.d.cts +0 -6
  470. package/dist/insights/persistence.d.mts +0 -6
  471. package/dist/schema/dsl.cjs +0 -103
  472. package/dist/schema/dsl.mjs +0 -87
  473. package/dist/schema/index.cjs +0 -6
  474. package/dist/schema/index.mjs +0 -7
  475. package/dist/schema/settings.cjs +0 -9
  476. package/dist/schema/settings.mjs +0 -9
  477. package/dist/schema/types.cjs +0 -8
  478. package/dist/schema/types.mjs +0 -6
  479. package/dist/schema/v1_0_0.cjs +0 -364
  480. package/dist/schema/v1_0_0.mjs +0 -357
  481. package/src/adapters/databaseConstraintErrors.ts +0 -25
  482. package/src/adapters/databasePluginTestDatabase.ts +0 -106
  483. package/src/adapters/databasePluginUtils.spec.ts +0 -29
  484. package/src/adapters/databasePluginUtils.ts +0 -214
  485. package/src/adapters/drizzleCrud.ts +0 -419
  486. package/src/adapters/drizzleLazyDB.ts +0 -257
  487. package/src/adapters/drizzleQuery.ts +0 -164
  488. package/src/adapters/kyselyCrud.ts +0 -588
  489. package/src/adapters/kyselyQuery.ts +0 -128
  490. package/src/adapters/mongodb.spec.ts +0 -141
  491. package/src/adapters/mongodbCollections.ts +0 -62
  492. package/src/adapters/mongodbQuery.ts +0 -226
  493. package/src/adapters/mongodbReads.ts +0 -311
  494. package/src/adapters/mongodbRows.spec.ts +0 -64
  495. package/src/adapters/mongodbRows.ts +0 -118
  496. package/src/adapters/mongodbTestClient.ts +0 -342
  497. package/src/adapters/mongodbTestFilter.ts +0 -166
  498. package/src/adapters/mongodbWrites.ts +0 -260
  499. package/src/adapters/prismaQuery.ts +0 -129
  500. package/src/adapters/prismaRows.spec.ts +0 -98
  501. package/src/adapters/prismaRows.ts +0 -301
  502. package/src/adapters/prismaTestClient.ts +0 -396
  503. package/src/calculatePagination.ts +0 -34
  504. package/src/db/databasePluginCore.spec.ts +0 -62
  505. package/src/db/databasePluginCore.testFixtures.ts +0 -76
  506. package/src/db/databasePluginCore.ts +0 -185
  507. package/src/db/databasePluginUpdateCheck.bench.ts +0 -107
  508. package/src/db/fixedMigrator.spec.ts +0 -231
  509. package/src/db/fixedMigrator.ts +0 -147
  510. package/src/db/fixedMigratorMongo.spec.ts +0 -192
  511. package/src/db/fixedMigratorMongo.ts +0 -425
  512. package/src/db/hotUpdaterSchema.ts +0 -5
  513. package/src/db/mongoMigrationExecution.ts +0 -23
  514. package/src/db/schema/definitions.ts +0 -1
  515. package/src/db/schema/mongodb.ts +0 -40
  516. package/src/db/schema/registry.ts +0 -55
  517. package/src/db/schema/sql.ts +0 -241
  518. package/src/db/schema/sqlOperations.ts +0 -63
  519. package/src/db/schema/types.ts +0 -1
  520. package/src/db/schemaGenerators.ts +0 -466
  521. package/src/db/schemaReadiness.ts +0 -34
  522. package/src/db/sqlMigrationExecution.spec.ts +0 -65
  523. package/src/db/sqlMigrationExecution.ts +0 -108
  524. package/src/db/types.spec.ts +0 -55
  525. package/src/handlerBundleRoutes.ts +0 -259
  526. package/src/handlerReleaseManagementRoutes.ts +0 -213
  527. package/src/insights/bounded/activeOverview.spec.ts +0 -189
  528. package/src/insights/bounded/activeOverview.ts +0 -148
  529. package/src/insights/bounded/installationSearch.spec.ts +0 -130
  530. package/src/insights/bounded/installationSearch.ts +0 -69
  531. package/src/insights/bounded/persistence.ts +0 -43
  532. package/src/insights/bounded/provider.spec.ts +0 -219
  533. package/src/insights/bounded/provider.ts +0 -229
  534. package/src/insights/bounded/scan.ts +0 -235
  535. package/src/insights/persistence.ts +0 -10
  536. package/src/schema/dsl-all-versions.spec.ts +0 -35
  537. package/src/schema/dsl.ts +0 -187
  538. package/src/schema/index.ts +0 -10
  539. package/src/schema/settings.ts +0 -15
  540. package/src/schema/types.ts +0 -79
  541. package/src/schema/v1_0_0.spec.ts +0 -146
  542. package/src/schema/v1_0_0.ts +0 -465
@@ -0,0 +1,308 @@
1
+ import {
2
+ createReleaseCatalogScopeKey,
3
+ encodeChannelKey,
4
+ } from "@hot-updater/core";
5
+ import {
6
+ compileReleaseCatalog,
7
+ releaseRowToRelease,
8
+ type BundleRow,
9
+ type ReleaseCatalogScope,
10
+ type ReleaseRow,
11
+ } from "@hot-updater/plugin-core";
12
+ import { createMemoryAdapter } from "@hot-updater/plugin-core/internal";
13
+ import { describe, expect, it } from "vitest";
14
+
15
+ import {
16
+ createBundlePatchRowFixture,
17
+ createBundleRowFixture,
18
+ } from "../../../test-utils/src/databaseTestFixtures";
19
+ import { createDatabaseEngine } from "../database/database";
20
+ import { resolveSchema } from "../database/resolveSchema";
21
+ import {
22
+ changeReleases,
23
+ coreModule,
24
+ createCoreReads,
25
+ insertBundle,
26
+ insertChannel,
27
+ rebuildCatalog,
28
+ targetBaseCandidateKey,
29
+ type ReleaseChangeInput,
30
+ } from "./index";
31
+
32
+ const channel = { id: "channel-production", name: "production" };
33
+ const channelKey = encodeChannelKey(channel.name);
34
+ const scope: ReleaseCatalogScope = {
35
+ channelId: channel.id,
36
+ channelName: channel.name,
37
+ platform: "ios",
38
+ scopeKey: createReleaseCatalogScopeKey({
39
+ channelKey,
40
+ platform: "ios",
41
+ strategy: "APP_VERSION",
42
+ }),
43
+ strategy: "APP_VERSION",
44
+ fingerprintHash: null,
45
+ };
46
+
47
+ const releaseOf = (bundle: BundleRow): Omit<ReleaseRow, "id"> => ({
48
+ revision: 1,
49
+ scope_key: scope.scopeKey,
50
+ channel_id: channel.id,
51
+ platform: "ios",
52
+ kind: "BUNDLE",
53
+ bundle_id: bundle.id,
54
+ strategy: "APP_VERSION",
55
+ target_app_version: "1.0.0",
56
+ fingerprint_hash: null,
57
+ enabled: true,
58
+ should_force_update: false,
59
+ message: null,
60
+ rollout_cohort_count: 1000,
61
+ target_cohorts: [],
62
+ operation: "DEPLOY",
63
+ source_release_id: null,
64
+ created_at_ms: 1,
65
+ updated_at_ms: 1,
66
+ });
67
+
68
+ const deploy = (bundle: BundleRow): ReleaseChangeInput => ({
69
+ scope,
70
+ bundle: { row: bundle, patches: [] },
71
+ change: { operation: "insert", row: releaseOf(bundle) },
72
+ updatedAtMs: 10,
73
+ });
74
+
75
+ const setup = async () => {
76
+ const adapter = createMemoryAdapter();
77
+ const schema = resolveSchema([coreModule]);
78
+ const engine = createDatabaseEngine({ adapter, schema, verify: true });
79
+ const db = engine.database(coreModule);
80
+ await insertChannel(db, channel);
81
+ return {
82
+ adapter,
83
+ schema,
84
+ engine,
85
+ db,
86
+ reads: createCoreReads(db, { resolveFileUrl: async () => null }),
87
+ };
88
+ };
89
+
90
+ const baseKey = targetBaseCandidateKey({
91
+ channel: channel.name,
92
+ platform: "ios",
93
+ fingerprintHash: null,
94
+ appVersion: "1.0.0",
95
+ })!;
96
+ const NEWEST = "ffffffff-ffff-7fff-bfff-ffffffffffff";
97
+
98
+ describe("release changes rooted at the catalog", () => {
99
+ it("deploys a bundle with its release and compiles the scope's catalog in one transaction", async () => {
100
+ const { db, reads } = await setup();
101
+ const bundle = createBundleRowFixture("501");
102
+ const patchBase = createBundleRowFixture("500");
103
+ await db.transaction(async (tx) => insertBundle(tx, patchBase));
104
+ const patch = createBundlePatchRowFixture("1", bundle.id, patchBase.id);
105
+ const [result] = await changeReleases(db, [
106
+ { ...deploy(bundle), bundle: { row: bundle, patches: [patch] } },
107
+ ]);
108
+ const release = result!.release!;
109
+ expect(release.id > bundle.id).toBe(true);
110
+ const compiled = await compileReleaseCatalog({
111
+ strategy: "APP_VERSION",
112
+ releases: [releaseRowToRelease(release)],
113
+ });
114
+ expect(result!.catalog).toMatchObject({
115
+ scope_key: scope.scopeKey,
116
+ channel_key: channelKey,
117
+ generation: 1,
118
+ payload: compiled.canonicalPayload,
119
+ catalog_hash: compiled.catalogHash,
120
+ is_tombstone: false,
121
+ updated_at_ms: 10,
122
+ });
123
+ await expect(
124
+ reads.getReleaseCatalog({
125
+ strategy: "APP_VERSION",
126
+ platform: "ios",
127
+ channelKey,
128
+ appVersion: "1.0.0",
129
+ }),
130
+ ).resolves.toMatchObject({
131
+ generation: 1,
132
+ releases: [{ releaseId: release.id, bundleId: bundle.id }],
133
+ });
134
+ await expect(reads.getBundle(bundle.id)).resolves.toMatchObject({
135
+ patches: [patch],
136
+ });
137
+ await expect(reads.countBundles("ios")).resolves.toBe(2);
138
+ await expect(reads.findBaseBundleIds(baseKey, NEWEST, 3)).resolves.toEqual([
139
+ bundle.id,
140
+ ]);
141
+ });
142
+
143
+ it("updates and deletes a release with the next catalog generation each", async () => {
144
+ const { db, reads } = await setup();
145
+ const bundle = createBundleRowFixture("511");
146
+ const [deployed] = await changeReleases(db, [deploy(bundle)]);
147
+ const id = deployed!.release!.id;
148
+
149
+ const [disabled] = await changeReleases(db, [
150
+ {
151
+ scope,
152
+ change: { operation: "update", id, update: { enabled: false } },
153
+ updatedAtMs: 20,
154
+ },
155
+ ]);
156
+ expect(disabled!.release).toMatchObject({ revision: 2, enabled: false });
157
+ expect(disabled!.catalog).toMatchObject({
158
+ generation: 2,
159
+ is_tombstone: true,
160
+ catalog_id: deployed!.catalog.catalog_id,
161
+ });
162
+ await expect(reads.findBaseBundleIds(baseKey, NEWEST, 3)).resolves.toEqual(
163
+ [],
164
+ );
165
+
166
+ const [deleted] = await changeReleases(db, [
167
+ { scope, change: { operation: "delete", id }, updatedAtMs: 30 },
168
+ ]);
169
+ expect(deleted).toMatchObject({
170
+ release: null,
171
+ catalog: { generation: 3 },
172
+ });
173
+ await expect(reads.getRelease(id)).resolves.toBeNull();
174
+ });
175
+
176
+ it("orders ids after the scope's latest release and refuses one that does not sort after it", async () => {
177
+ const { db } = await setup();
178
+ const first = createBundleRowFixture("521");
179
+ const second = createBundleRowFixture("522");
180
+ const [one] = await changeReleases(db, [deploy(first)]);
181
+ const [two] = await changeReleases(db, [deploy(second)]);
182
+ expect(two!.release!.id > one!.release!.id).toBe(true);
183
+
184
+ await expect(
185
+ changeReleases(db, [
186
+ {
187
+ scope,
188
+ change: {
189
+ operation: "insert",
190
+ row: { ...releaseOf(first), id: one!.release!.id },
191
+ },
192
+ updatedAtMs: 40,
193
+ },
194
+ ]),
195
+ ).rejects.toMatchObject({ code: "NON_MONOTONIC_RELEASE_ID" });
196
+ });
197
+
198
+ it("refuses a release outside its scope and a scope whose catalog history is missing", async () => {
199
+ const { adapter, schema, db } = await setup();
200
+ await expect(
201
+ changeReleases(db, [
202
+ {
203
+ scope,
204
+ change: { operation: "delete", id: NEWEST },
205
+ updatedAtMs: 50,
206
+ },
207
+ ]),
208
+ ).rejects.toMatchObject({ code: "RELEASE_NOT_FOUND" });
209
+
210
+ await changeReleases(db, [deploy(createBundleRowFixture("531"))]);
211
+ // A restore that lost the catalog row: a write under the engine, which
212
+ // keeps a catalog while its scope has releases.
213
+ const table = schema.models.get("release_catalogs")!.table;
214
+ const [row] = await adapter.get(table, [[scope.scopeKey]]);
215
+ await adapter.write([
216
+ {
217
+ type: "delete",
218
+ table,
219
+ key: [scope.scopeKey],
220
+ guard: { v: Number(row!._v) },
221
+ previous: row!,
222
+ },
223
+ ]);
224
+ await expect(
225
+ changeReleases(db, [deploy(createBundleRowFixture("532"))]),
226
+ ).rejects.toMatchObject({ code: "CATALOG_IDENTITY_MISSING" });
227
+ await expect(rebuildCatalog(db, scope, 60)).rejects.toMatchObject({
228
+ code: "CATALOG_IDENTITY_MISSING",
229
+ });
230
+ });
231
+
232
+ it("serializes concurrent deploys in one scope through its catalog row", async () => {
233
+ const { db, reads } = await setup();
234
+ const bundles = Array.from({ length: 8 }, (_, n) =>
235
+ createBundleRowFixture(String(541 + n)),
236
+ );
237
+ const results = await Promise.all(
238
+ bundles.map((bundle) => changeReleases(db, [deploy(bundle)])),
239
+ );
240
+ const generations = results.map(([result]) => result!.catalog.generation);
241
+ expect(generations.toSorted((a, b) => a - b)).toEqual([
242
+ 1, 2, 3, 4, 5, 6, 7, 8,
243
+ ]);
244
+ const byGeneration = results
245
+ .map(([result]) => result!)
246
+ .sort((a, b) => a.catalog.generation - b.catalog.generation);
247
+ const ids = byGeneration.map(({ release }) => release!.id);
248
+ expect(ids).toEqual(ids.toSorted());
249
+ await expect(
250
+ reads.getReleaseCatalogRow(scope.scopeKey),
251
+ ).resolves.toMatchObject({ generation: 8 });
252
+ await expect(
253
+ reads.listReleases({
254
+ filter: { kind: "scope", scopeKey: scope.scopeKey },
255
+ limit: 10,
256
+ }),
257
+ ).resolves.toHaveLength(8);
258
+ });
259
+
260
+ it("reads only the scope's enabled releases and its latest id, all of them used", async () => {
261
+ const { engine, db } = await setup();
262
+ const bundles = Array.from({ length: 5 }, (_, n) =>
263
+ createBundleRowFixture(String(551 + n)),
264
+ );
265
+ const deployed: string[] = [];
266
+ for (const bundle of bundles) {
267
+ const [result] = await changeReleases(db, [deploy(bundle)]);
268
+ deployed.push(result!.release!.id);
269
+ }
270
+ for (const id of deployed.slice(0, 2)) {
271
+ await changeReleases(db, [
272
+ {
273
+ scope,
274
+ change: { operation: "update", id, update: { enabled: false } },
275
+ updatedAtMs: 70,
276
+ },
277
+ ]);
278
+ }
279
+ const measured = await engine.measureReads(() =>
280
+ changeReleases(db, [deploy(createBundleRowFixture("556"))]),
281
+ );
282
+ // three enabled releases for the compile, one row for the latest id
283
+ expect(measured.adapter.rows).toBe(4);
284
+ expect(measured.adapter.queries).toBe(2);
285
+ });
286
+
287
+ it("rebuilds a catalog only when its releases changed underneath it", async () => {
288
+ const { db } = await setup();
289
+ const [deployed] = await changeReleases(db, [
290
+ deploy(createBundleRowFixture("561")),
291
+ ]);
292
+ await expect(rebuildCatalog(db, scope, 80)).resolves.toMatchObject({
293
+ changed: false,
294
+ catalog: { generation: 1 },
295
+ });
296
+ // The release changes underneath the catalog, as only a raw write does.
297
+ await db.transaction(async (tx) => {
298
+ const stored = await tx.findOne("releases", {
299
+ id: deployed!.release!.id,
300
+ });
301
+ tx.update("releases", stored!, { message: "hotfix", revision: 2 });
302
+ });
303
+ await expect(rebuildCatalog(db, scope, 90)).resolves.toMatchObject({
304
+ changed: true,
305
+ catalog: { generation: 2, updated_at_ms: 90 },
306
+ });
307
+ });
308
+ });
@@ -0,0 +1,345 @@
1
+ import {
2
+ createReleaseCatalogScopeKey,
3
+ encodeChannelKey,
4
+ } from "@hot-updater/core";
5
+ import {
6
+ compileReleaseCatalog,
7
+ createUUIDv7,
8
+ createUUIDv7After,
9
+ isUUIDv7,
10
+ releaseRowToRelease,
11
+ ReleaseCatalogMutationError,
12
+ type BundlePatchRow,
13
+ type BundleRow,
14
+ type ReleaseCatalogCompilation,
15
+ type ReleaseCatalogRow,
16
+ type ReleaseCatalogScope,
17
+ type ReleaseRow,
18
+ type ReleaseRowUpdate,
19
+ } from "@hot-updater/plugin-core";
20
+
21
+ import { toCatalogRow, toReleaseRow, type CoreDatabase } from "./reads";
22
+ import { insertBundle, type CoreTransaction } from "./writes";
23
+
24
+ const PAGE = 500;
25
+
26
+ /** One change to one release, applied with its scope's catalog. */
27
+ export type ReleaseChange =
28
+ /** `id` is assigned after the scope's latest release unless given. */
29
+ | {
30
+ readonly operation: "insert";
31
+ readonly row: Omit<ReleaseRow, "id"> & { readonly id?: string };
32
+ }
33
+ | {
34
+ readonly operation: "update";
35
+ readonly id: string;
36
+ readonly update: Omit<ReleaseRowUpdate, "revision" | "scope_key">;
37
+ }
38
+ | { readonly operation: "delete"; readonly id: string };
39
+
40
+ export interface ReleaseChangeInput {
41
+ readonly scope: ReleaseCatalogScope;
42
+ readonly change: ReleaseChange;
43
+ /** A deploy's new bundle and its patches, written first. */
44
+ readonly bundle?: {
45
+ readonly row: BundleRow;
46
+ readonly patches: readonly BundlePatchRow[];
47
+ };
48
+ readonly updatedAtMs: number;
49
+ }
50
+
51
+ export interface ReleaseChangeResult {
52
+ readonly catalog: ReleaseCatalogRow;
53
+ readonly release: ReleaseRow | null;
54
+ readonly diagnostics: ReleaseCatalogCompilation["diagnostics"];
55
+ }
56
+
57
+ const fail = (
58
+ code: ConstructorParameters<typeof ReleaseCatalogMutationError>[0],
59
+ message: string,
60
+ ): never => {
61
+ throw new ReleaseCatalogMutationError(code, message);
62
+ };
63
+
64
+ const checkScope = (scope: ReleaseCatalogScope): void => {
65
+ const channelKey = encodeChannelKey(scope.channelName);
66
+ const expected =
67
+ scope.strategy === "APP_VERSION"
68
+ ? createReleaseCatalogScopeKey({
69
+ channelKey,
70
+ platform: scope.platform,
71
+ strategy: "APP_VERSION",
72
+ })
73
+ : createReleaseCatalogScopeKey({
74
+ channelKey,
75
+ fingerprintHash: scope.fingerprintHash ?? "",
76
+ platform: scope.platform,
77
+ strategy: "FINGERPRINT",
78
+ });
79
+ if (
80
+ (scope.strategy === "FINGERPRINT") !== (scope.fingerprintHash !== null) ||
81
+ scope.scopeKey !== expected
82
+ ) {
83
+ fail("INVALID_SCOPE", "Release catalog scope metadata is inconsistent");
84
+ }
85
+ };
86
+
87
+ /** A scope's enabled releases, all of them, under the catalog's guard. */
88
+ const enabledReleases = async (
89
+ tx: CoreTransaction,
90
+ scopeKey: string,
91
+ ): Promise<ReleaseRow[]> => {
92
+ const rows: ReleaseRow[] = [];
93
+ let cursor: string | undefined;
94
+ do {
95
+ const page = await tx.findMany("releases", {
96
+ index: "byScopeEnabled",
97
+ where: { scope_key: scopeKey, enabled: true },
98
+ limit: PAGE,
99
+ ...(cursor === undefined ? {} : { cursor }),
100
+ });
101
+ rows.push(...page.rows.map(toReleaseRow));
102
+ cursor = page.next;
103
+ } while (cursor !== undefined);
104
+ return rows;
105
+ };
106
+
107
+ /** The scope's newest release id: `byScope` descending, limit 1. */
108
+ const latestReleaseId = async (tx: CoreTransaction, scopeKey: string) => {
109
+ const { rows } = await tx.findMany("releases", {
110
+ index: "byScope",
111
+ where: { scope_key: scopeKey },
112
+ order: "desc",
113
+ limit: 1,
114
+ });
115
+ return rows[0]?.id ?? null;
116
+ };
117
+
118
+ /** Compiles `enabled` and writes it as the scope's next catalog generation. */
119
+ const writeCatalog = async (
120
+ tx: CoreTransaction,
121
+ scope: ReleaseCatalogScope,
122
+ stored: Awaited<ReturnType<typeof readCatalog>>,
123
+ enabled: readonly ReleaseRow[],
124
+ updatedAtMs: number,
125
+ ) => {
126
+ const compilation = await compileReleaseCatalog({
127
+ strategy: scope.strategy,
128
+ releases: enabled.map(releaseRowToRelease),
129
+ });
130
+ const generation = (stored.generation ?? 0) + 1;
131
+ if (!Number.isSafeInteger(generation)) {
132
+ fail(
133
+ "CATALOG_GENERATION_EXHAUSTED",
134
+ `Catalog "${scope.scopeKey}" exhausted its generation counter`,
135
+ );
136
+ }
137
+ const catalog: ReleaseCatalogRow = {
138
+ scope_key: scope.scopeKey,
139
+ catalog_id: stored.catalog?.catalog_id ?? createUUIDv7(),
140
+ strategy: scope.strategy,
141
+ channel_id: scope.channelId,
142
+ channel_key: encodeChannelKey(scope.channelName),
143
+ platform: scope.platform,
144
+ fingerprint_hash: scope.fingerprintHash,
145
+ generation,
146
+ payload: compilation.canonicalPayload,
147
+ catalog_hash: compilation.catalogHash,
148
+ byte_size: compilation.byteSize,
149
+ is_tombstone: compilation.payload.releaseDescriptors.length === 0,
150
+ updated_at_ms: updatedAtMs,
151
+ };
152
+ const { scope_key: _scopeKey, ...set } = catalog;
153
+ if (stored.row === null) tx.create("release_catalogs", catalog);
154
+ else tx.update("release_catalogs", stored.row, set);
155
+ return { catalog, compilation };
156
+ };
157
+
158
+ /** The scope's catalog row, and its compiled catalog and generation if it has one. */
159
+ export const readCatalog = async (tx: CoreTransaction, scopeKey: string) => {
160
+ const row = await tx.findOne("release_catalogs", { scope_key: scopeKey });
161
+ return {
162
+ row,
163
+ generation: row?.generation ?? null,
164
+ catalog: row === null ? null : toCatalogRow(row),
165
+ };
166
+ };
167
+
168
+ /** A scope with releases and no compiled catalog lost its history. */
169
+ const assertHistory = (
170
+ stored: Awaited<ReturnType<typeof readCatalog>>,
171
+ hasReleases: boolean,
172
+ ) => {
173
+ if (stored.catalog === null && hasReleases) {
174
+ fail(
175
+ "CATALOG_IDENTITY_MISSING",
176
+ "Catalog history is missing. Restore its catalog row from backup before rebuilding or deploying.",
177
+ );
178
+ }
179
+ };
180
+
181
+ /** One release change and its scope's next catalog, inside `tx`. */
182
+ export const changeRelease = async (
183
+ tx: CoreTransaction,
184
+ { scope, change, bundle, updatedAtMs }: ReleaseChangeInput,
185
+ ): Promise<ReleaseChangeResult> => {
186
+ checkScope(scope);
187
+ const stored = await readCatalog(tx, scope.scopeKey);
188
+ const enabled = await enabledReleases(tx, scope.scopeKey);
189
+ if (bundle !== undefined) {
190
+ insertBundle(tx, bundle.row);
191
+ for (const patch of bundle.patches) tx.create("bundle_patches", patch);
192
+ }
193
+ let before: ReleaseRow | null = null;
194
+ let release: ReleaseRow | null;
195
+ if (change.operation === "insert") {
196
+ const latest = await latestReleaseId(tx, scope.scopeKey);
197
+ assertHistory(stored, latest !== null);
198
+ const floor = [
199
+ latest,
200
+ bundle && isUUIDv7(bundle.row.id) ? bundle.row.id : null,
201
+ ]
202
+ .filter((id): id is string => id !== null)
203
+ .sort()
204
+ .at(-1);
205
+ const id = change.row.id ?? createUUIDv7After(floor ?? null);
206
+ if (latest !== null && id <= latest) {
207
+ fail(
208
+ "NON_MONOTONIC_RELEASE_ID",
209
+ `Release "${id}" must sort after "${latest}"`,
210
+ );
211
+ }
212
+ release = { ...change.row, id };
213
+ if (
214
+ release.scope_key !== scope.scopeKey ||
215
+ release.channel_id !== scope.channelId ||
216
+ release.platform !== scope.platform ||
217
+ release.strategy !== scope.strategy ||
218
+ release.fingerprint_hash !== scope.fingerprintHash ||
219
+ release.revision !== 1
220
+ ) {
221
+ fail(
222
+ "INVALID_SCOPE",
223
+ "Inserted Release does not match its catalog scope",
224
+ );
225
+ }
226
+ tx.create("releases", release);
227
+ } else {
228
+ const row = await tx.findOne("releases", { id: change.id });
229
+ if (row === null || row.scope_key !== scope.scopeKey) {
230
+ return fail(
231
+ "RELEASE_NOT_FOUND",
232
+ `Release "${change.id}" was not found in scope "${scope.scopeKey}"`,
233
+ );
234
+ }
235
+ assertHistory(stored, true);
236
+ before = toReleaseRow(row);
237
+ if (change.operation === "delete") {
238
+ await tx.delete("releases", row);
239
+ release = null;
240
+ } else {
241
+ const set = { ...change.update, revision: before.revision + 1 };
242
+ tx.update("releases", row, set);
243
+ release = { ...before, ...set };
244
+ }
245
+ }
246
+ const changedId = before?.id ?? release?.id;
247
+ const next = [
248
+ ...enabled.filter(({ id }) => id !== changedId),
249
+ ...(release?.enabled ? [release] : []),
250
+ ].sort((left, right) => (left.id < right.id ? -1 : 1));
251
+ const { catalog, compilation } = await writeCatalog(
252
+ tx,
253
+ scope,
254
+ stored,
255
+ next,
256
+ updatedAtMs,
257
+ );
258
+ return { catalog, release, diagnostics: compilation.diagnostics };
259
+ };
260
+
261
+ /**
262
+ * Release changes rooted at their catalogs: each reads its scope's catalog
263
+ * and enabled releases, applies the change, and writes the next catalog
264
+ * generation, all in one transaction. A concurrent change in a scope bumps
265
+ * its catalog row, so this transaction reruns instead of publishing a stale
266
+ * catalog.
267
+ */
268
+ export const changeReleases = (
269
+ db: CoreDatabase,
270
+ inputs: readonly ReleaseChangeInput[],
271
+ ): Promise<ReleaseChangeResult[]> => {
272
+ const scopes = new Set(inputs.map(({ scope }) => scope.scopeKey));
273
+ if (scopes.size !== inputs.length) {
274
+ fail("INVALID_SCOPE", "A batch cannot change one catalog scope twice");
275
+ }
276
+ return db.transaction(async (tx) => {
277
+ const results: ReleaseChangeResult[] = [];
278
+ for (const input of inputs) results.push(await changeRelease(tx, input));
279
+ return results;
280
+ });
281
+ };
282
+
283
+ /** Recompiles a scope's catalog from its enabled releases inside `tx`; an unchanged catalog is not rewritten. */
284
+ export const rebuildScope = async (
285
+ tx: CoreTransaction,
286
+ scope: ReleaseCatalogScope,
287
+ updatedAtMs: number,
288
+ ): Promise<{
289
+ readonly catalog: ReleaseCatalogRow;
290
+ readonly current: ReleaseCatalogRow | null;
291
+ readonly changed: boolean;
292
+ readonly diagnostics: ReleaseCatalogCompilation["diagnostics"];
293
+ }> => {
294
+ checkScope(scope);
295
+ const stored = await readCatalog(tx, scope.scopeKey);
296
+ assertHistory(stored, (await latestReleaseId(tx, scope.scopeKey)) !== null);
297
+ const enabled = await enabledReleases(tx, scope.scopeKey);
298
+ const compilation = await compileReleaseCatalog({
299
+ strategy: scope.strategy,
300
+ releases: enabled.map(releaseRowToRelease),
301
+ });
302
+ const current = stored.catalog;
303
+ if (
304
+ current !== null &&
305
+ current.strategy === scope.strategy &&
306
+ current.channel_id === scope.channelId &&
307
+ current.channel_key === encodeChannelKey(scope.channelName) &&
308
+ current.platform === scope.platform &&
309
+ current.fingerprint_hash === scope.fingerprintHash &&
310
+ current.catalog_hash === compilation.catalogHash
311
+ ) {
312
+ return {
313
+ catalog: current,
314
+ current,
315
+ changed: false,
316
+ diagnostics: compilation.diagnostics,
317
+ };
318
+ }
319
+ const written = await writeCatalog(tx, scope, stored, enabled, updatedAtMs);
320
+ return {
321
+ catalog: written.catalog,
322
+ current,
323
+ changed: true,
324
+ diagnostics: written.compilation.diagnostics,
325
+ };
326
+ };
327
+
328
+ /** Recompiles a scope's catalog from its enabled releases; an unchanged catalog is not rewritten. */
329
+ export const rebuildCatalog = (
330
+ db: CoreDatabase,
331
+ scope: ReleaseCatalogScope,
332
+ updatedAtMs: number,
333
+ ): Promise<{
334
+ readonly catalog: ReleaseCatalogRow;
335
+ readonly changed: boolean;
336
+ readonly diagnostics: ReleaseCatalogCompilation["diagnostics"];
337
+ }> =>
338
+ db.transaction(async (tx) => {
339
+ const { current: _current, ...rebuilt } = await rebuildScope(
340
+ tx,
341
+ scope,
342
+ updatedAtMs,
343
+ );
344
+ return rebuilt;
345
+ });