@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,707 @@
1
+ import {
2
+ DatabasePluginInputError,
3
+ type BundleEventRow,
4
+ type InsightsBundleEventFilter,
5
+ type InsightsCountEventsInput,
6
+ type InsightsCountLatestEventsInput,
7
+ type InsightsFindLatestEventsInput,
8
+ type InsightsGetAppUsageInput,
9
+ type InsightsGetAppUsageResult,
10
+ type InsightsGetReleaseActivityInput,
11
+ type InsightsGetReleaseActivityResult,
12
+ type InsightsListEventsInput,
13
+ type InsightsTimeRange,
14
+ type ReleaseActivityMetrics,
15
+ type ReleaseReference,
16
+ } from "@hot-updater/plugin-core";
17
+ import {
18
+ countInsightsDistinct,
19
+ mergeInsightsDistinct,
20
+ } from "@hot-updater/plugin-core/internal";
21
+
22
+ import type { HotUpdaterDatabase } from "../../database/database";
23
+ import type { Page } from "../../database/engineReads";
24
+ import { EVENT_LIST_RANGE_MS } from "../../insights/provider";
25
+ import {
26
+ bundlePairKey,
27
+ insightsIdentity,
28
+ PAIR_FIELD,
29
+ type InsightsIdentityParts,
30
+ } from "./recordEvent";
31
+ import { DAILY_EVENTS, DAY_MS, HOUR_MS, type InsightsSchema } from "./schema";
32
+
33
+ type Db = HotUpdaterDatabase<InsightsSchema>;
34
+ type Parts = Omit<InsightsIdentityParts, "periodKind">;
35
+ /** The lists that read one UTC day a query: global and bundle. */
36
+ type DayFilter = Exclude<
37
+ InsightsListEventsInput["filter"],
38
+ { readonly kind: "installationMovement" }
39
+ >;
40
+ /** Receipt bounds: from `since` to the cutoff, or to the cursor's row. */
41
+ interface EventRange {
42
+ readonly gte: number;
43
+ readonly lt: number | readonly [number, string];
44
+ }
45
+
46
+ const PAGE = 500;
47
+
48
+ const hourFloor = (ms: number) => ms - (ms % HOUR_MS);
49
+ const hourCeil = (ms: number) => hourFloor(ms + HOUR_MS - 1);
50
+ const dayFloor = (ms: number) => ms - (ms % DAY_MS);
51
+ const dayCeil = (ms: number) => dayFloor(ms + DAY_MS - 1);
52
+
53
+ /** Every page of one read, in order. */
54
+ const drain = async <T>(
55
+ read: (page: { readonly cursor?: string }) => Promise<Page<T>>,
56
+ ): Promise<T[]> => {
57
+ const rows: T[] = [];
58
+ let cursor: string | undefined;
59
+ do {
60
+ const page = await read(cursor === undefined ? {} : { cursor });
61
+ rows.push(...page.rows);
62
+ cursor = page.next;
63
+ } while (cursor !== undefined);
64
+ return rows;
65
+ };
66
+
67
+ const EVENT_FIELDS = [
68
+ "id",
69
+ "type",
70
+ "install_id",
71
+ "user_id",
72
+ "from_release_id",
73
+ "from_bundle_id",
74
+ "to_release_id",
75
+ "to_bundle_id",
76
+ "platform",
77
+ "app_version",
78
+ "channel",
79
+ "metadata",
80
+ "received_at_ms",
81
+ ] as const;
82
+
83
+ /** A stored event or head, as the event row it holds. */
84
+ const toEvent = (row: Readonly<Record<string, unknown>>): BundleEventRow =>
85
+ Object.fromEntries(
86
+ EVENT_FIELDS.map((field) => [field, row[field]]),
87
+ ) as unknown as BundleEventRow;
88
+
89
+ const bundleRef = (filter: InsightsBundleEventFilter) =>
90
+ filter.type === "RECOVERED"
91
+ ? `from:${filter.fromBundleId}`
92
+ : `to:${filter.toBundleId}`;
93
+
94
+ const scopeOf = (filter: InsightsBundleEventFilter) => ({
95
+ platform: filter.platform,
96
+ channel: filter.channel,
97
+ type: filter.type,
98
+ bundle_ref: bundleRef(filter),
99
+ });
100
+
101
+ /** One UTC day of a global or bundle list, newest first. */
102
+ const eventsOfDay = (
103
+ db: Db,
104
+ filter: DayFilter,
105
+ day: number,
106
+ range: EventRange,
107
+ limit: number,
108
+ ) =>
109
+ filter.kind === "all"
110
+ ? db.findMany("bundle_events", {
111
+ index: "byDay",
112
+ where: { day },
113
+ range,
114
+ order: "desc",
115
+ limit,
116
+ })
117
+ : db.findMany("bundle_events", {
118
+ index: "byBundle",
119
+ where: { ...scopeOf(filter), day },
120
+ range,
121
+ order: "desc",
122
+ limit,
123
+ });
124
+
125
+ /**
126
+ * The newest UTC day before `day`, and not before `since`'s, that holds an
127
+ * event the list matches, from one outcome row: the global list's per-day
128
+ * count, or the bundle filter's own hourly one.
129
+ */
130
+ const newestDayBelow = async (
131
+ db: Db,
132
+ filter: DayFilter,
133
+ day: number,
134
+ since: number,
135
+ ): Promise<number | undefined> => {
136
+ const counted =
137
+ filter.kind === "all"
138
+ ? { where: DAILY_EVENTS, from: dayFloor(since) }
139
+ : { where: scopeOf(filter), from: hourFloor(since) };
140
+ const [newest] = (
141
+ await db.findAggregates("insights_outcomes", {
142
+ index: "byRef",
143
+ where: counted.where,
144
+ range: { gte: counted.from, lt: day },
145
+ order: "desc",
146
+ limit: 1,
147
+ })
148
+ ).rows;
149
+ return newest === undefined ? undefined : dayFloor(newest.bucket_start_ms);
150
+ };
151
+
152
+ /**
153
+ * Newest first over [since, before), after the cursor. The global and bundle
154
+ * lists cover at most 90 × 24 hours, however many UTC days that touches, and
155
+ * reject a longer range instead of cutting it short. They read one query per
156
+ * UTC day that holds a matching event: a day that holds none is read once,
157
+ * then one outcome row names the newest day below it that does, so a gap of
158
+ * any length costs two reads.
159
+ */
160
+ export const listEvents = async (
161
+ db: Db,
162
+ input: InsightsListEventsInput,
163
+ ): Promise<readonly BundleEventRow[]> => {
164
+ const since = input.sinceMs ?? 0;
165
+ const range = {
166
+ gte: since,
167
+ lt:
168
+ input.after === undefined
169
+ ? input.beforeReceivedAtMs
170
+ : ([input.after.receivedAtMs, input.after.id] as const),
171
+ };
172
+ const { filter, limit } = input;
173
+ if (filter.kind === "installationMovement") {
174
+ const page = await db.findMany("bundle_events", {
175
+ index: "movementsByInstall",
176
+ where: { movement_install_id: filter.installId },
177
+ range,
178
+ order: "desc",
179
+ limit,
180
+ });
181
+ return page.rows.map(toEvent);
182
+ }
183
+ if (input.beforeReceivedAtMs - since > EVENT_LIST_RANGE_MS) {
184
+ throw new DatabasePluginInputError("invalid-query");
185
+ }
186
+ const rows: BundleEventRow[] = [];
187
+ const bottom = dayFloor(since);
188
+ let day: number | undefined = dayFloor(
189
+ input.after?.receivedAtMs ?? input.beforeReceivedAtMs - 1,
190
+ );
191
+ while (day !== undefined && day >= bottom && rows.length < limit) {
192
+ const page = await eventsOfDay(db, filter, day, range, limit - rows.length);
193
+ rows.push(...page.rows.map(toEvent));
194
+ // A day with events may continue into the day before it; after an empty
195
+ // day, the outcome counters name the next day to read, if any is left.
196
+ day =
197
+ page.rows.length > 0
198
+ ? day - DAY_MS
199
+ : day > bottom
200
+ ? await newestDayBelow(db, filter, day, since)
201
+ : undefined;
202
+ }
203
+ return rows;
204
+ };
205
+
206
+ /** One point read for an installation; a user's installations in binary install id order. */
207
+ export const findLatestEvents = async (
208
+ db: Db,
209
+ input: InsightsFindLatestEventsInput,
210
+ ): Promise<readonly BundleEventRow[]> => {
211
+ if ("installId" in input) {
212
+ const head = await db.findOne("bundle_event_heads", {
213
+ install_id: input.installId,
214
+ });
215
+ return head === null ? [] : [toEvent(head)];
216
+ }
217
+ const page = await db.findMany("bundle_event_heads", {
218
+ index: "byUser",
219
+ where: { user_id: input.userId },
220
+ ...(input.afterInstallId === undefined
221
+ ? {}
222
+ : { range: { gt: input.afterInstallId } }),
223
+ order: "asc",
224
+ limit: input.limit,
225
+ });
226
+ return page.rows.map(toEvent);
227
+ };
228
+
229
+ /** Raw events of one bundle filter over [from, to), which spans at most two partial hours. */
230
+ const countRaw = async (
231
+ db: Db,
232
+ filter: InsightsBundleEventFilter,
233
+ from: number,
234
+ to: number,
235
+ ) => {
236
+ let total = 0;
237
+ for (let day = dayFloor(from); from < to && day < to; day += DAY_MS) {
238
+ const rows = await drain((page) =>
239
+ db.findMany("bundle_events", {
240
+ index: "byBundle",
241
+ where: { ...scopeOf(filter), day },
242
+ range: { gte: from, lt: to },
243
+ limit: PAGE,
244
+ ...page,
245
+ }),
246
+ );
247
+ total += rows.length;
248
+ }
249
+ return total;
250
+ };
251
+
252
+ /** Whole hours from outcome counters; the partial hours at either edge from raw events. */
253
+ export const countEvents = async (
254
+ db: Db,
255
+ input: InsightsCountEventsInput,
256
+ ): Promise<number> => {
257
+ const { filter, sinceMs, beforeReceivedAtMs } = input;
258
+ const start = hourCeil(sinceMs);
259
+ const end = hourFloor(beforeReceivedAtMs);
260
+ if (start >= end) return countRaw(db, filter, sinceMs, beforeReceivedAtMs);
261
+ const hours = await drain((page) =>
262
+ db.findAggregates("insights_outcomes", {
263
+ index: "byRef",
264
+ where: scopeOf(filter),
265
+ range: { gte: start, lt: end },
266
+ limit: PAGE,
267
+ ...page,
268
+ }),
269
+ );
270
+ return (
271
+ (await countRaw(db, filter, sinceMs, start)) +
272
+ hours.reduce((sum, row) => sum + row.events, 0) +
273
+ (await countRaw(db, filter, end, beforeReceivedAtMs))
274
+ );
275
+ };
276
+
277
+ type Predicate = {
278
+ readonly field: "from_bundle_id" | "to_bundle_id";
279
+ readonly value: string;
280
+ readonly type: string;
281
+ };
282
+
283
+ /** A bundle filter's distinct (field, value, type) predicates. */
284
+ const latestPredicates = (
285
+ bundle: InsightsCountLatestEventsInput["bundle"],
286
+ ): readonly Predicate[] | undefined => {
287
+ if (bundle === undefined) return undefined;
288
+ return [
289
+ ...new Map(
290
+ bundle.flatMap(({ field, value, types }) =>
291
+ types.map((type) => [
292
+ JSON.stringify([field, value, type]),
293
+ { field, value, type },
294
+ ]),
295
+ ),
296
+ ).values(),
297
+ ];
298
+ };
299
+
300
+ /** Heads whose latest event falls in [sinceMs, end), a partial hour: those events' installs, then their heads. */
301
+ const countPartialHeads = async (
302
+ db: Db,
303
+ input: InsightsCountLatestEventsInput,
304
+ predicates: readonly Predicate[] | undefined,
305
+ end: number,
306
+ ) => {
307
+ const events = await drain((page) =>
308
+ db.findMany("bundle_events", {
309
+ index: "recent",
310
+ where: {
311
+ channel: input.channel,
312
+ platform: input.platform,
313
+ day: dayFloor(input.sinceMs),
314
+ },
315
+ range: { gte: input.sinceMs, lt: end },
316
+ limit: PAGE,
317
+ ...page,
318
+ }),
319
+ );
320
+ const ids = new Set(events.map(({ id }) => id));
321
+ const heads = await Promise.all(
322
+ [...new Set(events.map(({ install_id }) => install_id))].map((install) =>
323
+ db.findOne("bundle_event_heads", { install_id: install }),
324
+ ),
325
+ );
326
+ return heads.filter(
327
+ (head) =>
328
+ head !== null &&
329
+ ids.has(head.id) &&
330
+ (predicates === undefined ||
331
+ predicates.some(
332
+ ({ field, value, type }) =>
333
+ head[field] === value && head.type === type,
334
+ )),
335
+ ).length;
336
+ };
337
+
338
+ /** Latest events at or after `sinceMs`: whole hours from gauges, the first partial hour from heads. */
339
+ export const countLatestEvents = async (
340
+ db: Db,
341
+ input: InsightsCountLatestEventsInput,
342
+ ): Promise<number> => {
343
+ const { platform, channel, sinceMs } = input;
344
+ const start = hourCeil(sinceMs);
345
+ const predicates = latestPredicates(input.bundle);
346
+ let total = 0;
347
+ if (predicates === undefined) {
348
+ const rows = await drain((page) =>
349
+ db.findAggregates("insights_distribution", {
350
+ index: "byScope",
351
+ where: { channel, platform },
352
+ range: { gte: start },
353
+ limit: PAGE,
354
+ ...page,
355
+ }),
356
+ );
357
+ total += rows.reduce((sum, row) => sum + row.latest_installations, 0);
358
+ } else {
359
+ const gauge = async (
360
+ bundleField: string,
361
+ bundleId: string,
362
+ type: string,
363
+ ) => {
364
+ const rows = await drain((page) =>
365
+ db.findAggregates("insights_latest_by_bundle", {
366
+ index: "byBundle",
367
+ where: {
368
+ platform,
369
+ channel,
370
+ bundle_field: bundleField,
371
+ bundle_id: bundleId,
372
+ type,
373
+ },
374
+ range: { gte: start },
375
+ limit: PAGE,
376
+ ...page,
377
+ }),
378
+ );
379
+ return rows.reduce((sum, row) => sum + row.installations, 0);
380
+ };
381
+ for (const { field, value, type } of predicates) {
382
+ total += await gauge(field, value, type);
383
+ }
384
+ // A head has one (from, to) pair, so it matches a `from` and a `to`
385
+ // predicate of one type only through that pair: subtract the pair's gauge
386
+ // to count it once.
387
+ for (const from of predicates) {
388
+ if (from.field !== "from_bundle_id") continue;
389
+ for (const to of predicates) {
390
+ if (to.field !== "to_bundle_id" || to.type !== from.type) continue;
391
+ total -= await gauge(
392
+ PAIR_FIELD,
393
+ bundlePairKey(from.value, to.value),
394
+ from.type,
395
+ );
396
+ }
397
+ }
398
+ }
399
+ return sinceMs < start
400
+ ? total + (await countPartialHeads(db, input, predicates, start))
401
+ : total;
402
+ };
403
+
404
+ /** Periods covering [start, end): whole UTC days when `days` and the window is over 48 hours, hours elsewhere. */
405
+ const periodsOf = (range: InsightsTimeRange, days: boolean) => {
406
+ const { start, end } = range;
407
+ if (!days || end - start <= 2 * DAY_MS) {
408
+ return [{ periodKind: "hour" as const, start, end }];
409
+ }
410
+ const first = Math.min(end, dayCeil(start));
411
+ const last = Math.max(first, dayFloor(end));
412
+ return [
413
+ { periodKind: "hour" as const, start, end: first },
414
+ { periodKind: "day" as const, start: first, end: last },
415
+ { periodKind: "hour" as const, start: last, end },
416
+ ].filter((period) => period.start < period.end);
417
+ };
418
+
419
+ interface CounterRow {
420
+ readonly bucket_start_ms: number;
421
+ readonly downloads: number;
422
+ readonly launches: number;
423
+ readonly failed_launches: number;
424
+ }
425
+
426
+ interface SketchRow {
427
+ readonly bucket_start_ms: number;
428
+ readonly launch_users: string | null;
429
+ readonly activity_users: string | null;
430
+ }
431
+
432
+ /** Every overview or sketch row of one identity over [start, end), mixing day and hour periods. */
433
+ const windowRows = async (
434
+ db: Db,
435
+ model: "insights_overview" | "insights_sketches",
436
+ parts: Parts,
437
+ range: InsightsTimeRange,
438
+ days: boolean,
439
+ ): Promise<readonly object[]> => {
440
+ const rows: object[] = [];
441
+ for (const period of periodsOf(range, days)) {
442
+ const identity = insightsIdentity({
443
+ ...parts,
444
+ periodKind: period.periodKind,
445
+ });
446
+ rows.push(
447
+ ...(await drain((page) =>
448
+ db.findAggregates(model, {
449
+ index: "window",
450
+ where: { identity },
451
+ range: { gte: period.start, lt: period.end },
452
+ limit: PAGE,
453
+ ...page,
454
+ }),
455
+ )),
456
+ );
457
+ }
458
+ return rows;
459
+ };
460
+
461
+ const counterRows = (...args: [Db, Parts, InsightsTimeRange, boolean]) =>
462
+ windowRows(
463
+ args[0],
464
+ "insights_overview",
465
+ args[1],
466
+ args[2],
467
+ args[3],
468
+ ) as Promise<readonly CounterRow[]>;
469
+
470
+ const sketchRows = (...args: [Db, Parts, InsightsTimeRange, boolean]) =>
471
+ windowRows(
472
+ args[0],
473
+ "insights_sketches",
474
+ args[1],
475
+ args[2],
476
+ args[3],
477
+ ) as Promise<readonly SketchRow[]>;
478
+
479
+ const releaseParts = (release: ReleaseReference): Parts => ({
480
+ scopeKind: "release",
481
+ releaseKind: "specific",
482
+ releaseId: release.releaseId,
483
+ channel: release.channel,
484
+ platform: release.platform,
485
+ appVersionKind: "all",
486
+ appVersion: "",
487
+ });
488
+
489
+ /** A release's lifetime counters: one logical row at bucket 0. */
490
+ const lifetimeMetrics = async (
491
+ db: Db,
492
+ release: ReleaseReference,
493
+ ): Promise<ReleaseActivityMetrics> => {
494
+ const identity = insightsIdentity({
495
+ ...releaseParts(release),
496
+ periodKind: "lifetime",
497
+ });
498
+ const [lifetime] = (
499
+ await db.findAggregates("insights_overview", {
500
+ index: "window",
501
+ where: { identity },
502
+ range: { gte: 0, lte: 0 },
503
+ limit: PAGE,
504
+ })
505
+ ).rows;
506
+ return {
507
+ downloads: lifetime?.downloads ?? 0,
508
+ launches: lifetime?.launches ?? 0,
509
+ failedLaunches: lifetime?.failed_launches ?? 0,
510
+ };
511
+ };
512
+
513
+ /** Counters, unique users, and a per-UTC-day series over a window. */
514
+ const rangedMetrics = async (
515
+ db: Db,
516
+ parts: Parts,
517
+ range: InsightsTimeRange,
518
+ days: boolean,
519
+ ): Promise<ReleaseActivityMetrics> => {
520
+ const [counters, sketches] = await Promise.all([
521
+ counterRows(db, parts, range, days),
522
+ sketchRows(db, parts, range, days),
523
+ ]);
524
+ const series = new Map<
525
+ number,
526
+ { launches: number; failedLaunches: number }
527
+ >();
528
+ for (const row of counters) {
529
+ const day = dayFloor(row.bucket_start_ms);
530
+ const point = series.get(day) ?? { launches: 0, failedLaunches: 0 };
531
+ point.launches += row.launches;
532
+ point.failedLaunches += row.failed_launches;
533
+ series.set(day, point);
534
+ }
535
+ const total = (field: "downloads" | "launches" | "failed_launches") =>
536
+ counters.reduce((sum, row) => sum + row[field], 0);
537
+ return {
538
+ downloads: total("downloads"),
539
+ launches: total("launches"),
540
+ failedLaunches: total("failed_launches"),
541
+ uniqueUsers: countInsightsDistinct(
542
+ mergeInsightsDistinct(sketches.map((row) => row.launch_users)),
543
+ ),
544
+ series: [...series]
545
+ .sort(([left], [right]) => left - right)
546
+ .map(([startMs, point]) => ({ startMs, ...point })),
547
+ };
548
+ };
549
+
550
+ export const getReleaseActivity = async (
551
+ db: Db,
552
+ input: InsightsGetReleaseActivityInput,
553
+ now: () => number,
554
+ ): Promise<InsightsGetReleaseActivityResult> => {
555
+ const data =
556
+ input.scope !== undefined
557
+ ? [
558
+ {
559
+ scope: input.scope,
560
+ metrics: await rangedMetrics(
561
+ db,
562
+ {
563
+ scopeKind: "channel",
564
+ releaseKind: "all",
565
+ releaseId: "",
566
+ channel: input.scope.channel,
567
+ platform: input.scope.platform,
568
+ appVersionKind: "all",
569
+ appVersion: "",
570
+ },
571
+ input.timeRange,
572
+ true,
573
+ ),
574
+ },
575
+ ]
576
+ : await Promise.all(
577
+ input.releases.map(async (release) => ({
578
+ release,
579
+ metrics:
580
+ input.timeRange === undefined
581
+ ? await lifetimeMetrics(db, release)
582
+ : await rangedMetrics(
583
+ db,
584
+ releaseParts(release),
585
+ input.timeRange,
586
+ false,
587
+ ),
588
+ })),
589
+ );
590
+ return {
591
+ coverage: { kind: "complete", sinceMs: 0 },
592
+ data,
593
+ measuredAtMs: now(),
594
+ };
595
+ };
596
+
597
+ const byInstallations = (values: Map<string, number>) =>
598
+ [...values]
599
+ .map(([name, installations]) => ({ name, installations }))
600
+ .sort(
601
+ (left, right) =>
602
+ right.installations - left.installations ||
603
+ left.name.localeCompare(right.name, "en", { numeric: true }),
604
+ );
605
+
606
+ export const getAppUsage = async (
607
+ db: Db,
608
+ input: InsightsGetAppUsageInput,
609
+ now: () => number,
610
+ ): Promise<InsightsGetAppUsageResult> => {
611
+ const { timeRange, intervalMs } = input;
612
+ const usage = await sketchRows(
613
+ db,
614
+ {
615
+ scopeKind: "usage",
616
+ releaseKind: "all",
617
+ releaseId: "",
618
+ channel: input.channel,
619
+ platform: input.platform,
620
+ appVersionKind: input.appVersion === undefined ? "all" : "specific",
621
+ appVersion: input.appVersion ?? "",
622
+ },
623
+ timeRange,
624
+ intervalMs % DAY_MS === 0 && timeRange.start % DAY_MS === 0,
625
+ );
626
+ const distribution = [];
627
+ for (const platform of input.platform === "all"
628
+ ? (["ios", "android"] as const)
629
+ : [input.platform]) {
630
+ distribution.push(
631
+ ...(await drain((page) =>
632
+ input.appVersion === undefined
633
+ ? db.findAggregates("insights_distribution", {
634
+ index: "byScope",
635
+ where: { channel: input.channel, platform },
636
+ range: { gte: timeRange.start, lt: timeRange.end },
637
+ limit: PAGE,
638
+ ...page,
639
+ })
640
+ : db.findAggregates("insights_distribution", {
641
+ index: "byVersion",
642
+ where: {
643
+ channel: input.channel,
644
+ platform,
645
+ app_version: input.appVersion,
646
+ },
647
+ range: { gte: timeRange.start, lt: timeRange.end },
648
+ limit: PAGE,
649
+ ...page,
650
+ }),
651
+ )),
652
+ );
653
+ }
654
+ const points = [];
655
+ for (
656
+ let start = timeRange.start;
657
+ start < timeRange.end;
658
+ start += intervalMs
659
+ ) {
660
+ points.push({
661
+ startMs: start,
662
+ installations: countInsightsDistinct(
663
+ mergeInsightsDistinct(
664
+ usage
665
+ .filter(
666
+ (row) =>
667
+ row.bucket_start_ms >= start &&
668
+ row.bucket_start_ms < start + intervalMs,
669
+ )
670
+ .map((row) => row.activity_users),
671
+ ),
672
+ ),
673
+ });
674
+ }
675
+ const versions = new Map<string, number>();
676
+ const platforms = new Map<string, number>();
677
+ const bundles = new Map<
678
+ string,
679
+ InsightsGetAppUsageResult["bundleDistribution"][number]
680
+ >();
681
+ for (const row of distribution) {
682
+ const count = row.latest_installations;
683
+ versions.set(row.app_version, (versions.get(row.app_version) ?? 0) + count);
684
+ platforms.set(row.platform, (platforms.get(row.platform) ?? 0) + count);
685
+ const releaseId = row.release_id === "" ? null : row.release_id;
686
+ const key = JSON.stringify([row.app_version, row.platform, releaseId]);
687
+ bundles.set(key, {
688
+ appVersion: row.app_version,
689
+ platform: row.platform as "ios" | "android",
690
+ releaseId,
691
+ installations: (bundles.get(key)?.installations ?? 0) + count,
692
+ });
693
+ }
694
+ const sortedVersions = byInstallations(versions);
695
+ return {
696
+ coverage: { kind: "complete", sinceMs: 0 },
697
+ activeInstallations: countInsightsDistinct(
698
+ mergeInsightsDistinct(usage.map((row) => row.activity_users)),
699
+ ),
700
+ points,
701
+ appVersions: sortedVersions.map(({ name }) => name),
702
+ versions: sortedVersions,
703
+ platforms: byInstallations(platforms),
704
+ bundleDistribution: [...bundles.values()],
705
+ measuredAtMs: now(),
706
+ };
707
+ };