@hot-updater/server 1.0.0-rc.14 → 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 (514) hide show
  1. package/dist/adapters/drizzle.cjs +35 -74
  2. package/dist/adapters/drizzle.d.cts +13 -5
  3. package/dist/adapters/drizzle.d.mts +13 -5
  4. package/dist/adapters/drizzle.mjs +34 -75
  5. package/dist/adapters/drizzleExecutor.cjs +114 -0
  6. package/dist/adapters/drizzleExecutor.d.cts +9 -0
  7. package/dist/adapters/drizzleExecutor.d.mts +9 -0
  8. package/dist/adapters/drizzleExecutor.mjs +112 -0
  9. package/dist/adapters/kysely.cjs +25 -31
  10. package/dist/adapters/kysely.d.cts +10 -6
  11. package/dist/adapters/kysely.d.mts +10 -6
  12. package/dist/adapters/kysely.mjs +25 -32
  13. package/dist/adapters/kyselyExecutor.cjs +32 -0
  14. package/dist/adapters/kyselyExecutor.d.cts +12 -0
  15. package/dist/adapters/kyselyExecutor.d.mts +12 -0
  16. package/dist/adapters/kyselyExecutor.mjs +32 -0
  17. package/dist/adapters/mongodb.cjs +28 -62
  18. package/dist/adapters/mongodb.d.cts +10 -5
  19. package/dist/adapters/mongodb.d.mts +10 -5
  20. package/dist/adapters/mongodb.mjs +28 -63
  21. package/dist/adapters/mongodbAdapter.cjs +174 -0
  22. package/dist/adapters/mongodbAdapter.d.cts +9 -0
  23. package/dist/adapters/mongodbAdapter.d.mts +9 -0
  24. package/dist/adapters/mongodbAdapter.mjs +173 -0
  25. package/dist/adapters/prisma.cjs +38 -257
  26. package/dist/adapters/prisma.d.cts +12 -7
  27. package/dist/adapters/prisma.d.mts +12 -7
  28. package/dist/adapters/prisma.mjs +38 -257
  29. package/dist/adapters/prismaExecutor.cjs +64 -0
  30. package/dist/adapters/prismaExecutor.mjs +64 -0
  31. package/dist/adapters/sqlProviders.cjs +9 -0
  32. package/dist/adapters/sqlProviders.mjs +9 -0
  33. package/dist/apiKeys.cjs +2 -6
  34. package/dist/apiKeys.d.cts +1 -23
  35. package/dist/apiKeys.d.mts +1 -23
  36. package/dist/apiKeys.mjs +2 -6
  37. package/dist/assembly/assemblePlugins.cjs +147 -0
  38. package/dist/assembly/assemblePlugins.d.cts +12 -0
  39. package/dist/assembly/assemblePlugins.d.mts +12 -0
  40. package/dist/assembly/assemblePlugins.mjs +146 -0
  41. package/dist/assembly/databasePlugins.cjs +23 -0
  42. package/dist/assembly/databasePlugins.d.cts +43 -0
  43. package/dist/assembly/databasePlugins.d.mts +43 -0
  44. package/dist/assembly/databasePlugins.mjs +22 -0
  45. package/dist/core/api.cjs +91 -0
  46. package/dist/core/api.d.cts +65 -0
  47. package/dist/core/api.d.mts +65 -0
  48. package/dist/core/api.mjs +87 -0
  49. package/dist/core/baseCandidates.cjs +71 -0
  50. package/dist/core/baseCandidates.d.cts +17 -0
  51. package/dist/core/baseCandidates.d.mts +17 -0
  52. package/dist/core/baseCandidates.mjs +69 -0
  53. package/dist/core/operations.cjs +350 -0
  54. package/dist/core/operations.mjs +350 -0
  55. package/dist/core/reads.cjs +273 -0
  56. package/dist/core/reads.d.cts +48 -0
  57. package/dist/core/reads.d.mts +48 -0
  58. package/dist/core/reads.mjs +267 -0
  59. package/dist/core/releases.cjs +193 -0
  60. package/dist/core/releases.mjs +190 -0
  61. package/dist/core/schema.cjs +279 -0
  62. package/dist/core/schema.d.cts +307 -0
  63. package/dist/core/schema.d.mts +307 -0
  64. package/dist/core/schema.mjs +277 -0
  65. package/dist/core/writes.cjs +96 -0
  66. package/dist/core/writes.mjs +91 -0
  67. package/dist/createHotUpdaterCore.cjs +58 -75
  68. package/dist/createHotUpdaterCore.d.cts +52 -44
  69. package/dist/createHotUpdaterCore.d.mts +52 -44
  70. package/dist/createHotUpdaterCore.mjs +59 -75
  71. package/dist/database/cursor.cjs +44 -0
  72. package/dist/database/cursor.d.cts +7 -0
  73. package/dist/database/cursor.d.mts +7 -0
  74. package/dist/database/cursor.mjs +40 -0
  75. package/dist/database/database.cjs +44 -0
  76. package/dist/database/database.d.cts +123 -0
  77. package/dist/database/database.d.mts +123 -0
  78. package/dist/database/database.mjs +44 -0
  79. package/dist/database/definitions.d.cts +68 -0
  80. package/dist/database/definitions.d.mts +68 -0
  81. package/dist/database/engine.cjs +37 -0
  82. package/dist/database/engine.d.cts +40 -0
  83. package/dist/database/engine.d.mts +40 -0
  84. package/dist/database/engine.mjs +37 -0
  85. package/dist/database/engineAggregates.cjs +115 -0
  86. package/dist/database/engineAggregates.mjs +112 -0
  87. package/dist/database/engineReads.cjs +180 -0
  88. package/dist/database/engineReads.d.cts +35 -0
  89. package/dist/database/engineReads.d.mts +35 -0
  90. package/dist/database/engineReads.mjs +179 -0
  91. package/dist/database/engineTransaction.cjs +403 -0
  92. package/dist/database/engineTransaction.d.cts +26 -0
  93. package/dist/database/engineTransaction.d.mts +26 -0
  94. package/dist/database/engineTransaction.mjs +402 -0
  95. package/dist/database/errors.cjs +27 -0
  96. package/dist/database/errors.d.cts +21 -0
  97. package/dist/database/errors.d.mts +21 -0
  98. package/dist/database/errors.mjs +24 -0
  99. package/dist/database/fence.cjs +124 -0
  100. package/dist/database/fence.d.cts +42 -0
  101. package/dist/database/fence.d.mts +42 -0
  102. package/dist/database/fence.mjs +116 -0
  103. package/dist/database/index.cjs +127 -0
  104. package/dist/database/index.d.cts +17 -0
  105. package/dist/database/index.d.mts +17 -0
  106. package/dist/database/index.mjs +15 -0
  107. package/dist/database/kv/kvAdapter.cjs +273 -0
  108. package/dist/database/kv/kvAdapter.d.cts +101 -0
  109. package/dist/database/kv/kvAdapter.d.mts +101 -0
  110. package/dist/database/kv/kvAdapter.mjs +272 -0
  111. package/dist/database/resolveSchema.cjs +196 -0
  112. package/dist/database/resolveSchema.d.cts +42 -0
  113. package/dist/database/resolveSchema.d.mts +42 -0
  114. package/dist/database/resolveSchema.mjs +193 -0
  115. package/dist/database/schema.cjs +21 -0
  116. package/dist/database/schema.d.cts +61 -0
  117. package/dist/database/schema.d.mts +61 -0
  118. package/dist/database/schema.mjs +20 -0
  119. package/dist/database/sql/sqlAdapter.cjs +282 -0
  120. package/dist/database/sql/sqlAdapter.d.cts +46 -0
  121. package/dist/database/sql/sqlAdapter.d.mts +46 -0
  122. package/dist/database/sql/sqlAdapter.mjs +279 -0
  123. package/dist/database/sql/sqlSchema.cjs +115 -0
  124. package/dist/database/sql/sqlSchema.d.cts +8 -0
  125. package/dist/database/sql/sqlSchema.d.mts +8 -0
  126. package/dist/database/sql/sqlSchema.mjs +111 -0
  127. package/dist/db/builtInDatabase.cjs +104 -0
  128. package/dist/db/builtInDatabase.d.cts +38 -0
  129. package/dist/db/builtInDatabase.d.mts +38 -0
  130. package/dist/db/builtInDatabase.mjs +97 -0
  131. package/dist/db/createBundleDiff.cjs +4 -4
  132. package/dist/db/createBundleDiff.d.cts +3 -2
  133. package/dist/db/createBundleDiff.d.mts +3 -2
  134. package/dist/db/createBundleDiff.mjs +5 -5
  135. package/dist/db/engineDrizzleSchema.cjs +54 -0
  136. package/dist/db/engineDrizzleSchema.mjs +54 -0
  137. package/dist/db/engineMigrator.cjs +53 -0
  138. package/dist/db/engineMigrator.mjs +53 -0
  139. package/dist/db/enginePrismaSchema.cjs +85 -0
  140. package/dist/db/enginePrismaSchema.d.cts +5 -0
  141. package/dist/db/enginePrismaSchema.d.mts +5 -0
  142. package/dist/db/enginePrismaSchema.mjs +84 -0
  143. package/dist/db/engineSql.cjs +26 -0
  144. package/dist/db/engineSql.d.cts +17 -0
  145. package/dist/db/engineSql.d.mts +17 -0
  146. package/dist/db/engineSql.mjs +25 -0
  147. package/dist/db/engineSqlMigrator.cjs +66 -0
  148. package/dist/db/engineSqlMigrator.mjs +66 -0
  149. package/dist/db/fixedMigratorShared.cjs +0 -11
  150. package/dist/db/fixedMigratorShared.mjs +1 -8
  151. package/dist/db/index.cjs +23 -8
  152. package/dist/db/index.d.cts +10 -3
  153. package/dist/db/index.d.mts +10 -3
  154. package/dist/db/index.mjs +16 -8
  155. package/dist/db/releaseCatalog.cjs +20 -43
  156. package/dist/db/releaseCatalog.d.cts +2 -2
  157. package/dist/db/releaseCatalog.d.mts +2 -2
  158. package/dist/db/releaseCatalog.mjs +20 -44
  159. package/dist/db/schemaSettings.cjs +63 -0
  160. package/dist/db/schemaSettings.d.cts +12 -0
  161. package/dist/db/schemaSettings.d.mts +12 -0
  162. package/dist/db/schemaSettings.mjs +62 -0
  163. package/dist/db/settingsMigrator.cjs +92 -0
  164. package/dist/db/settingsMigrator.mjs +89 -0
  165. package/dist/db/types.cjs +1 -12
  166. package/dist/db/types.d.cts +26 -66
  167. package/dist/db/types.d.mts +26 -66
  168. package/dist/db/types.mjs +2 -11
  169. package/dist/db/updateArtifacts.cjs +38 -67
  170. package/dist/db/updateArtifacts.mjs +40 -69
  171. package/dist/handler.cjs +72 -43
  172. package/dist/handler.d.cts +3 -5
  173. package/dist/handler.d.mts +3 -5
  174. package/dist/handler.mjs +73 -43
  175. package/dist/handlerAdminRoutes.cjs +375 -0
  176. package/dist/handlerAdminRoutes.mjs +373 -0
  177. package/dist/handlerParameters.cjs +6 -11
  178. package/dist/handlerParameters.mjs +6 -10
  179. package/dist/handlerReleaseCatalogRoutes.cjs +11 -13
  180. package/dist/handlerReleaseCatalogRoutes.mjs +11 -13
  181. package/dist/handlerTypes.d.cts +2 -55
  182. package/dist/handlerTypes.d.mts +2 -55
  183. package/dist/handlerVersionRoutes.cjs +20 -4
  184. package/dist/handlerVersionRoutes.mjs +20 -4
  185. package/dist/index.cjs +4 -7
  186. package/dist/index.d.cts +8 -7
  187. package/dist/index.d.mts +8 -7
  188. package/dist/index.mjs +4 -4
  189. package/dist/insights/domain.d.cts +12 -0
  190. package/dist/insights/domain.d.mts +12 -0
  191. package/dist/insights/errors.cjs +34 -0
  192. package/dist/insights/errors.mjs +34 -1
  193. package/dist/insights/eventInput.cjs +14 -19
  194. package/dist/insights/eventInput.mjs +15 -20
  195. package/dist/insights/provider.cjs +47 -23
  196. package/dist/insights/provider.mjs +47 -24
  197. package/dist/insights/routes.cjs +79 -14
  198. package/dist/insights/routes.mjs +77 -14
  199. package/dist/node.d.cts +1 -1
  200. package/dist/node.d.mts +1 -1
  201. package/dist/package.cjs +1 -1
  202. package/dist/package.mjs +1 -1
  203. package/dist/plugins/api-keys/index.cjs +50 -0
  204. package/dist/plugins/api-keys/index.d.cts +85 -0
  205. package/dist/plugins/api-keys/index.d.mts +85 -0
  206. package/dist/plugins/api-keys/index.mjs +47 -0
  207. package/dist/plugins/api-keys/model.cjs +58 -0
  208. package/dist/plugins/api-keys/model.d.cts +8 -0
  209. package/dist/plugins/api-keys/model.d.mts +8 -0
  210. package/dist/plugins/api-keys/model.mjs +58 -0
  211. package/dist/plugins/api-keys/schema.cjs +38 -0
  212. package/dist/plugins/api-keys/schema.d.cts +44 -0
  213. package/dist/plugins/api-keys/schema.d.mts +44 -0
  214. package/dist/plugins/api-keys/schema.mjs +39 -0
  215. package/dist/plugins/builtIn.cjs +7 -0
  216. package/dist/plugins/builtIn.mjs +6 -0
  217. package/dist/plugins/definePlugin.cjs +5 -0
  218. package/dist/plugins/definePlugin.d.cts +73 -0
  219. package/dist/plugins/definePlugin.d.mts +73 -0
  220. package/dist/plugins/definePlugin.mjs +5 -0
  221. package/dist/plugins/index.cjs +17 -0
  222. package/dist/plugins/index.d.cts +9 -0
  223. package/dist/plugins/index.d.mts +9 -0
  224. package/dist/plugins/index.mjs +7 -0
  225. package/dist/plugins/insights/index.cjs +48 -0
  226. package/dist/plugins/insights/index.d.cts +295 -0
  227. package/dist/plugins/insights/index.d.mts +295 -0
  228. package/dist/plugins/insights/index.mjs +44 -0
  229. package/dist/plugins/insights/model.cjs +17 -0
  230. package/dist/plugins/insights/model.d.cts +10 -0
  231. package/dist/plugins/insights/model.d.mts +10 -0
  232. package/dist/plugins/insights/model.mjs +17 -0
  233. package/dist/plugins/insights/reads.cjs +471 -0
  234. package/dist/plugins/insights/reads.mjs +466 -0
  235. package/dist/plugins/insights/recordEvent.cjs +135 -0
  236. package/dist/plugins/insights/recordEvent.d.cts +13 -0
  237. package/dist/plugins/insights/recordEvent.d.mts +13 -0
  238. package/dist/plugins/insights/recordEvent.mjs +132 -0
  239. package/dist/plugins/insights/schema.cjs +347 -0
  240. package/dist/plugins/insights/schema.d.cts +258 -0
  241. package/dist/plugins/insights/schema.d.mts +258 -0
  242. package/dist/plugins/insights/schema.mjs +344 -0
  243. package/dist/types/index.d.cts +2 -28
  244. package/dist/types/index.d.mts +2 -28
  245. package/package.json +28 -5
  246. package/src/adapters/drizzle.conformance.integration.spec.ts +34 -0
  247. package/src/adapters/drizzle.readBudgets.integration.spec.ts +41 -0
  248. package/src/adapters/drizzle.spec.ts +270 -399
  249. package/src/adapters/drizzle.ts +48 -140
  250. package/src/adapters/drizzleExecutor.ts +166 -0
  251. package/src/adapters/kysely.conformance.integration.spec.ts +36 -0
  252. package/src/adapters/kysely.readBudgets.integration.spec.ts +42 -0
  253. package/src/adapters/kysely.spec.ts +204 -171
  254. package/src/adapters/kysely.ts +32 -93
  255. package/src/adapters/kyselyExecutor.ts +53 -0
  256. package/src/adapters/mongodb.integration.spec.ts +143 -446
  257. package/src/adapters/mongodb.readBudgets.integration.spec.ts +61 -0
  258. package/src/adapters/mongodb.ts +36 -123
  259. package/src/adapters/mongodbAdapter.ts +293 -0
  260. package/src/adapters/prisma.conformance.integration.spec.ts +36 -0
  261. package/src/adapters/prisma.integration.spec.ts +138 -0
  262. package/src/adapters/prisma.readBudgets.integration.spec.ts +40 -0
  263. package/src/adapters/prisma.spec.ts +216 -433
  264. package/src/adapters/prisma.ts +60 -524
  265. package/src/adapters/prismaExecutor.ts +107 -0
  266. package/src/adapters/prismaTestClients.ts +175 -0
  267. package/src/adapters/sqlProviders.ts +14 -0
  268. package/src/apiKeys.integration.spec.ts +19 -119
  269. package/src/apiKeys.spec.ts +39 -35
  270. package/src/apiKeys.ts +2 -8
  271. package/src/assembly/assemblePlugins.ts +269 -0
  272. package/src/assembly/assembly.spec.ts +325 -0
  273. package/src/assembly/assembly.types.spec.ts +155 -0
  274. package/src/assembly/builtInRoutes.spec.ts +213 -0
  275. package/src/assembly/databasePlugins.spec.ts +71 -0
  276. package/src/assembly/databasePlugins.ts +96 -0
  277. package/src/core/api.ts +139 -0
  278. package/src/core/baseCandidates.spec.ts +378 -0
  279. package/src/core/baseCandidates.ts +136 -0
  280. package/src/core/index.ts +44 -0
  281. package/src/core/operations.spec.ts +415 -0
  282. package/src/core/operations.ts +551 -0
  283. package/src/core/reads.spec.ts +300 -0
  284. package/src/core/reads.ts +417 -0
  285. package/src/core/releases.spec.ts +308 -0
  286. package/src/core/releases.ts +345 -0
  287. package/src/core/schema.ts +173 -0
  288. package/src/core/writes.ts +160 -0
  289. package/src/createHotUpdaterCore.ts +191 -181
  290. package/src/database/cursor.ts +65 -0
  291. package/src/database/database.ts +335 -0
  292. package/src/database/database.types.spec.ts +135 -0
  293. package/src/database/definitions.ts +72 -0
  294. package/src/database/engine.ts +62 -0
  295. package/src/database/engineAggregates.spec.ts +539 -0
  296. package/src/database/engineAggregates.ts +193 -0
  297. package/src/database/engineReads.spec.ts +219 -0
  298. package/src/database/engineReads.ts +302 -0
  299. package/src/database/engineTransaction.spec.ts +710 -0
  300. package/src/database/engineTransaction.ts +588 -0
  301. package/src/database/errors.ts +30 -0
  302. package/src/database/fence.spec.ts +223 -0
  303. package/src/database/fence.ts +171 -0
  304. package/src/database/index.ts +154 -0
  305. package/src/database/kv/kvAdapter.conformance.integration.spec.ts +17 -0
  306. package/src/database/kv/kvAdapter.readBudgets.integration.spec.ts +17 -0
  307. package/src/database/kv/kvAdapter.spec.ts +468 -0
  308. package/src/database/kv/kvAdapter.ts +450 -0
  309. package/src/database/kv/kvTestStore.ts +91 -0
  310. package/src/database/memory.readBudgets.integration.spec.ts +13 -0
  311. package/src/database/resolveSchema.spec.ts +230 -0
  312. package/src/database/resolveSchema.ts +372 -0
  313. package/src/database/schema.ts +169 -0
  314. package/src/database/schema.types.spec.ts +94 -0
  315. package/src/database/sql/docker-compose.yml +36 -0
  316. package/src/database/sql/sqlAdapter.integration.spec.ts +148 -0
  317. package/src/database/sql/sqlAdapter.readBudgets.integration.spec.ts +109 -0
  318. package/src/database/sql/sqlAdapter.spec.ts +255 -0
  319. package/src/database/sql/sqlAdapter.ts +532 -0
  320. package/src/database/sql/sqlSchema.ts +205 -0
  321. package/src/database/sql/sqlTestExecutors.ts +183 -0
  322. package/src/databaseTestSuite.spec.ts +39 -0
  323. package/src/db/builtInDatabase.ts +149 -0
  324. package/src/db/bundleRows.spec.ts +3 -3
  325. package/src/db/createBundleDiff.spec.ts +43 -29
  326. package/src/db/createBundleDiff.ts +13 -7
  327. package/src/db/engineDrizzleSchema.ts +72 -0
  328. package/src/db/engineMigrator.ts +105 -0
  329. package/src/db/enginePrismaSchema.ts +147 -0
  330. package/src/db/engineSql.spec.ts +129 -0
  331. package/src/db/engineSql.ts +48 -0
  332. package/src/db/engineSqlMigrator.ts +116 -0
  333. package/src/db/fixedMigratorShared.ts +1 -20
  334. package/src/db/index.spec.ts +116 -829
  335. package/src/db/index.ts +43 -14
  336. package/src/db/pluginTables.spec.ts +218 -0
  337. package/src/db/releaseCatalog.ts +48 -90
  338. package/src/db/schemaSettings.ts +104 -0
  339. package/src/db/settingsMigrator.spec.ts +65 -0
  340. package/src/db/settingsMigrator.ts +152 -0
  341. package/src/db/types.ts +25 -171
  342. package/src/db/updateArtifacts.spec.ts +170 -294
  343. package/src/db/updateArtifacts.ts +74 -142
  344. package/src/db/updateCheck.bench.ts +47 -0
  345. package/src/handler-standalone.integration.spec.ts +204 -410
  346. package/src/handler.spec.ts +134 -51
  347. package/src/handler.testFixtures.ts +14 -19
  348. package/src/handler.ts +163 -75
  349. package/src/handlerAdminRoutes.spec.ts +183 -0
  350. package/src/handlerAdminRoutes.ts +536 -0
  351. package/src/handlerAuth.integration.spec.ts +3 -3
  352. package/src/handlerManagement.spec.ts +136 -196
  353. package/src/handlerReleaseCatalogRoutes.ts +15 -17
  354. package/src/handlerTypes.ts +3 -80
  355. package/src/handlerVersionRoutes.ts +20 -7
  356. package/src/index.ts +5 -12
  357. package/src/insights/domain.ts +12 -0
  358. package/src/insights/errors.spec.ts +48 -0
  359. package/src/insights/errors.ts +51 -0
  360. package/src/insights/eventInput.ts +27 -24
  361. package/src/insights/insights.integration.spec.ts +228 -78
  362. package/src/insights/provider.spec.ts +203 -25
  363. package/src/insights/provider.ts +88 -32
  364. package/src/insights/routes.ts +95 -20
  365. package/src/lintRules.spec.ts +76 -0
  366. package/src/plugins/api-keys/apiKeysPlugin.spec.ts +172 -0
  367. package/src/plugins/api-keys/index.ts +66 -0
  368. package/src/plugins/api-keys/model.ts +69 -0
  369. package/src/plugins/api-keys/schema.ts +22 -0
  370. package/src/plugins/builtIn.ts +5 -0
  371. package/src/plugins/definePlugin.ts +100 -0
  372. package/src/plugins/index.ts +55 -0
  373. package/src/plugins/insights/index.ts +86 -0
  374. package/src/plugins/insights/insightsRollout.integration.spec.ts +187 -0
  375. package/src/plugins/insights/model.spec.ts +66 -0
  376. package/src/plugins/insights/model.ts +19 -0
  377. package/src/plugins/insights/reads.spec.ts +725 -0
  378. package/src/plugins/insights/reads.ts +707 -0
  379. package/src/plugins/insights/recordEvent.spec.ts +443 -0
  380. package/src/plugins/insights/recordEvent.ts +238 -0
  381. package/src/plugins/insights/schema.ts +229 -0
  382. package/src/plugins/pluginTestHarness.spec.ts +56 -0
  383. package/src/readBudgets.testFixtures.ts +15 -0
  384. package/src/releaseCatalogRoutes.integration.spec.ts +47 -92
  385. package/src/runtime.spec.ts +91 -77
  386. package/src/runtime.testFixtures.ts +28 -44
  387. package/src/runtimeBundles.integration.spec.ts +112 -0
  388. package/src/testUtilsMutation.integration.spec.ts +215 -0
  389. package/src/testUtilsPackage.integration.spec.ts +176 -0
  390. package/src/types/index.ts +0 -32
  391. package/dist/adapters/databaseConstraintErrors.cjs +0 -22
  392. package/dist/adapters/databaseConstraintErrors.mjs +0 -21
  393. package/dist/adapters/databasePluginUtils.cjs +0 -130
  394. package/dist/adapters/databasePluginUtils.mjs +0 -118
  395. package/dist/adapters/drizzleCrud.cjs +0 -340
  396. package/dist/adapters/drizzleCrud.mjs +0 -337
  397. package/dist/adapters/drizzleLazyDB.cjs +0 -103
  398. package/dist/adapters/drizzleLazyDB.mjs +0 -103
  399. package/dist/adapters/drizzleQuery.cjs +0 -69
  400. package/dist/adapters/drizzleQuery.mjs +0 -69
  401. package/dist/adapters/kyselyCrud.cjs +0 -293
  402. package/dist/adapters/kyselyCrud.mjs +0 -292
  403. package/dist/adapters/kyselyQuery.cjs +0 -56
  404. package/dist/adapters/kyselyQuery.mjs +0 -56
  405. package/dist/adapters/mongodbCollections.cjs +0 -38
  406. package/dist/adapters/mongodbCollections.mjs +0 -32
  407. package/dist/adapters/mongodbQuery.cjs +0 -90
  408. package/dist/adapters/mongodbQuery.mjs +0 -83
  409. package/dist/adapters/mongodbReads.cjs +0 -206
  410. package/dist/adapters/mongodbReads.mjs +0 -206
  411. package/dist/adapters/mongodbWrites.cjs +0 -133
  412. package/dist/adapters/mongodbWrites.mjs +0 -133
  413. package/dist/adapters/prismaInsights.cjs +0 -99
  414. package/dist/adapters/prismaInsights.mjs +0 -97
  415. package/dist/adapters/prismaQuery.cjs +0 -57
  416. package/dist/adapters/prismaQuery.mjs +0 -56
  417. package/dist/adapters/prismaRows.cjs +0 -197
  418. package/dist/adapters/prismaRows.mjs +0 -188
  419. package/dist/db/databasePluginCore.cjs +0 -137
  420. package/dist/db/databasePluginCore.mjs +0 -137
  421. package/dist/db/fixedMigrator.cjs +0 -79
  422. package/dist/db/fixedMigrator.mjs +0 -78
  423. package/dist/db/fixedMigratorMongo.cjs +0 -321
  424. package/dist/db/fixedMigratorMongo.mjs +0 -321
  425. package/dist/db/mongoMigrationExecution.cjs +0 -11
  426. package/dist/db/mongoMigrationExecution.mjs +0 -11
  427. package/dist/db/schema/insightsCollation.cjs +0 -14
  428. package/dist/db/schema/insightsCollation.mjs +0 -14
  429. package/dist/db/schema/mongodb.cjs +0 -25
  430. package/dist/db/schema/mongodb.mjs +0 -25
  431. package/dist/db/schema/registry.cjs +0 -21
  432. package/dist/db/schema/registry.mjs +0 -17
  433. package/dist/db/schema/sql.cjs +0 -105
  434. package/dist/db/schema/sql.mjs +0 -97
  435. package/dist/db/schema/sqlOperations.cjs +0 -27
  436. package/dist/db/schema/sqlOperations.mjs +0 -26
  437. package/dist/db/schemaGenerators.cjs +0 -276
  438. package/dist/db/schemaGenerators.mjs +0 -274
  439. package/dist/db/schemaReadiness.cjs +0 -26
  440. package/dist/db/schemaReadiness.d.cts +0 -9
  441. package/dist/db/schemaReadiness.d.mts +0 -9
  442. package/dist/db/schemaReadiness.mjs +0 -25
  443. package/dist/db/sqlMigrationExecution.cjs +0 -67
  444. package/dist/db/sqlMigrationExecution.mjs +0 -67
  445. package/dist/handlerBundleRoutes.cjs +0 -155
  446. package/dist/handlerBundleRoutes.mjs +0 -155
  447. package/dist/handlerReleaseManagementRoutes.cjs +0 -124
  448. package/dist/handlerReleaseManagementRoutes.mjs +0 -124
  449. package/dist/schema/dsl.cjs +0 -103
  450. package/dist/schema/dsl.mjs +0 -87
  451. package/dist/schema/index.cjs +0 -20
  452. package/dist/schema/index.mjs +0 -7
  453. package/dist/schema/settings.cjs +0 -9
  454. package/dist/schema/settings.mjs +0 -9
  455. package/dist/schema/types.cjs +0 -8
  456. package/dist/schema/types.mjs +0 -6
  457. package/dist/schema/v1_0_0.cjs +0 -393
  458. package/dist/schema/v1_0_0.mjs +0 -385
  459. package/src/adapters/databaseConstraintErrors.ts +0 -25
  460. package/src/adapters/databasePluginTestDatabase.ts +0 -116
  461. package/src/adapters/databasePluginUtils.spec.ts +0 -29
  462. package/src/adapters/databasePluginUtils.ts +0 -238
  463. package/src/adapters/drizzleCrud.ts +0 -610
  464. package/src/adapters/drizzleLazyDB.ts +0 -284
  465. package/src/adapters/drizzleQuery.ts +0 -164
  466. package/src/adapters/kyselyCrud.ts +0 -696
  467. package/src/adapters/kyselyInsights.spec.ts +0 -168
  468. package/src/adapters/kyselyQuery.ts +0 -128
  469. package/src/adapters/mongodb.spec.ts +0 -230
  470. package/src/adapters/mongodbCollections.ts +0 -78
  471. package/src/adapters/mongodbQuery.ts +0 -245
  472. package/src/adapters/mongodbReads.ts +0 -387
  473. package/src/adapters/mongodbRows.spec.ts +0 -64
  474. package/src/adapters/mongodbRows.ts +0 -118
  475. package/src/adapters/mongodbTestClient.ts +0 -443
  476. package/src/adapters/mongodbTestFilter.ts +0 -170
  477. package/src/adapters/mongodbWrites.ts +0 -261
  478. package/src/adapters/prismaInsights.ts +0 -190
  479. package/src/adapters/prismaQuery.ts +0 -129
  480. package/src/adapters/prismaRows.spec.ts +0 -98
  481. package/src/adapters/prismaRows.ts +0 -308
  482. package/src/adapters/prismaTestClient.ts +0 -478
  483. package/src/calculatePagination.ts +0 -34
  484. package/src/db/databasePluginCore.spec.ts +0 -62
  485. package/src/db/databasePluginCore.testFixtures.ts +0 -76
  486. package/src/db/databasePluginCore.ts +0 -185
  487. package/src/db/databasePluginUpdateCheck.bench.ts +0 -117
  488. package/src/db/fixedMigrator.spec.ts +0 -294
  489. package/src/db/fixedMigrator.ts +0 -147
  490. package/src/db/fixedMigratorMongo.spec.ts +0 -253
  491. package/src/db/fixedMigratorMongo.ts +0 -425
  492. package/src/db/hotUpdaterSchema.ts +0 -5
  493. package/src/db/mongoMigrationExecution.ts +0 -23
  494. package/src/db/schema/definitions.ts +0 -1
  495. package/src/db/schema/insightsCollation.ts +0 -21
  496. package/src/db/schema/mongodb.ts +0 -40
  497. package/src/db/schema/registry.ts +0 -55
  498. package/src/db/schema/sql.ts +0 -244
  499. package/src/db/schema/sqlOperations.ts +0 -63
  500. package/src/db/schema/types.ts +0 -1
  501. package/src/db/schemaGenerators.ts +0 -495
  502. package/src/db/schemaReadiness.ts +0 -34
  503. package/src/db/sqlMigrationExecution.spec.ts +0 -65
  504. package/src/db/sqlMigrationExecution.ts +0 -108
  505. package/src/db/types.spec.ts +0 -63
  506. package/src/handlerBundleRoutes.ts +0 -259
  507. package/src/handlerReleaseManagementRoutes.ts +0 -213
  508. package/src/schema/dsl-all-versions.spec.ts +0 -35
  509. package/src/schema/dsl.ts +0 -187
  510. package/src/schema/index.ts +0 -10
  511. package/src/schema/settings.ts +0 -15
  512. package/src/schema/types.ts +0 -79
  513. package/src/schema/v1_0_0.spec.ts +0 -220
  514. package/src/schema/v1_0_0.ts +0 -511
@@ -7,6 +7,16 @@ import { createInsightsProvider } from "./provider";
7
7
  const eventId = (index: number) =>
8
8
  `00000000-0000-7000-8000-${String(index).padStart(12, "0")}`;
9
9
 
10
+ const HOUR = 3_600_000;
11
+ const DAY = 86_400_000;
12
+ const cutoff = Date.UTC(2026, 8, 20, 9, 30);
13
+ const bundle = {
14
+ platform: "ios",
15
+ channel: "production",
16
+ bundleId: "bundle-after",
17
+ outcome: "applied",
18
+ } as const;
19
+
10
20
  type TransitionEventRow = Extract<
11
21
  BundleEventRow,
12
22
  { readonly type: "UPDATE_APPLIED" | "RECOVERED" }
@@ -67,6 +77,23 @@ const createModel = () => {
67
77
  findLatestEvents: vi.fn<InsightsModel["findLatestEvents"]>(async () => []),
68
78
  countLatestEvents: vi.fn<InsightsModel["countLatestEvents"]>(async () => 0),
69
79
  countEvents: vi.fn<InsightsModel["countEvents"]>(async () => 0),
80
+ getReleaseActivity: vi.fn<InsightsModel["getReleaseActivity"]>(
81
+ async () => ({
82
+ coverage: { kind: "complete", sinceMs: 0 },
83
+ data: [],
84
+ measuredAtMs: 0,
85
+ }),
86
+ ),
87
+ getAppUsage: vi.fn<InsightsModel["getAppUsage"]>(async () => ({
88
+ coverage: { kind: "complete", sinceMs: 0 },
89
+ activeInstallations: 0,
90
+ points: [],
91
+ appVersions: [],
92
+ versions: [],
93
+ platforms: [],
94
+ bundleDistribution: [],
95
+ measuredAtMs: 0,
96
+ })),
70
97
  } satisfies InsightsModel;
71
98
  return { ...model, model };
72
99
  };
@@ -84,7 +111,6 @@ describe("createInsightsProvider", () => {
84
111
  .mockResolvedValueOnce([
85
112
  eventRow(eventId(3), 999),
86
113
  eventRow(eventId(2), 900),
87
- eventRow(eventId(1), 800),
88
114
  ])
89
115
  .mockResolvedValueOnce([eventRow(eventId(1), 800)]);
90
116
  const provider = createInsightsProvider(fixture.model);
@@ -96,7 +122,7 @@ describe("createInsightsProvider", () => {
96
122
  expect(first.nextCursor).not.toBeNull();
97
123
  expect(fixture.listEvents).toHaveBeenNthCalledWith(1, {
98
124
  beforeReceivedAtMs: 1_000,
99
- limit: 3,
125
+ limit: 2,
100
126
  sinceMs: 0,
101
127
  filter: { kind: "all" },
102
128
  });
@@ -111,20 +137,160 @@ describe("createInsightsProvider", () => {
111
137
  expect(fixture.listEvents).toHaveBeenNthCalledWith(2, {
112
138
  after: { id: eventId(2), receivedAtMs: 900 },
113
139
  beforeReceivedAtMs: 1_000,
114
- limit: 3,
140
+ limit: 2,
115
141
  sinceMs: 0,
116
142
  filter: { kind: "all" },
117
143
  });
118
144
  });
119
145
 
120
- it("binds event cursors to their filter", async () => {
146
+ it("reads only `limit` rows, so a full last page is followed by an empty one", async () => {
121
147
  vi.useFakeTimers();
122
148
  vi.setSystemTime(1_000);
123
149
  const fixture = createModel();
124
- fixture.listEvents.mockResolvedValue([
125
- eventRow(eventId(2), 900),
126
- eventRow(eventId(1), 800),
150
+ fixture.listEvents
151
+ .mockResolvedValueOnce([
152
+ eventRow(eventId(2), 900),
153
+ eventRow(eventId(1), 800),
154
+ ])
155
+ .mockResolvedValueOnce([]);
156
+ const provider = createInsightsProvider(fixture.model);
157
+
158
+ const first = await provider.listEvents({ limit: 2 });
159
+ const second = await provider.listEvents({
160
+ cursor: first.nextCursor ?? undefined,
161
+ limit: 2,
162
+ });
163
+
164
+ expect(first.nextCursor).not.toBeNull();
165
+ expect(second).toMatchObject({ data: [], nextCursor: null });
166
+ expect(fixture.listEvents.mock.calls.map(([input]) => input.limit)).toEqual(
167
+ [2, 2],
168
+ );
169
+ });
170
+
171
+ it("lists the 90 days before the cutoff without a start, and all of an installation's history", async () => {
172
+ vi.useFakeTimers();
173
+ vi.setSystemTime(cutoff);
174
+ const fixture = createModel();
175
+ const provider = createInsightsProvider(fixture.model);
176
+
177
+ await provider.listEvents({});
178
+ await provider.listEvents({ beforeReceivedAtMs: cutoff - DAY, bundle });
179
+ await provider.listInstallationEvents({ installId: "install-1" });
180
+
181
+ expect(
182
+ fixture.listEvents.mock.calls.map(([input]) => [
183
+ input.sinceMs,
184
+ input.beforeReceivedAtMs,
185
+ ]),
186
+ ).toEqual([
187
+ [cutoff - 90 * DAY, cutoff],
188
+ [cutoff - 91 * DAY, cutoff - DAY],
189
+ [0, cutoff],
127
190
  ]);
191
+ });
192
+
193
+ it("answers 400 for a global or bundle range over 90 days, before the database", async () => {
194
+ const fixture = createModel();
195
+ const provider = createInsightsProvider(fixture.model);
196
+ const range = { beforeReceivedAtMs: cutoff, sinceMs: cutoff - 90 * DAY };
197
+
198
+ for (const input of [{}, { bundle }]) {
199
+ await expect(
200
+ provider.listEvents({ ...input, ...range, sinceMs: range.sinceMs - 1 }),
201
+ ).rejects.toThrow(
202
+ new InsightsBadRequestError(
203
+ "Insights event lists cover at most 90 days: send a sinceMs no more than 90 days before beforeReceivedAtMs.",
204
+ ),
205
+ );
206
+ await provider.listEvents({ ...input, ...range });
207
+ }
208
+ await provider.listInstallationEvents({
209
+ installId: "install-1",
210
+ beforeReceivedAtMs: cutoff,
211
+ sinceMs: 0,
212
+ });
213
+ expect(
214
+ fixture.listEvents.mock.calls.map(([input]) => input.sinceMs),
215
+ ).toEqual([range.sinceMs, range.sinceMs, 0]);
216
+ });
217
+
218
+ it("returns a cursor only after a full page, and keeps the first page's range", async () => {
219
+ vi.useFakeTimers();
220
+ vi.setSystemTime(cutoff);
221
+ const fixture = createModel();
222
+ fixture.listEvents
223
+ .mockResolvedValueOnce([
224
+ eventRow(eventId(3), cutoff - HOUR),
225
+ eventRow(eventId(2), cutoff - 30 * DAY),
226
+ ])
227
+ .mockResolvedValueOnce([eventRow(eventId(1), cutoff - 60 * DAY)]);
228
+ const provider = createInsightsProvider(fixture.model);
229
+
230
+ const first = await provider.listEvents({ limit: 2 });
231
+ // Days later, the next page still reads the first page's range.
232
+ vi.setSystemTime(cutoff + 3 * DAY);
233
+ const second = await provider.listEvents({
234
+ cursor: first.nextCursor ?? undefined,
235
+ limit: 2,
236
+ });
237
+
238
+ expect(
239
+ JSON.parse(Buffer.from(first.nextCursor!, "base64url").toString("utf8")),
240
+ ).toEqual({
241
+ after: { id: eventId(2), receivedAtMs: cutoff - 30 * DAY },
242
+ beforeReceivedAtMs: cutoff,
243
+ sinceMs: cutoff - 90 * DAY,
244
+ kind: "events",
245
+ filter: { kind: "all" },
246
+ version: 2,
247
+ });
248
+ expect(second).toMatchObject({
249
+ beforeReceivedAtMs: cutoff,
250
+ nextCursor: null,
251
+ });
252
+ expect(fixture.listEvents).toHaveBeenLastCalledWith({
253
+ after: { id: eventId(2), receivedAtMs: cutoff - 30 * DAY },
254
+ beforeReceivedAtMs: cutoff,
255
+ sinceMs: cutoff - 90 * DAY,
256
+ filter: { kind: "all" },
257
+ limit: 2,
258
+ });
259
+ // A short page and an empty one end the list.
260
+ await expect(provider.listEvents({ limit: 2 })).resolves.toMatchObject({
261
+ data: [],
262
+ nextCursor: null,
263
+ });
264
+ });
265
+
266
+ it("rejects the cursors that continued below a window of days", async () => {
267
+ const fixture = createModel();
268
+ const provider = createInsightsProvider(fixture.model);
269
+ const window = {
270
+ beforeReceivedAtMs: cutoff,
271
+ kind: "events",
272
+ filter: { kind: "all" },
273
+ version: 2,
274
+ };
275
+ for (const position of [
276
+ { olderThanMs: cutoff - 90 * DAY, sinceMs: cutoff - 150 * DAY },
277
+ { olderThanMs: cutoff - 10 * DAY, sinceMs: cutoff - 30 * DAY },
278
+ ]) {
279
+ const cursor = Buffer.from(
280
+ JSON.stringify({ ...window, ...position }),
281
+ ).toString("base64url");
282
+ await expect(provider.listEvents({ cursor })).rejects.toThrow(
283
+ new InsightsBadRequestError("Invalid Insights cursor."),
284
+ );
285
+ }
286
+ expect(fixture.listEvents).not.toHaveBeenCalled();
287
+ });
288
+
289
+ it("binds event cursors to their filter", async () => {
290
+ vi.useFakeTimers();
291
+ vi.setSystemTime(1_000);
292
+ const fixture = createModel();
293
+ fixture.listEvents.mockResolvedValue([eventRow(eventId(2), 900)]);
128
294
  const provider = createInsightsProvider(fixture.model);
129
295
  const allEvents = await provider.listEvents({ limit: 1 });
130
296
 
@@ -181,7 +347,7 @@ describe("createInsightsProvider", () => {
181
347
  ]);
182
348
  expect(fixture.listEvents).toHaveBeenCalledWith({
183
349
  beforeReceivedAtMs: 1_000,
184
- limit: 11,
350
+ limit: 10,
185
351
  sinceMs: 0,
186
352
  filter: { kind: "installationMovement", installId: "install-2" },
187
353
  });
@@ -192,7 +358,6 @@ describe("createInsightsProvider", () => {
192
358
  fixture.findLatestEvents.mockResolvedValue([
193
359
  installationRow("install-a"),
194
360
  installationRow("install-b"),
195
- installationRow("install-c"),
196
361
  ]);
197
362
  const provider = createInsightsProvider(fixture.model);
198
363
 
@@ -206,7 +371,7 @@ describe("createInsightsProvider", () => {
206
371
  "install-b",
207
372
  ]);
208
373
  expect(fixture.findLatestEvents).toHaveBeenCalledWith({
209
- limit: 3,
374
+ limit: 2,
210
375
  userId: "user-1",
211
376
  });
212
377
  await expect(
@@ -243,6 +408,31 @@ describe("createInsightsProvider", () => {
243
408
  expect(fixture.countEvents).not.toHaveBeenCalled();
244
409
  });
245
410
 
411
+ it("counts whole hours that end with the current one", async () => {
412
+ vi.useFakeTimers();
413
+ vi.setSystemTime(new Date("2026-08-12T10:25:00.000Z"));
414
+ const fixture = createModel();
415
+ fixture.countLatestEvents.mockResolvedValue(4);
416
+ const provider = createInsightsProvider(fixture.model);
417
+
418
+ const overview = await provider.getReportingOverview({
419
+ window: "24h",
420
+ platform: "ios",
421
+ channel: "production",
422
+ });
423
+
424
+ const end = Date.parse("2026-08-12T11:00:00.000Z");
425
+ expect(overview).toMatchObject({
426
+ beforeReceivedAtMs: end,
427
+ sinceMs: end - 24 * 60 * 60 * 1_000,
428
+ });
429
+ expect(fixture.countLatestEvents).toHaveBeenCalledWith({
430
+ platform: "ios",
431
+ channel: "production",
432
+ sinceMs: end - 24 * 60 * 60 * 1_000,
433
+ });
434
+ });
435
+
246
436
  it("attributes recovery to the source bundle and reuses its count predicate for drill-down", async () => {
247
437
  vi.useFakeTimers();
248
438
  vi.setSystemTime(new Date("2026-08-12T00:00:00.000Z"));
@@ -297,23 +487,14 @@ describe("createInsightsProvider", () => {
297
487
  expect(fixture.listEvents).toHaveBeenCalledWith({
298
488
  ...counted,
299
489
  filter: { kind: "bundle", ...counted.filter },
300
- limit: 51,
490
+ limit: 50,
301
491
  });
302
492
  });
303
493
 
304
494
  it("binds bundle cursors to the outcome, scope, bundle, and both time bounds", async () => {
305
495
  const fixture = createModel();
306
- fixture.listEvents.mockResolvedValue([
307
- eventRow(eventId(2), 900),
308
- eventRow(eventId(1), 800),
309
- ]);
496
+ fixture.listEvents.mockResolvedValue([eventRow(eventId(2), 900)]);
310
497
  const provider = createInsightsProvider(fixture.model);
311
- const bundle = {
312
- platform: "ios",
313
- channel: "production",
314
- bundleId: "bundle-after",
315
- outcome: "applied",
316
- } as const;
317
498
  const first = await provider.listEvents({
318
499
  bundle,
319
500
  sinceMs: 100,
@@ -337,10 +518,7 @@ describe("createInsightsProvider", () => {
337
518
 
338
519
  it("rejects forged event keys before they reach the database boundary", async () => {
339
520
  const fixture = createModel();
340
- fixture.listEvents.mockResolvedValue([
341
- eventRow(eventId(2), 900),
342
- eventRow(eventId(1), 800),
343
- ]);
521
+ fixture.listEvents.mockResolvedValue([eventRow(eventId(2), 900)]);
344
522
  const provider = createInsightsProvider(fixture.model);
345
523
  const first = await provider.listEvents({
346
524
  sinceMs: 100,
@@ -32,12 +32,24 @@ const MAX_EVENT_ID_LENGTH = 1_024;
32
32
  const MAX_IDENTITY_LENGTH = 255;
33
33
  const MAX_CURSOR_LENGTH = 8 * 1_024;
34
34
 
35
+ const HOUR_MS = 60 * 60 * 1_000;
36
+
35
37
  const WINDOW_MS: Record<ActiveInstallationWindow, number> = {
36
38
  "24h": 24 * 60 * 60 * 1_000,
37
39
  "7d": 7 * 24 * 60 * 60 * 1_000,
38
40
  "30d": 30 * 24 * 60 * 60 * 1_000,
39
41
  };
40
42
 
43
+ /**
44
+ * The longest receipt range, `[sinceMs, beforeReceivedAtMs)`, of a global or
45
+ * bundle event list, and the range one covers without `sinceMs`: raw events
46
+ * are listed inside a time range, as analytics products list them. It is a
47
+ * duration, so a range that ends mid-day touches 91 UTC days. Installation
48
+ * history is one index range and reads back to `sinceMs`.
49
+ */
50
+ export const EVENT_LIST_RANGE_MS = 90 * 24 * HOUR_MS;
51
+
52
+ /** A full page's last row, and the range every page of the list shares. */
41
53
  type EventCursorPayload = {
42
54
  readonly after: InsightsEventCursor;
43
55
  readonly beforeReceivedAtMs: number;
@@ -188,6 +200,8 @@ const readEventCursor = (
188
200
  filter: InsightsEventFilter,
189
201
  ): EventCursorPayload => {
190
202
  const cursor = decodeCursor(value);
203
+ // A cursor names the row it continues after. One that only held a time
204
+ // (`olderThanMs`) resumed below a window of days, which lists no longer do.
191
205
  if (
192
206
  !isRecord(cursor) ||
193
207
  cursor.version !== 2 ||
@@ -335,17 +349,34 @@ const toInstallationRow = (row: BundleEventRow): InstallationRow => ({
335
349
  username: row.metadata.username,
336
350
  });
337
351
 
338
- const pageEventRows = async <T extends EventHistoryRow>(
339
- model: InsightsModel,
352
+ interface EventBounds {
353
+ readonly beforeReceivedAtMs: number;
354
+ readonly sinceMs: number;
355
+ }
356
+
357
+ /**
358
+ * Where a list without `sinceMs` starts: 90 days before its cutoff for the
359
+ * global and bundle lists, at the first event for installation history.
360
+ */
361
+ const defaultEventStart = (
362
+ filter: InsightsEventFilter,
363
+ beforeReceivedAtMs: number,
364
+ ): number =>
365
+ filter.kind === "installationMovement"
366
+ ? 0
367
+ : Math.max(0, beforeReceivedAtMs - EVENT_LIST_RANGE_MS);
368
+
369
+ /** Whether a cursor's row lies inside the list's range. */
370
+ const isInsideBounds = ({ after }: EventCursorPayload, bounds: EventBounds) =>
371
+ after.receivedAtMs >= bounds.sinceMs &&
372
+ after.receivedAtMs < bounds.beforeReceivedAtMs;
373
+
374
+ /** A page's range: its cursor's, which the request may repeat but not change. */
375
+ const readEventBounds = (
340
376
  input: InsightsEventPageInput,
377
+ cursor: EventCursorPayload | undefined,
341
378
  filter: InsightsEventFilter,
342
- map: (row: BundleEventRow) => T,
343
- ) => {
344
- const limit = readLimit(input.limit);
345
- const cursor =
346
- input.cursor === undefined
347
- ? undefined
348
- : readEventCursor(input.cursor, filter);
379
+ ): EventBounds => {
349
380
  const beforeReceivedAtMs =
350
381
  cursor?.beforeReceivedAtMs ??
351
382
  (input.beforeReceivedAtMs === undefined
@@ -363,41 +394,63 @@ const pageEventRows = async <T extends EventHistoryRow>(
363
394
  const sinceMs =
364
395
  cursor?.sinceMs ??
365
396
  (input.sinceMs === undefined
366
- ? 0
397
+ ? defaultEventStart(filter, beforeReceivedAtMs)
367
398
  : requireTimestamp(input.sinceMs, "event start"));
399
+ const bounds = { beforeReceivedAtMs, sinceMs };
368
400
  if (
369
401
  sinceMs > beforeReceivedAtMs ||
370
402
  (input.sinceMs !== undefined && input.sinceMs !== sinceMs) ||
371
- (cursor !== undefined &&
372
- (cursor.after.receivedAtMs < sinceMs ||
373
- cursor.after.receivedAtMs >= beforeReceivedAtMs))
403
+ (cursor !== undefined && !isInsideBounds(cursor, bounds))
374
404
  ) {
375
405
  throw new InsightsBadRequestError(
376
406
  "Insights cursor or range does not match the requested event start.",
377
407
  );
378
408
  }
379
- const databaseInput = {
409
+ if (
410
+ filter.kind !== "installationMovement" &&
411
+ beforeReceivedAtMs - sinceMs > EVENT_LIST_RANGE_MS
412
+ ) {
413
+ throw new InsightsBadRequestError(
414
+ "Insights event lists cover at most 90 days: send a sinceMs no more than 90 days before beforeReceivedAtMs.",
415
+ );
416
+ }
417
+ return bounds;
418
+ };
419
+
420
+ const pageEventRows = async <T extends EventHistoryRow>(
421
+ model: InsightsModel,
422
+ input: InsightsEventPageInput,
423
+ filter: InsightsEventFilter,
424
+ map: (row: BundleEventRow) => T,
425
+ ) => {
426
+ const limit = readLimit(input.limit);
427
+ const cursor =
428
+ input.cursor === undefined
429
+ ? undefined
430
+ : readEventCursor(input.cursor, filter);
431
+ const bounds = readEventBounds(input, cursor, filter);
432
+ const query = {
380
433
  filter,
381
- sinceMs,
382
- beforeReceivedAtMs,
434
+ ...bounds,
383
435
  ...(cursor === undefined ? {} : { after: cursor.after }),
384
- limit: limit + 1,
436
+ limit,
385
437
  };
386
- const rows = await model.listEvents(databaseInput);
387
- assertEventRows(rows, databaseInput);
388
- const pageRows = rows.slice(0, limit);
389
- const last = pageRows.at(-1);
438
+ const rows = await model.listEvents(query);
439
+ assertEventRows(rows, query);
440
+ const last = rows.at(-1);
390
441
  return {
391
- beforeReceivedAtMs,
392
- data: pageRows.map(map),
442
+ beforeReceivedAtMs: bounds.beforeReceivedAtMs,
443
+ data: rows.map(map),
444
+ // A short page ends the range, so only a full page carries a cursor,
445
+ // with the range every page shares: the last call may read an empty
446
+ // page, and no page ends before the range start while events remain.
393
447
  nextCursor:
394
- rows.length > limit && last
448
+ rows.length === limit && last
395
449
  ? encodeCursor({
396
450
  after: { id: last.id, receivedAtMs: last.received_at_ms },
397
- beforeReceivedAtMs,
451
+ ...bounds,
398
452
  kind: "events",
399
453
  filter,
400
- sinceMs,
401
454
  version: 2,
402
455
  })
403
456
  : null,
@@ -502,16 +555,15 @@ export const createInsightsProvider = (
502
555
  ...(cursor === undefined
503
556
  ? {}
504
557
  : { afterInstallId: cursor.afterInstallId }),
505
- limit: limit + 1,
558
+ limit,
506
559
  };
507
560
  const rows = await model.findLatestEvents(databaseInput);
508
561
  assertInstallationRows(rows, databaseInput);
509
- const pageRows = rows.slice(0, limit);
510
- const last = pageRows.at(-1);
562
+ const last = rows.at(-1);
511
563
  return {
512
- data: pageRows.map(toInstallationRow),
564
+ data: rows.map(toInstallationRow),
513
565
  nextCursor:
514
- rows.length > limit && last
566
+ rows.length === limit && last
515
567
  ? encodeCursor({
516
568
  afterInstallId: last.install_id,
517
569
  kind: "user-installations",
@@ -529,7 +581,11 @@ export const createInsightsProvider = (
529
581
  "Invalid reporting installation window.",
530
582
  );
531
583
  }
532
- const beforeReceivedAtMs = Date.now();
584
+ // Whole hours, ending with the current one: the counts read only the
585
+ // maintained hour and day rows, never raw events.
586
+ const now = Date.now();
587
+ const beforeReceivedAtMs =
588
+ now - (now % HOUR_MS) + (now % HOUR_MS === 0 ? 0 : HOUR_MS);
533
589
  const sinceMs = Math.max(0, beforeReceivedAtMs - WINDOW_MS[window]);
534
590
  const measure = async (count: Promise<number>) => {
535
591
  const value = await count;
@@ -1,7 +1,7 @@
1
- import type { RouteHandler } from "../handlerTypes";
2
1
  import {
3
2
  InsightsBadRequestError,
4
3
  InsightsPayloadTooLargeError,
4
+ isDatabaseBusyError,
5
5
  } from "./errors";
6
6
  import { parseBundleEventRequest } from "./eventInput";
7
7
  import {
@@ -11,9 +11,16 @@ import {
11
11
  } from "./queryInput";
12
12
  import type { InsightsProvider } from "./types";
13
13
 
14
- const json = (body: unknown, status: number): Response =>
14
+ /** Seconds a client waits before it resends a request the busy database refused. */
15
+ const RETRY_AFTER_SECONDS = 5;
16
+
17
+ const json = (
18
+ body: unknown,
19
+ status: number,
20
+ headers: Readonly<Record<string, string>> = {},
21
+ ): Response =>
15
22
  Response.json(body, {
16
- headers: { "cache-control": "private, no-store" },
23
+ headers: { "cache-control": "private, no-store", ...headers },
17
24
  status,
18
25
  });
19
26
 
@@ -42,6 +49,18 @@ const run = async (operation: () => Promise<Response>): Promise<Response> => {
42
49
  if (error instanceof InsightsPayloadTooLargeError) {
43
50
  return json({ error: error.message }, 413);
44
51
  }
52
+ // Back-pressure: a busy database has not failed the request, so the
53
+ // client may send it again after Retry-After. A 500 would read as a fault.
54
+ // The warning keeps the overload visible to whoever runs the server.
55
+ if (isDatabaseBusyError(error)) {
56
+ console.warn(
57
+ "[hot-updater] Insights answered 503: the database is busy.",
58
+ error,
59
+ );
60
+ return json({ error: "Service unavailable" }, 503, {
61
+ "retry-after": String(RETRY_AFTER_SECONDS),
62
+ });
63
+ }
45
64
  throw error;
46
65
  }
47
66
  };
@@ -49,9 +68,81 @@ const run = async (operation: () => Promise<Response>): Promise<Response> => {
49
68
  const query = (operation: () => Promise<unknown>): Promise<Response> =>
50
69
  run(async () => json(await operation(), 200));
51
70
 
71
+ /** Every Insights route: where it mounts, and the handler it runs. */
72
+ export const INSIGHTS_ROUTES = [
73
+ {
74
+ access: "client",
75
+ method: "POST",
76
+ path: "/events",
77
+ handler: "appendBundleEvent",
78
+ },
79
+ { access: "admin", method: "GET", path: "/events", handler: "listEvents" },
80
+ {
81
+ access: "admin",
82
+ method: "GET",
83
+ path: "/overview",
84
+ handler: "getReportingOverview",
85
+ },
86
+ {
87
+ access: "admin",
88
+ method: "GET",
89
+ path: "/installations",
90
+ handler: "pageInstallationsByCurrentUserId",
91
+ },
92
+ {
93
+ access: "admin",
94
+ method: "GET",
95
+ path: "/installations/:installId/events",
96
+ handler: "listInstallationEvents",
97
+ },
98
+ {
99
+ access: "admin",
100
+ method: "GET",
101
+ path: "/installations/:installId",
102
+ handler: "getInstallation",
103
+ },
104
+ ] as const;
105
+
106
+ export type InsightsRouteName = (typeof INSIGHTS_ROUTES)[number]["handler"];
107
+
108
+ export type InsightsRouteHandler = (
109
+ params: Readonly<Record<string, string>>,
110
+ request: Request,
111
+ ) => Promise<Response>;
112
+
113
+ /** Without the insights plugin, its routes answer 204 and say Insights is off. */
114
+ export const insightsDisabled: InsightsRouteHandler = async () =>
115
+ new Response(null, {
116
+ headers: {
117
+ "cache-control": "private, no-store",
118
+ "x-hot-updater-insights": "disabled",
119
+ },
120
+ status: 204,
121
+ });
122
+
123
+ /** What a server without the insights plugin logs on the first event it drops. */
124
+ export const INSIGHTS_OFF_WARNING =
125
+ "[hot-updater] Insights is off, so POST /events answered 204 and stored nothing. Add insights() from @hot-updater/server/plugins/insights to plugins to record app events, or set insights: false in the app's HotUpdater.init to stop sending them. This warning prints once per server.";
126
+
127
+ /**
128
+ * The client's `POST /events` without the insights plugin. It answers as
129
+ * {@link insightsDisabled} does, so apps need no change, and warns on the
130
+ * first event it drops, so a server that left out `insights()` says so.
131
+ */
132
+ export const createDroppedEventHandler = (): InsightsRouteHandler => {
133
+ let warned = false;
134
+ return (params, request) => {
135
+ if (!warned) {
136
+ warned = true;
137
+ console.warn(INSIGHTS_OFF_WARNING);
138
+ }
139
+ return insightsDisabled(params, request);
140
+ };
141
+ };
142
+
52
143
  export const createInsightsRouteHandlers = (
53
144
  provider: InsightsProvider,
54
- ): Record<string, RouteHandler> => ({
145
+ ): Record<InsightsRouteName, InsightsRouteHandler> => ({
55
146
  appendBundleEvent: async (_params, request) =>
56
147
  run(async () => {
57
148
  await provider.appendBundleEvent(await parseBundleEventRequest(request));
@@ -86,19 +177,3 @@ export const createInsightsRouteHandlers = (
86
177
  ),
87
178
  ),
88
179
  });
89
-
90
- export const registerInsightsClientRoutes = (
91
- add: (method: string, path: string, handler: string) => void,
92
- ): void => {
93
- add("POST", "/events", "appendBundleEvent");
94
- };
95
-
96
- export const registerInsightsAdminRoutes = (
97
- add: (method: string, path: string, handler: string) => void,
98
- ): void => {
99
- add("GET", "/events", "listEvents");
100
- add("GET", "/overview", "getReportingOverview");
101
- add("GET", "/installations", "pageInstallationsByCurrentUserId");
102
- add("GET", "/installations/:installId/events", "listInstallationEvents");
103
- add("GET", "/installations/:installId", "getInstallation");
104
- };