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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/dist/adapters/databaseConstraintErrors.cjs +23 -0
  2. package/dist/adapters/databaseConstraintErrors.mjs +21 -0
  3. package/dist/adapters/databasePluginUtils.cjs +117 -0
  4. package/dist/adapters/databasePluginUtils.mjs +106 -0
  5. package/dist/adapters/drizzle.cjs +53 -176
  6. package/dist/adapters/drizzle.d.cts +6 -4
  7. package/dist/adapters/drizzle.d.mts +6 -4
  8. package/dist/adapters/drizzle.mjs +54 -177
  9. package/dist/adapters/drizzleCrud.cjs +250 -0
  10. package/dist/adapters/drizzleCrud.mjs +249 -0
  11. package/dist/adapters/drizzleLazyDB.cjs +84 -36
  12. package/dist/adapters/drizzleLazyDB.mjs +84 -36
  13. package/dist/adapters/drizzleQuery.cjs +68 -0
  14. package/dist/adapters/drizzleQuery.mjs +67 -0
  15. package/dist/adapters/kysely.cjs +18 -138
  16. package/dist/adapters/kysely.d.cts +3 -12
  17. package/dist/adapters/kysely.d.mts +3 -12
  18. package/dist/adapters/kysely.mjs +19 -139
  19. package/dist/adapters/kyselyCrud.cjs +254 -0
  20. package/dist/adapters/kyselyCrud.mjs +253 -0
  21. package/dist/adapters/kyselyQuery.cjs +56 -0
  22. package/dist/adapters/kyselyQuery.mjs +55 -0
  23. package/dist/adapters/mongodb.cjs +21 -174
  24. package/dist/adapters/mongodb.d.cts +3 -2
  25. package/dist/adapters/mongodb.d.mts +3 -2
  26. package/dist/adapters/mongodb.mjs +21 -174
  27. package/dist/adapters/mongodbCollections.cjs +36 -0
  28. package/dist/adapters/mongodbCollections.mjs +30 -0
  29. package/dist/adapters/mongodbQuery.cjs +77 -0
  30. package/dist/adapters/mongodbQuery.mjs +70 -0
  31. package/dist/adapters/mongodbReads.cjs +148 -0
  32. package/dist/adapters/mongodbReads.mjs +147 -0
  33. package/dist/adapters/mongodbWrites.cjs +134 -0
  34. package/dist/adapters/mongodbWrites.mjs +133 -0
  35. package/dist/adapters/prisma.cjs +205 -180
  36. package/dist/adapters/prisma.d.cts +3 -3
  37. package/dist/adapters/prisma.d.mts +3 -3
  38. package/dist/adapters/prisma.mjs +206 -181
  39. package/dist/adapters/prismaQuery.cjs +58 -0
  40. package/dist/adapters/prismaQuery.mjs +56 -0
  41. package/dist/adapters/prismaRows.cjs +198 -0
  42. package/dist/adapters/prismaRows.mjs +188 -0
  43. package/dist/apiKeys.cjs +139 -0
  44. package/dist/apiKeys.d.cts +50 -0
  45. package/dist/apiKeys.d.mts +50 -0
  46. package/dist/apiKeys.mjs +133 -0
  47. package/dist/createHotUpdaterCore.cjs +48 -26
  48. package/dist/createHotUpdaterCore.d.cts +49 -17
  49. package/dist/createHotUpdaterCore.d.mts +49 -17
  50. package/dist/createHotUpdaterCore.mjs +51 -29
  51. package/dist/db/createBundleDiff.cjs +45 -68
  52. package/dist/db/createBundleDiff.d.cts +3 -3
  53. package/dist/db/createBundleDiff.d.mts +3 -3
  54. package/dist/db/createBundleDiff.mjs +47 -68
  55. package/dist/db/databasePluginCore.cjs +138 -0
  56. package/dist/db/databasePluginCore.mjs +137 -0
  57. package/dist/db/fixedMigrator.cjs +36 -93
  58. package/dist/db/fixedMigrator.mjs +36 -93
  59. package/dist/db/fixedMigratorMongo.cjs +319 -0
  60. package/dist/db/fixedMigratorMongo.mjs +319 -0
  61. package/dist/db/fixedMigratorShared.cjs +23 -0
  62. package/dist/db/fixedMigratorShared.mjs +18 -0
  63. package/dist/db/mongoMigrationExecution.cjs +11 -0
  64. package/dist/db/mongoMigrationExecution.mjs +11 -0
  65. package/dist/db/releaseCatalog.cjs +66 -0
  66. package/dist/db/releaseCatalog.d.cts +17 -0
  67. package/dist/db/releaseCatalog.d.mts +17 -0
  68. package/dist/db/releaseCatalog.mjs +64 -0
  69. package/dist/db/schema/mongodb.cjs +14 -6
  70. package/dist/db/schema/mongodb.mjs +14 -6
  71. package/dist/db/schema/registry.cjs +0 -2
  72. package/dist/db/schema/registry.mjs +1 -2
  73. package/dist/db/schema/sql.cjs +31 -14
  74. package/dist/db/schema/sql.mjs +32 -13
  75. package/dist/db/schema/sqlOperations.cjs +4 -4
  76. package/dist/db/schema/sqlOperations.mjs +5 -5
  77. package/dist/db/schemaGenerators.cjs +52 -18
  78. package/dist/db/schemaGenerators.mjs +52 -18
  79. package/dist/db/schemaReadiness.cjs +4 -3
  80. package/dist/db/schemaReadiness.mjs +4 -3
  81. package/dist/db/sqlMigrationExecution.cjs +67 -0
  82. package/dist/db/sqlMigrationExecution.mjs +66 -0
  83. package/dist/db/types.cjs +2 -6
  84. package/dist/db/types.d.cts +60 -16
  85. package/dist/db/types.d.mts +60 -16
  86. package/dist/db/types.mjs +3 -6
  87. package/dist/db/updateArtifacts.cjs +114 -60
  88. package/dist/db/updateArtifacts.mjs +117 -62
  89. package/dist/handler.cjs +108 -282
  90. package/dist/handler.d.cts +3 -46
  91. package/dist/handler.d.mts +3 -46
  92. package/dist/handler.mjs +107 -281
  93. package/dist/handlerBundleRoutes.cjs +155 -0
  94. package/dist/handlerBundleRoutes.mjs +155 -0
  95. package/dist/handlerErrors.cjs +9 -0
  96. package/dist/handlerErrors.mjs +9 -0
  97. package/dist/handlerParameters.cjs +39 -0
  98. package/dist/handlerParameters.mjs +34 -0
  99. package/dist/handlerReleaseCatalogRoutes.cjs +111 -0
  100. package/dist/handlerReleaseCatalogRoutes.mjs +110 -0
  101. package/dist/handlerReleaseManagementRoutes.cjs +125 -0
  102. package/dist/handlerReleaseManagementRoutes.mjs +124 -0
  103. package/dist/handlerTypes.d.cts +63 -0
  104. package/dist/handlerTypes.d.mts +63 -0
  105. package/dist/handlerVersionRoutes.cjs +10 -0
  106. package/dist/handlerVersionRoutes.d.cts +4 -0
  107. package/dist/handlerVersionRoutes.d.mts +4 -0
  108. package/dist/handlerVersionRoutes.mjs +9 -0
  109. package/dist/index.cjs +10 -1
  110. package/dist/index.d.cts +9 -3
  111. package/dist/index.d.mts +9 -3
  112. package/dist/index.mjs +5 -2
  113. package/dist/insights/bounded/activeOverview.cjs +87 -0
  114. package/dist/insights/bounded/activeOverview.mjs +85 -0
  115. package/dist/insights/bounded/installationSearch.cjs +42 -0
  116. package/dist/insights/bounded/installationSearch.mjs +42 -0
  117. package/dist/insights/bounded/persistence.cjs +39 -0
  118. package/dist/insights/bounded/persistence.mjs +38 -0
  119. package/dist/insights/bounded/provider.cjs +170 -0
  120. package/dist/insights/bounded/provider.d.cts +7 -0
  121. package/dist/insights/bounded/provider.d.mts +7 -0
  122. package/dist/insights/bounded/provider.mjs +170 -0
  123. package/dist/insights/bounded/scan.cjs +144 -0
  124. package/dist/insights/bounded/scan.mjs +138 -0
  125. package/dist/insights/domain.d.cts +121 -0
  126. package/dist/insights/domain.d.mts +121 -0
  127. package/dist/insights/errors.cjs +22 -0
  128. package/dist/insights/errors.mjs +20 -0
  129. package/dist/insights/eventInput.cjs +111 -0
  130. package/dist/insights/eventInput.mjs +111 -0
  131. package/dist/insights/persistence.d.cts +6 -0
  132. package/dist/insights/persistence.d.mts +6 -0
  133. package/dist/insights/queryInput.cjs +58 -0
  134. package/dist/insights/queryInput.mjs +55 -0
  135. package/dist/insights/routes.cjs +63 -0
  136. package/dist/insights/routes.mjs +61 -0
  137. package/dist/insights/types.d.cts +17 -0
  138. package/dist/insights/types.d.mts +17 -0
  139. package/dist/node.cjs +19 -12
  140. package/dist/node.d.cts +15 -10
  141. package/dist/node.d.mts +15 -10
  142. package/dist/node.mjs +19 -13
  143. package/dist/package.cjs +1 -1
  144. package/dist/package.mjs +1 -1
  145. package/dist/schema/dsl.cjs +19 -2
  146. package/dist/schema/dsl.mjs +17 -3
  147. package/dist/schema/index.cjs +1 -8
  148. package/dist/schema/index.mjs +2 -8
  149. package/dist/schema/types.cjs +3 -1
  150. package/dist/schema/types.mjs +3 -2
  151. package/dist/schema/v1_0_0.cjs +364 -0
  152. package/dist/schema/v1_0_0.mjs +357 -0
  153. package/dist/storageAccess.cjs +69 -25
  154. package/dist/storageAccess.mjs +68 -25
  155. package/dist/types/index.d.cts +4 -3
  156. package/dist/types/index.d.mts +5 -4
  157. package/package.json +8 -7
  158. package/src/adapters/databaseConstraintErrors.ts +25 -0
  159. package/src/adapters/databasePluginTestDatabase.ts +106 -0
  160. package/src/adapters/databasePluginUtils.spec.ts +29 -0
  161. package/src/adapters/databasePluginUtils.ts +214 -0
  162. package/src/adapters/drizzle.spec.ts +208 -47
  163. package/src/adapters/drizzle.ts +106 -293
  164. package/src/adapters/drizzleCrud.ts +419 -0
  165. package/src/adapters/drizzleLazyDB.ts +206 -100
  166. package/src/adapters/drizzleQuery.ts +164 -0
  167. package/src/adapters/kysely.spec.ts +126 -88
  168. package/src/adapters/kysely.ts +76 -334
  169. package/src/adapters/kyselyCrud.ts +588 -0
  170. package/src/adapters/kyselyQuery.ts +128 -0
  171. package/src/adapters/mongodb.spec.ts +141 -0
  172. package/src/adapters/mongodb.ts +60 -288
  173. package/src/adapters/mongodbCollections.ts +62 -0
  174. package/src/adapters/mongodbQuery.ts +226 -0
  175. package/src/adapters/mongodbReads.ts +311 -0
  176. package/src/adapters/mongodbRows.spec.ts +64 -0
  177. package/src/adapters/mongodbRows.ts +118 -0
  178. package/src/adapters/mongodbTestClient.ts +342 -0
  179. package/src/adapters/mongodbTestFilter.ts +166 -0
  180. package/src/adapters/mongodbWrites.ts +260 -0
  181. package/src/adapters/prisma.spec.ts +196 -0
  182. package/src/adapters/prisma.ts +417 -292
  183. package/src/adapters/prismaQuery.ts +129 -0
  184. package/src/adapters/prismaRows.spec.ts +98 -0
  185. package/src/adapters/prismaRows.ts +301 -0
  186. package/src/adapters/prismaTestClient.ts +396 -0
  187. package/src/apiKeys.integration.spec.ts +192 -0
  188. package/src/apiKeys.spec.ts +162 -0
  189. package/src/apiKeys.ts +220 -0
  190. package/src/createHotUpdaterCore.ts +164 -100
  191. package/src/db/bundleRows.spec.ts +164 -0
  192. package/src/db/bundleRows.ts +10 -140
  193. package/src/db/createBundleDiff.spec.ts +172 -127
  194. package/src/db/createBundleDiff.ts +91 -110
  195. package/src/db/databasePluginCore.spec.ts +62 -0
  196. package/src/db/databasePluginCore.testFixtures.ts +76 -0
  197. package/src/db/databasePluginCore.ts +185 -0
  198. package/src/db/databasePluginUpdateCheck.bench.ts +107 -0
  199. package/src/db/fixedMigrator.spec.ts +184 -42
  200. package/src/db/fixedMigrator.ts +64 -205
  201. package/src/db/fixedMigratorMongo.spec.ts +192 -0
  202. package/src/db/fixedMigratorMongo.ts +425 -0
  203. package/src/db/fixedMigratorShared.ts +36 -0
  204. package/src/db/hotUpdaterSchema.ts +0 -1
  205. package/src/db/index.spec.ts +333 -1025
  206. package/src/db/mongoMigrationExecution.ts +23 -0
  207. package/src/db/node-sqlite.d.ts +25 -0
  208. package/src/db/releaseCatalog.ts +148 -0
  209. package/src/db/schema/mongodb.ts +20 -6
  210. package/src/db/schema/sql.ts +52 -11
  211. package/src/db/schema/sqlOperations.ts +5 -4
  212. package/src/db/schemaGenerators.ts +107 -23
  213. package/src/db/schemaReadiness.ts +2 -1
  214. package/src/db/sqlMigrationExecution.spec.ts +65 -0
  215. package/src/db/sqlMigrationExecution.ts +108 -0
  216. package/src/db/types.spec.ts +55 -0
  217. package/src/db/types.ts +129 -65
  218. package/src/db/updateArtifacts.spec.ts +359 -0
  219. package/src/db/updateArtifacts.ts +290 -126
  220. package/src/handler-standalone.integration.spec.ts +401 -364
  221. package/src/handler.spec.ts +137 -582
  222. package/src/handler.testFixtures.ts +30 -0
  223. package/src/handler.ts +218 -616
  224. package/src/handlerAuth.integration.spec.ts +33 -0
  225. package/src/handlerBundleRoutes.ts +259 -0
  226. package/src/handlerErrors.ts +7 -0
  227. package/src/handlerManagement.spec.ts +262 -0
  228. package/src/handlerParameters.ts +105 -0
  229. package/src/handlerReleaseCatalogRoutes.ts +154 -0
  230. package/src/handlerReleaseManagementRoutes.ts +213 -0
  231. package/src/handlerTypes.ts +96 -0
  232. package/src/handlerVersionRoutes.ts +12 -0
  233. package/src/index.ts +22 -2
  234. package/src/insights/bounded/activeOverview.spec.ts +189 -0
  235. package/src/insights/bounded/activeOverview.ts +148 -0
  236. package/src/insights/bounded/installationSearch.spec.ts +130 -0
  237. package/src/insights/bounded/installationSearch.ts +69 -0
  238. package/src/insights/bounded/persistence.ts +43 -0
  239. package/src/insights/bounded/provider.spec.ts +219 -0
  240. package/src/insights/bounded/provider.ts +229 -0
  241. package/src/insights/bounded/scan.ts +235 -0
  242. package/src/insights/domain.ts +139 -0
  243. package/src/insights/errors.ts +18 -0
  244. package/src/insights/eventInput.ts +168 -0
  245. package/src/insights/insights.integration.spec.ts +144 -0
  246. package/src/insights/persistence.ts +10 -0
  247. package/src/insights/queryInput.ts +102 -0
  248. package/src/insights/routes.ts +123 -0
  249. package/src/insights/types.ts +44 -0
  250. package/src/node.spec.ts +79 -10
  251. package/src/node.ts +20 -17
  252. package/src/releaseCatalogRoutes.integration.spec.ts +238 -0
  253. package/src/runtime.spec.ts +126 -959
  254. package/src/runtime.testFixtures.ts +71 -0
  255. package/src/schema/dsl-all-versions.spec.ts +26 -17
  256. package/src/schema/dsl.ts +41 -2
  257. package/src/schema/index.ts +3 -9
  258. package/src/schema/types.ts +9 -3
  259. package/src/schema/v1_0_0.spec.ts +146 -0
  260. package/src/schema/v1_0_0.ts +465 -0
  261. package/src/storageAccess.spec.ts +127 -32
  262. package/src/storageAccess.ts +106 -55
  263. package/src/types/index.ts +2 -1
  264. package/dist/db/bundleRows.cjs +0 -82
  265. package/dist/db/bundleRows.d.cts +0 -33
  266. package/dist/db/bundleRows.d.mts +0 -33
  267. package/dist/db/bundleRows.mjs +0 -79
  268. package/dist/db/pluginCore.cjs +0 -242
  269. package/dist/db/pluginCore.mjs +0 -241
  270. package/dist/db/schema/sqlMigrations.cjs +0 -74
  271. package/dist/db/schema/sqlMigrations.mjs +0 -73
  272. package/dist/db/schemaEnhancements.cjs +0 -20
  273. package/dist/db/schemaEnhancements.mjs +0 -19
  274. package/dist/route.cjs +0 -7
  275. package/dist/route.mjs +0 -7
  276. package/dist/schema/v0_21_0.cjs +0 -38
  277. package/dist/schema/v0_21_0.mjs +0 -37
  278. package/dist/schema/v0_29_0.cjs +0 -45
  279. package/dist/schema/v0_29_0.mjs +0 -44
  280. package/dist/schema/v0_31_0.cjs +0 -82
  281. package/dist/schema/v0_31_0.mjs +0 -80
  282. package/src/db/pluginCore.spec.ts +0 -1095
  283. package/src/db/pluginCore.ts +0 -475
  284. package/src/db/pluginUpdateCheck.bench.ts +0 -254
  285. package/src/db/schema/sqlMigrations.ts +0 -219
  286. package/src/db/schemaEnhancements.ts +0 -55
  287. package/src/route.ts +0 -7
  288. package/src/schema/v0_21_0.ts +0 -52
  289. package/src/schema/v0_29_0.ts +0 -62
  290. package/src/schema/v0_31_0.spec.ts +0 -73
  291. package/src/schema/v0_31_0.ts +0 -120
@@ -0,0 +1,38 @@
1
+ import { createUUIDv7 } from "@hot-updater/plugin-core";
2
+ //#region src/insights/bounded/persistence.ts
3
+ function createBundleEventRow(input) {
4
+ const base = {
5
+ id: createUUIDv7(),
6
+ install_id: input.installId,
7
+ user_id: input.userId ?? null,
8
+ username: input.username ?? null,
9
+ from_release_id: input.fromReleaseId,
10
+ to_release_id: input.toReleaseId,
11
+ to_bundle_id: input.toBundleId,
12
+ platform: input.platform,
13
+ app_version: input.appVersion,
14
+ channel: input.channel,
15
+ cohort: input.cohort,
16
+ fingerprint_hash: input.fingerprintHash,
17
+ sdk_version: input.sdkVersion ?? null,
18
+ received_at_ms: Date.now()
19
+ };
20
+ switch (input.type) {
21
+ case "UPDATE_APPLIED":
22
+ case "RECOVERED":
23
+ case "RELEASE_ADOPTED": return {
24
+ ...base,
25
+ type: input.type,
26
+ from_bundle_id: input.fromBundleId,
27
+ update_strategy: input.updateStrategy
28
+ };
29
+ case "UNCHANGED": return {
30
+ ...base,
31
+ type: input.type,
32
+ from_bundle_id: null,
33
+ update_strategy: null
34
+ };
35
+ }
36
+ }
37
+ //#endregion
38
+ export { createBundleEventRow };
@@ -0,0 +1,170 @@
1
+ const require_activeOverview = require("./activeOverview.cjs");
2
+ const require_scan = require("./scan.cjs");
3
+ const require_installationSearch = require("./installationSearch.cjs");
4
+ const require_persistence = require("./persistence.cjs");
5
+ //#region src/insights/bounded/provider.ts
6
+ const isTransitionEventRow = (row) => row.type === "UPDATE_APPLIED" || row.type === "RECOVERED";
7
+ const toHistoryRow = (row) => ({
8
+ id: row.id,
9
+ type: row.type,
10
+ fromBundleId: row.from_bundle_id,
11
+ toBundleId: row.to_bundle_id,
12
+ username: row.username,
13
+ userId: row.user_id,
14
+ platform: row.platform,
15
+ appVersion: row.app_version,
16
+ channel: row.channel,
17
+ cohort: row.cohort,
18
+ receivedAtMs: row.received_at_ms
19
+ });
20
+ const isInstalledForBundle = (row, bundleId) => row.type === "UPDATE_APPLIED" && row.to_bundle_id === bundleId;
21
+ const isRecoveredFromBundle = (row, bundleId) => row.type === "RECOVERED" && row.from_bundle_id === bundleId;
22
+ const countDistinctInstallations = (rows) => new Set(rows.map(({ install_id }) => install_id)).size;
23
+ const getInsightsResult = async (persistence, bundleId, window, limit, offset) => {
24
+ const scope = {
25
+ persistence,
26
+ cutoffMs: Date.now()
27
+ };
28
+ const rows = (await require_scan.materializeRowsForWindow(scope, window)).filter(isTransitionEventRow);
29
+ const installedRows = rows.filter((row) => isInstalledForBundle(row, bundleId));
30
+ const recoveredRows = rows.filter((row) => isRecoveredFromBundle(row, bundleId));
31
+ const installed = require_scan.collectEventActivity({
32
+ rows: installedRows,
33
+ window,
34
+ cutoffMs: scope.cutoffMs
35
+ });
36
+ const recovered = require_scan.collectEventActivity({
37
+ rows: recoveredRows,
38
+ window,
39
+ cutoffMs: scope.cutoffMs
40
+ });
41
+ const recentRows = [...installedRows, ...recoveredRows].sort(require_scan.compareEventNewest);
42
+ return {
43
+ summary: {
44
+ installed: installed.summary,
45
+ recovered: recovered.summary
46
+ },
47
+ series: {
48
+ installed: installed.series,
49
+ recovered: recovered.series
50
+ },
51
+ cohorts: {
52
+ installed: installed.cohorts,
53
+ recovered: recovered.cohorts
54
+ },
55
+ recentEvents: {
56
+ data: recentRows.slice(offset, offset + limit).map(toHistoryRow),
57
+ pagination: {
58
+ total: recentRows.length,
59
+ limit,
60
+ offset
61
+ }
62
+ }
63
+ };
64
+ };
65
+ const getInsightsSummaries = async (persistence, bundleIds, window) => {
66
+ const normalizedBundleIds = [...new Set(bundleIds)];
67
+ if (normalizedBundleIds.length === 0) return [];
68
+ const requestedBundleIds = new Set(normalizedBundleIds);
69
+ const installedByBundleId = /* @__PURE__ */ new Map();
70
+ const recoveredByBundleId = /* @__PURE__ */ new Map();
71
+ const rows = (await require_scan.materializeRowsForWindow({
72
+ persistence,
73
+ cutoffMs: Date.now()
74
+ }, window)).filter(isTransitionEventRow);
75
+ for (const row of rows) {
76
+ const bundleId = row.type === "UPDATE_APPLIED" ? row.to_bundle_id : row.from_bundle_id;
77
+ if (!requestedBundleIds.has(bundleId)) continue;
78
+ const counts = row.type === "UPDATE_APPLIED" ? installedByBundleId : recoveredByBundleId;
79
+ const installIds = counts.get(bundleId) ?? /* @__PURE__ */ new Set();
80
+ installIds.add(row.install_id);
81
+ counts.set(bundleId, installIds);
82
+ }
83
+ return normalizedBundleIds.map((bundleId) => ({
84
+ bundleId,
85
+ installed: installedByBundleId.get(bundleId)?.size ?? 0,
86
+ recovered: recoveredByBundleId.get(bundleId)?.size ?? 0
87
+ }));
88
+ };
89
+ const createInsightsProvider = (persistence) => Object.freeze({
90
+ mode: "bounded",
91
+ maxMatchingRows: require_scan.INSIGHTS_SCAN_MAX_ROWS,
92
+ async appendBundleEvent(input) {
93
+ await persistence.append(require_persistence.createBundleEventRow(input));
94
+ },
95
+ async getBundleEventSummary(bundleId) {
96
+ const rows = (await require_scan.materializeEventRows({
97
+ persistence,
98
+ cutoffMs: Date.now()
99
+ })).filter(isTransitionEventRow);
100
+ return {
101
+ installed: countDistinctInstallations(rows.filter((row) => isInstalledForBundle(row, bundleId))),
102
+ recovered: countDistinctInstallations(rows.filter((row) => isRecoveredFromBundle(row, bundleId)))
103
+ };
104
+ },
105
+ getBundleEventSummaries(bundleIds, window) {
106
+ return getInsightsSummaries(persistence, bundleIds, window);
107
+ },
108
+ getBundleEventInsights(bundleId, window, limit, offset) {
109
+ return getInsightsResult(persistence, bundleId, window, limit, offset);
110
+ },
111
+ async getBundleEventOverview() {
112
+ const rows = await require_scan.materializeEventRows({
113
+ persistence,
114
+ cutoffMs: Date.now()
115
+ });
116
+ const latestByInstall = /* @__PURE__ */ new Map();
117
+ for (const row of rows) {
118
+ const current = latestByInstall.get(row.install_id);
119
+ if (current === void 0 || require_scan.compareEventNewest(row, current) < 0) latestByInstall.set(row.install_id, row);
120
+ }
121
+ const counts = /* @__PURE__ */ new Map();
122
+ for (const row of latestByInstall.values()) counts.set(row.to_bundle_id, (counts.get(row.to_bundle_id) ?? 0) + 1);
123
+ return {
124
+ trackedInstallations: latestByInstall.size,
125
+ bundles: [...counts].map(([bundleId, installations]) => ({
126
+ bundleId,
127
+ installations
128
+ })).sort((left, right) => right.installations - left.installations || require_scan.compareCodePoints(left.bundleId, right.bundleId))
129
+ };
130
+ },
131
+ async getActiveInstallationOverview(input) {
132
+ const asOfMs = Date.now();
133
+ return require_activeOverview.collectActiveInstallationOverview({
134
+ rows: await require_scan.materializeActiveRows({
135
+ persistence,
136
+ cutoffMs: asOfMs
137
+ }, input.window),
138
+ asOfMs,
139
+ window: input.window,
140
+ ...input.userId === void 0 ? {} : { userId: input.userId }
141
+ });
142
+ },
143
+ async searchInstallations(query, limit, offset) {
144
+ return require_installationSearch.searchEventInstallations({
145
+ rows: await require_scan.materializeEventRows({
146
+ persistence,
147
+ cutoffMs: Date.now()
148
+ }),
149
+ query,
150
+ limit,
151
+ offset
152
+ });
153
+ },
154
+ async getInstallationHistory(installId, limit, offset) {
155
+ const ordered = (await require_scan.materializeEventRows({
156
+ persistence,
157
+ cutoffMs: Date.now()
158
+ })).filter((row) => row.install_id === installId && isTransitionEventRow(row)).toSorted(require_scan.compareEventNewest);
159
+ return {
160
+ data: ordered.slice(offset, offset + limit).map(toHistoryRow),
161
+ pagination: {
162
+ total: ordered.length,
163
+ limit,
164
+ offset
165
+ }
166
+ };
167
+ }
168
+ });
169
+ //#endregion
170
+ exports.createInsightsProvider = createInsightsProvider;
@@ -0,0 +1,7 @@
1
+ import { InsightsProvider } from "../types.cjs";
2
+ import { InsightsPersistence } from "../persistence.cjs";
3
+
4
+ //#region src/insights/bounded/provider.d.ts
5
+ declare const createInsightsProvider: (persistence: InsightsPersistence) => InsightsProvider;
6
+ //#endregion
7
+ export { createInsightsProvider };
@@ -0,0 +1,7 @@
1
+ import { InsightsProvider } from "../types.mjs";
2
+ import { InsightsPersistence } from "../persistence.mjs";
3
+
4
+ //#region src/insights/bounded/provider.d.ts
5
+ declare const createInsightsProvider: (persistence: InsightsPersistence) => InsightsProvider;
6
+ //#endregion
7
+ export { createInsightsProvider };
@@ -0,0 +1,170 @@
1
+ import { collectActiveInstallationOverview } from "./activeOverview.mjs";
2
+ import { INSIGHTS_SCAN_MAX_ROWS, collectEventActivity, compareCodePoints, compareEventNewest, materializeActiveRows, materializeEventRows, materializeRowsForWindow } from "./scan.mjs";
3
+ import { searchEventInstallations } from "./installationSearch.mjs";
4
+ import { createBundleEventRow } from "./persistence.mjs";
5
+ //#region src/insights/bounded/provider.ts
6
+ const isTransitionEventRow = (row) => row.type === "UPDATE_APPLIED" || row.type === "RECOVERED";
7
+ const toHistoryRow = (row) => ({
8
+ id: row.id,
9
+ type: row.type,
10
+ fromBundleId: row.from_bundle_id,
11
+ toBundleId: row.to_bundle_id,
12
+ username: row.username,
13
+ userId: row.user_id,
14
+ platform: row.platform,
15
+ appVersion: row.app_version,
16
+ channel: row.channel,
17
+ cohort: row.cohort,
18
+ receivedAtMs: row.received_at_ms
19
+ });
20
+ const isInstalledForBundle = (row, bundleId) => row.type === "UPDATE_APPLIED" && row.to_bundle_id === bundleId;
21
+ const isRecoveredFromBundle = (row, bundleId) => row.type === "RECOVERED" && row.from_bundle_id === bundleId;
22
+ const countDistinctInstallations = (rows) => new Set(rows.map(({ install_id }) => install_id)).size;
23
+ const getInsightsResult = async (persistence, bundleId, window, limit, offset) => {
24
+ const scope = {
25
+ persistence,
26
+ cutoffMs: Date.now()
27
+ };
28
+ const rows = (await materializeRowsForWindow(scope, window)).filter(isTransitionEventRow);
29
+ const installedRows = rows.filter((row) => isInstalledForBundle(row, bundleId));
30
+ const recoveredRows = rows.filter((row) => isRecoveredFromBundle(row, bundleId));
31
+ const installed = collectEventActivity({
32
+ rows: installedRows,
33
+ window,
34
+ cutoffMs: scope.cutoffMs
35
+ });
36
+ const recovered = collectEventActivity({
37
+ rows: recoveredRows,
38
+ window,
39
+ cutoffMs: scope.cutoffMs
40
+ });
41
+ const recentRows = [...installedRows, ...recoveredRows].sort(compareEventNewest);
42
+ return {
43
+ summary: {
44
+ installed: installed.summary,
45
+ recovered: recovered.summary
46
+ },
47
+ series: {
48
+ installed: installed.series,
49
+ recovered: recovered.series
50
+ },
51
+ cohorts: {
52
+ installed: installed.cohorts,
53
+ recovered: recovered.cohorts
54
+ },
55
+ recentEvents: {
56
+ data: recentRows.slice(offset, offset + limit).map(toHistoryRow),
57
+ pagination: {
58
+ total: recentRows.length,
59
+ limit,
60
+ offset
61
+ }
62
+ }
63
+ };
64
+ };
65
+ const getInsightsSummaries = async (persistence, bundleIds, window) => {
66
+ const normalizedBundleIds = [...new Set(bundleIds)];
67
+ if (normalizedBundleIds.length === 0) return [];
68
+ const requestedBundleIds = new Set(normalizedBundleIds);
69
+ const installedByBundleId = /* @__PURE__ */ new Map();
70
+ const recoveredByBundleId = /* @__PURE__ */ new Map();
71
+ const rows = (await materializeRowsForWindow({
72
+ persistence,
73
+ cutoffMs: Date.now()
74
+ }, window)).filter(isTransitionEventRow);
75
+ for (const row of rows) {
76
+ const bundleId = row.type === "UPDATE_APPLIED" ? row.to_bundle_id : row.from_bundle_id;
77
+ if (!requestedBundleIds.has(bundleId)) continue;
78
+ const counts = row.type === "UPDATE_APPLIED" ? installedByBundleId : recoveredByBundleId;
79
+ const installIds = counts.get(bundleId) ?? /* @__PURE__ */ new Set();
80
+ installIds.add(row.install_id);
81
+ counts.set(bundleId, installIds);
82
+ }
83
+ return normalizedBundleIds.map((bundleId) => ({
84
+ bundleId,
85
+ installed: installedByBundleId.get(bundleId)?.size ?? 0,
86
+ recovered: recoveredByBundleId.get(bundleId)?.size ?? 0
87
+ }));
88
+ };
89
+ const createInsightsProvider = (persistence) => Object.freeze({
90
+ mode: "bounded",
91
+ maxMatchingRows: INSIGHTS_SCAN_MAX_ROWS,
92
+ async appendBundleEvent(input) {
93
+ await persistence.append(createBundleEventRow(input));
94
+ },
95
+ async getBundleEventSummary(bundleId) {
96
+ const rows = (await materializeEventRows({
97
+ persistence,
98
+ cutoffMs: Date.now()
99
+ })).filter(isTransitionEventRow);
100
+ return {
101
+ installed: countDistinctInstallations(rows.filter((row) => isInstalledForBundle(row, bundleId))),
102
+ recovered: countDistinctInstallations(rows.filter((row) => isRecoveredFromBundle(row, bundleId)))
103
+ };
104
+ },
105
+ getBundleEventSummaries(bundleIds, window) {
106
+ return getInsightsSummaries(persistence, bundleIds, window);
107
+ },
108
+ getBundleEventInsights(bundleId, window, limit, offset) {
109
+ return getInsightsResult(persistence, bundleId, window, limit, offset);
110
+ },
111
+ async getBundleEventOverview() {
112
+ const rows = await materializeEventRows({
113
+ persistence,
114
+ cutoffMs: Date.now()
115
+ });
116
+ const latestByInstall = /* @__PURE__ */ new Map();
117
+ for (const row of rows) {
118
+ const current = latestByInstall.get(row.install_id);
119
+ if (current === void 0 || compareEventNewest(row, current) < 0) latestByInstall.set(row.install_id, row);
120
+ }
121
+ const counts = /* @__PURE__ */ new Map();
122
+ for (const row of latestByInstall.values()) counts.set(row.to_bundle_id, (counts.get(row.to_bundle_id) ?? 0) + 1);
123
+ return {
124
+ trackedInstallations: latestByInstall.size,
125
+ bundles: [...counts].map(([bundleId, installations]) => ({
126
+ bundleId,
127
+ installations
128
+ })).sort((left, right) => right.installations - left.installations || compareCodePoints(left.bundleId, right.bundleId))
129
+ };
130
+ },
131
+ async getActiveInstallationOverview(input) {
132
+ const asOfMs = Date.now();
133
+ return collectActiveInstallationOverview({
134
+ rows: await materializeActiveRows({
135
+ persistence,
136
+ cutoffMs: asOfMs
137
+ }, input.window),
138
+ asOfMs,
139
+ window: input.window,
140
+ ...input.userId === void 0 ? {} : { userId: input.userId }
141
+ });
142
+ },
143
+ async searchInstallations(query, limit, offset) {
144
+ return searchEventInstallations({
145
+ rows: await materializeEventRows({
146
+ persistence,
147
+ cutoffMs: Date.now()
148
+ }),
149
+ query,
150
+ limit,
151
+ offset
152
+ });
153
+ },
154
+ async getInstallationHistory(installId, limit, offset) {
155
+ const ordered = (await materializeEventRows({
156
+ persistence,
157
+ cutoffMs: Date.now()
158
+ })).filter((row) => row.install_id === installId && isTransitionEventRow(row)).toSorted(compareEventNewest);
159
+ return {
160
+ data: ordered.slice(offset, offset + limit).map(toHistoryRow),
161
+ pagination: {
162
+ total: ordered.length,
163
+ limit,
164
+ offset
165
+ }
166
+ };
167
+ }
168
+ });
169
+ //#endregion
170
+ export { createInsightsProvider };
@@ -0,0 +1,144 @@
1
+ const require_errors = require("../errors.cjs");
2
+ const require_activeOverview = require("./activeOverview.cjs");
3
+ //#region src/insights/bounded/scan.ts
4
+ const INSIGHTS_SCAN_MAX_ROWS = 5e4;
5
+ const INSIGHTS_MATERIALIZATION_LIMIT = INSIGHTS_SCAN_MAX_ROWS + 1;
6
+ const INSIGHTS_SCAN_PAGE_SIZE = 1e3;
7
+ const INSIGHTS_LOWER_BOUND_ID = "00000000-0000-0000-0000-000000000000";
8
+ const compareCodePoints = (left, right) => {
9
+ if (left < right) return -1;
10
+ if (left > right) return 1;
11
+ return 0;
12
+ };
13
+ const compareEventNewest = (left, right) => right.received_at_ms - left.received_at_ms || compareCodePoints(right.id, left.id);
14
+ const compareEventOldest = (left, right) => left.received_at_ms - right.received_at_ms || compareCodePoints(left.id, right.id);
15
+ const materializeEventRows = async (scope) => {
16
+ const rows = [];
17
+ const seenIds = /* @__PURE__ */ new Set();
18
+ let after = scope.lowerBoundMs === void 0 ? void 0 : {
19
+ receivedAtMs: scope.lowerBoundMs,
20
+ id: INSIGHTS_LOWER_BOUND_ID
21
+ };
22
+ while (rows.length < INSIGHTS_MATERIALIZATION_LIMIT) {
23
+ const limit = Math.min(INSIGHTS_SCAN_PAGE_SIZE, INSIGHTS_MATERIALIZATION_LIMIT - rows.length);
24
+ const page = await scope.persistence.scan({
25
+ beforeReceivedAtMs: scope.cutoffMs,
26
+ ...after === void 0 ? {} : { after },
27
+ limit
28
+ });
29
+ if (page.length === 0) break;
30
+ if (page.length > limit) throw new InsightsPersistenceOrderError();
31
+ let previous = after === void 0 ? void 0 : {
32
+ received_at_ms: after.receivedAtMs,
33
+ id: after.id
34
+ };
35
+ for (const row of page) {
36
+ if (row.received_at_ms >= scope.cutoffMs || previous !== void 0 && compareEventOldest(previous, row) >= 0 || seenIds.has(row.id)) throw new InsightsPersistenceOrderError();
37
+ seenIds.add(row.id);
38
+ rows.push(row);
39
+ previous = row;
40
+ }
41
+ const last = page.at(-1);
42
+ if (last !== void 0) after = {
43
+ receivedAtMs: last.received_at_ms,
44
+ id: last.id
45
+ };
46
+ if (rows.length > 5e4) throw new require_errors.InsightsScanLimitExceededError(INSIGHTS_SCAN_MAX_ROWS);
47
+ }
48
+ return rows;
49
+ };
50
+ var InsightsPersistenceOrderError = class extends Error {
51
+ name = "InsightsPersistenceOrderError";
52
+ constructor() {
53
+ super("Insights persistence did not advance its scan cursor.");
54
+ }
55
+ };
56
+ const materializeActiveRows = async (scope, window) => {
57
+ const definition = require_activeOverview.getActiveWindowDefinition(window);
58
+ const durationMs = definition.bucketCount * definition.bucketSizeMs;
59
+ return (await materializeEventRows({
60
+ ...scope,
61
+ lowerBoundMs: scope.cutoffMs - durationMs
62
+ })).filter((row) => row.received_at_ms >= scope.cutoffMs - durationMs && require_activeOverview.ACTIVE_BUNDLE_EVENT_TYPES.includes(row.type));
63
+ };
64
+ const startOfUtcHour = (value) => {
65
+ const date = new Date(value);
66
+ return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours());
67
+ };
68
+ const startOfUtcDay = (value) => {
69
+ const date = new Date(value);
70
+ return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
71
+ };
72
+ const getWindowRange = (window, now) => {
73
+ if (window === "24h") return {
74
+ sizeMs: 3600 * 1e3,
75
+ rangeStart: startOfUtcHour(now) - 1380 * 60 * 1e3
76
+ };
77
+ const days = window === "7d" ? 7 : 30;
78
+ return {
79
+ sizeMs: 1440 * 60 * 1e3,
80
+ rangeStart: startOfUtcDay(now) - (days - 1) * 24 * 60 * 60 * 1e3
81
+ };
82
+ };
83
+ const materializeRowsForWindow = async (scope, window) => {
84
+ if (window === "all") return materializeEventRows(scope);
85
+ const range = getWindowRange(window, scope.cutoffMs);
86
+ return (await materializeEventRows({
87
+ ...scope,
88
+ lowerBoundMs: range.rangeStart
89
+ })).filter(({ received_at_ms }) => received_at_ms >= range.rangeStart);
90
+ };
91
+ const bucketStart = (receivedAtMs, sizeMs) => sizeMs === 3600 * 1e3 ? startOfUtcHour(receivedAtMs) : startOfUtcDay(receivedAtMs);
92
+ const createSeries = (request) => {
93
+ const range = request.window === "all" ? void 0 : getWindowRange(request.window, request.cutoffMs);
94
+ const sizeMs = range?.sizeMs ?? 1440 * 60 * 1e3;
95
+ const installIdsByBucket = /* @__PURE__ */ new Map();
96
+ let oldestMs = request.cutoffMs;
97
+ for (const row of request.rows) {
98
+ oldestMs = Math.min(oldestMs, row.received_at_ms);
99
+ const start = bucketStart(row.received_at_ms, sizeMs);
100
+ const installIds = installIdsByBucket.get(start) ?? /* @__PURE__ */ new Set();
101
+ installIds.add(row.install_id);
102
+ installIdsByBucket.set(start, installIds);
103
+ }
104
+ const first = range?.rangeStart ?? startOfUtcDay(oldestMs);
105
+ const last = bucketStart(request.cutoffMs, sizeMs);
106
+ return Array.from({ length: Math.floor((last - first) / sizeMs) + 1 }, (_, index) => {
107
+ const start = first + index * sizeMs;
108
+ return {
109
+ bucketStartMs: start,
110
+ value: installIdsByBucket.get(start)?.size ?? 0
111
+ };
112
+ });
113
+ };
114
+ const collectEventActivity = (request) => {
115
+ const range = request.window === "all" ? void 0 : getWindowRange(request.window, request.cutoffMs);
116
+ const rows = range === void 0 ? request.rows : request.rows.filter(({ received_at_ms }) => received_at_ms >= range.rangeStart && received_at_ms < request.cutoffMs);
117
+ const installs = /* @__PURE__ */ new Set();
118
+ const installsByCohort = /* @__PURE__ */ new Map();
119
+ for (const row of rows) {
120
+ installs.add(row.install_id);
121
+ const cohort = installsByCohort.get(row.cohort) ?? /* @__PURE__ */ new Set();
122
+ cohort.add(row.install_id);
123
+ installsByCohort.set(row.cohort, cohort);
124
+ }
125
+ return {
126
+ summary: installs.size,
127
+ cohorts: [...installsByCohort].sort(([left], [right]) => compareCodePoints(left, right)).map(([cohort, installIds]) => ({
128
+ cohort,
129
+ value: installIds.size
130
+ })),
131
+ series: createSeries({
132
+ ...request,
133
+ rows
134
+ })
135
+ };
136
+ };
137
+ //#endregion
138
+ exports.INSIGHTS_SCAN_MAX_ROWS = INSIGHTS_SCAN_MAX_ROWS;
139
+ exports.collectEventActivity = collectEventActivity;
140
+ exports.compareCodePoints = compareCodePoints;
141
+ exports.compareEventNewest = compareEventNewest;
142
+ exports.materializeActiveRows = materializeActiveRows;
143
+ exports.materializeEventRows = materializeEventRows;
144
+ exports.materializeRowsForWindow = materializeRowsForWindow;
@@ -0,0 +1,138 @@
1
+ import { InsightsScanLimitExceededError } from "../errors.mjs";
2
+ import { ACTIVE_BUNDLE_EVENT_TYPES, getActiveWindowDefinition } from "./activeOverview.mjs";
3
+ //#region src/insights/bounded/scan.ts
4
+ const INSIGHTS_SCAN_MAX_ROWS = 5e4;
5
+ const INSIGHTS_MATERIALIZATION_LIMIT = INSIGHTS_SCAN_MAX_ROWS + 1;
6
+ const INSIGHTS_SCAN_PAGE_SIZE = 1e3;
7
+ const INSIGHTS_LOWER_BOUND_ID = "00000000-0000-0000-0000-000000000000";
8
+ const compareCodePoints = (left, right) => {
9
+ if (left < right) return -1;
10
+ if (left > right) return 1;
11
+ return 0;
12
+ };
13
+ const compareEventNewest = (left, right) => right.received_at_ms - left.received_at_ms || compareCodePoints(right.id, left.id);
14
+ const compareEventOldest = (left, right) => left.received_at_ms - right.received_at_ms || compareCodePoints(left.id, right.id);
15
+ const materializeEventRows = async (scope) => {
16
+ const rows = [];
17
+ const seenIds = /* @__PURE__ */ new Set();
18
+ let after = scope.lowerBoundMs === void 0 ? void 0 : {
19
+ receivedAtMs: scope.lowerBoundMs,
20
+ id: INSIGHTS_LOWER_BOUND_ID
21
+ };
22
+ while (rows.length < INSIGHTS_MATERIALIZATION_LIMIT) {
23
+ const limit = Math.min(INSIGHTS_SCAN_PAGE_SIZE, INSIGHTS_MATERIALIZATION_LIMIT - rows.length);
24
+ const page = await scope.persistence.scan({
25
+ beforeReceivedAtMs: scope.cutoffMs,
26
+ ...after === void 0 ? {} : { after },
27
+ limit
28
+ });
29
+ if (page.length === 0) break;
30
+ if (page.length > limit) throw new InsightsPersistenceOrderError();
31
+ let previous = after === void 0 ? void 0 : {
32
+ received_at_ms: after.receivedAtMs,
33
+ id: after.id
34
+ };
35
+ for (const row of page) {
36
+ if (row.received_at_ms >= scope.cutoffMs || previous !== void 0 && compareEventOldest(previous, row) >= 0 || seenIds.has(row.id)) throw new InsightsPersistenceOrderError();
37
+ seenIds.add(row.id);
38
+ rows.push(row);
39
+ previous = row;
40
+ }
41
+ const last = page.at(-1);
42
+ if (last !== void 0) after = {
43
+ receivedAtMs: last.received_at_ms,
44
+ id: last.id
45
+ };
46
+ if (rows.length > 5e4) throw new InsightsScanLimitExceededError(INSIGHTS_SCAN_MAX_ROWS);
47
+ }
48
+ return rows;
49
+ };
50
+ var InsightsPersistenceOrderError = class extends Error {
51
+ name = "InsightsPersistenceOrderError";
52
+ constructor() {
53
+ super("Insights persistence did not advance its scan cursor.");
54
+ }
55
+ };
56
+ const materializeActiveRows = async (scope, window) => {
57
+ const definition = getActiveWindowDefinition(window);
58
+ const durationMs = definition.bucketCount * definition.bucketSizeMs;
59
+ return (await materializeEventRows({
60
+ ...scope,
61
+ lowerBoundMs: scope.cutoffMs - durationMs
62
+ })).filter((row) => row.received_at_ms >= scope.cutoffMs - durationMs && ACTIVE_BUNDLE_EVENT_TYPES.includes(row.type));
63
+ };
64
+ const startOfUtcHour = (value) => {
65
+ const date = new Date(value);
66
+ return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours());
67
+ };
68
+ const startOfUtcDay = (value) => {
69
+ const date = new Date(value);
70
+ return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
71
+ };
72
+ const getWindowRange = (window, now) => {
73
+ if (window === "24h") return {
74
+ sizeMs: 3600 * 1e3,
75
+ rangeStart: startOfUtcHour(now) - 1380 * 60 * 1e3
76
+ };
77
+ const days = window === "7d" ? 7 : 30;
78
+ return {
79
+ sizeMs: 1440 * 60 * 1e3,
80
+ rangeStart: startOfUtcDay(now) - (days - 1) * 24 * 60 * 60 * 1e3
81
+ };
82
+ };
83
+ const materializeRowsForWindow = async (scope, window) => {
84
+ if (window === "all") return materializeEventRows(scope);
85
+ const range = getWindowRange(window, scope.cutoffMs);
86
+ return (await materializeEventRows({
87
+ ...scope,
88
+ lowerBoundMs: range.rangeStart
89
+ })).filter(({ received_at_ms }) => received_at_ms >= range.rangeStart);
90
+ };
91
+ const bucketStart = (receivedAtMs, sizeMs) => sizeMs === 3600 * 1e3 ? startOfUtcHour(receivedAtMs) : startOfUtcDay(receivedAtMs);
92
+ const createSeries = (request) => {
93
+ const range = request.window === "all" ? void 0 : getWindowRange(request.window, request.cutoffMs);
94
+ const sizeMs = range?.sizeMs ?? 1440 * 60 * 1e3;
95
+ const installIdsByBucket = /* @__PURE__ */ new Map();
96
+ let oldestMs = request.cutoffMs;
97
+ for (const row of request.rows) {
98
+ oldestMs = Math.min(oldestMs, row.received_at_ms);
99
+ const start = bucketStart(row.received_at_ms, sizeMs);
100
+ const installIds = installIdsByBucket.get(start) ?? /* @__PURE__ */ new Set();
101
+ installIds.add(row.install_id);
102
+ installIdsByBucket.set(start, installIds);
103
+ }
104
+ const first = range?.rangeStart ?? startOfUtcDay(oldestMs);
105
+ const last = bucketStart(request.cutoffMs, sizeMs);
106
+ return Array.from({ length: Math.floor((last - first) / sizeMs) + 1 }, (_, index) => {
107
+ const start = first + index * sizeMs;
108
+ return {
109
+ bucketStartMs: start,
110
+ value: installIdsByBucket.get(start)?.size ?? 0
111
+ };
112
+ });
113
+ };
114
+ const collectEventActivity = (request) => {
115
+ const range = request.window === "all" ? void 0 : getWindowRange(request.window, request.cutoffMs);
116
+ const rows = range === void 0 ? request.rows : request.rows.filter(({ received_at_ms }) => received_at_ms >= range.rangeStart && received_at_ms < request.cutoffMs);
117
+ const installs = /* @__PURE__ */ new Set();
118
+ const installsByCohort = /* @__PURE__ */ new Map();
119
+ for (const row of rows) {
120
+ installs.add(row.install_id);
121
+ const cohort = installsByCohort.get(row.cohort) ?? /* @__PURE__ */ new Set();
122
+ cohort.add(row.install_id);
123
+ installsByCohort.set(row.cohort, cohort);
124
+ }
125
+ return {
126
+ summary: installs.size,
127
+ cohorts: [...installsByCohort].sort(([left], [right]) => compareCodePoints(left, right)).map(([cohort, installIds]) => ({
128
+ cohort,
129
+ value: installIds.size
130
+ })),
131
+ series: createSeries({
132
+ ...request,
133
+ rows
134
+ })
135
+ };
136
+ };
137
+ //#endregion
138
+ export { INSIGHTS_SCAN_MAX_ROWS, collectEventActivity, compareCodePoints, compareEventNewest, materializeActiveRows, materializeEventRows, materializeRowsForWindow };