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

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 (191) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +1 -1
  2. package/dist/adapters/databaseConstraintErrors.cjs +0 -1
  3. package/dist/adapters/databasePluginUtils.cjs +0 -1
  4. package/dist/adapters/drizzle.cjs +10 -3
  5. package/dist/adapters/drizzle.d.cts +0 -1
  6. package/dist/adapters/drizzle.d.mts +0 -1
  7. package/dist/adapters/drizzle.mjs +11 -3
  8. package/dist/adapters/drizzleCrud.cjs +62 -1
  9. package/dist/adapters/drizzleCrud.mjs +61 -2
  10. package/dist/adapters/drizzleLazyDB.cjs +6 -1
  11. package/dist/adapters/drizzleLazyDB.mjs +6 -1
  12. package/dist/adapters/drizzleQuery.cjs +3 -2
  13. package/dist/adapters/drizzleQuery.mjs +3 -1
  14. package/dist/adapters/insightsTransaction.cjs +22 -0
  15. package/dist/adapters/insightsTransaction.mjs +22 -0
  16. package/dist/adapters/kysely.cjs +2 -1
  17. package/dist/adapters/kysely.d.cts +0 -1
  18. package/dist/adapters/kysely.d.mts +0 -1
  19. package/dist/adapters/kysely.mjs +3 -1
  20. package/dist/adapters/kyselyCrud.cjs +54 -3
  21. package/dist/adapters/kyselyCrud.mjs +54 -3
  22. package/dist/adapters/kyselyQuery.cjs +2 -2
  23. package/dist/adapters/kyselyQuery.mjs +2 -1
  24. package/dist/adapters/mongodb.cjs +27 -1
  25. package/dist/adapters/mongodb.d.cts +1 -1
  26. package/dist/adapters/mongodb.d.mts +1 -1
  27. package/dist/adapters/mongodb.mjs +28 -1
  28. package/dist/adapters/mongodbCollections.cjs +2 -0
  29. package/dist/adapters/mongodbCollections.mjs +2 -0
  30. package/dist/adapters/mongodbQuery.cjs +21 -4
  31. package/dist/adapters/mongodbQuery.mjs +21 -5
  32. package/dist/adapters/mongodbReads.cjs +91 -29
  33. package/dist/adapters/mongodbReads.mjs +92 -29
  34. package/dist/adapters/mongodbWrites.cjs +16 -2
  35. package/dist/adapters/mongodbWrites.mjs +18 -3
  36. package/dist/adapters/prisma.cjs +55 -7
  37. package/dist/adapters/prisma.d.cts +0 -1
  38. package/dist/adapters/prisma.d.mts +0 -1
  39. package/dist/adapters/prisma.mjs +56 -7
  40. package/dist/adapters/prismaQuery.cjs +0 -1
  41. package/dist/adapters/prismaRows.cjs +23 -2
  42. package/dist/adapters/prismaRows.mjs +23 -2
  43. package/dist/apiKeys.cjs +6 -2
  44. package/dist/apiKeys.d.cts +0 -1
  45. package/dist/apiKeys.d.mts +0 -1
  46. package/dist/apiKeys.mjs +3 -3
  47. package/dist/createHotUpdaterCore.cjs +24 -9
  48. package/dist/createHotUpdaterCore.d.cts +1 -1
  49. package/dist/createHotUpdaterCore.d.mts +1 -1
  50. package/dist/createHotUpdaterCore.mjs +23 -9
  51. package/dist/db/createBundleDiff.cjs +2 -2
  52. package/dist/db/createBundleDiff.d.cts +1 -5
  53. package/dist/db/createBundleDiff.d.mts +1 -5
  54. package/dist/db/databasePluginCore.cjs +0 -1
  55. package/dist/db/fixedMigrator.cjs +2 -2
  56. package/dist/db/fixedMigrator.mjs +2 -2
  57. package/dist/db/fixedMigratorMongo.cjs +6 -4
  58. package/dist/db/fixedMigratorMongo.mjs +6 -4
  59. package/dist/db/index.d.cts +0 -1
  60. package/dist/db/index.d.mts +0 -1
  61. package/dist/db/releaseCatalog.cjs +0 -1
  62. package/dist/db/releaseCatalog.d.cts +0 -1
  63. package/dist/db/releaseCatalog.d.mts +0 -1
  64. package/dist/db/schema/insightsCollation.cjs +14 -0
  65. package/dist/db/schema/insightsCollation.mjs +14 -0
  66. package/dist/db/schema/sql.cjs +11 -7
  67. package/dist/db/schema/sql.mjs +8 -8
  68. package/dist/db/schemaGenerators.cjs +28 -9
  69. package/dist/db/schemaGenerators.mjs +28 -9
  70. package/dist/db/schemaReadiness.cjs +2 -0
  71. package/dist/db/schemaReadiness.d.cts +1 -0
  72. package/dist/db/schemaReadiness.d.mts +1 -0
  73. package/dist/db/schemaReadiness.mjs +2 -0
  74. package/dist/db/sqlMigrationExecution.cjs +1 -1
  75. package/dist/db/sqlMigrationExecution.mjs +1 -0
  76. package/dist/db/types.cjs +3 -1
  77. package/dist/db/types.d.cts +3 -6
  78. package/dist/db/types.d.mts +3 -6
  79. package/dist/db/types.mjs +2 -2
  80. package/dist/db/updateArtifacts.cjs +0 -1
  81. package/dist/handler.d.cts +1 -2
  82. package/dist/handler.d.mts +1 -2
  83. package/dist/handlerReleaseCatalogRoutes.cjs +0 -1
  84. package/dist/handlerReleaseManagementRoutes.cjs +0 -1
  85. package/dist/handlerTypes.d.cts +0 -1
  86. package/dist/handlerTypes.d.mts +0 -1
  87. package/dist/handlerVersionRoutes.d.cts +1 -0
  88. package/dist/handlerVersionRoutes.d.mts +1 -0
  89. package/dist/index.cjs +1 -1
  90. package/dist/index.d.cts +4 -4
  91. package/dist/index.d.mts +4 -4
  92. package/dist/index.mjs +1 -1
  93. package/dist/insights/domain.d.cts +37 -69
  94. package/dist/insights/domain.d.mts +37 -69
  95. package/dist/insights/errors.cjs +1 -8
  96. package/dist/insights/errors.mjs +2 -8
  97. package/dist/insights/eventInput.cjs +49 -7
  98. package/dist/insights/eventInput.mjs +48 -8
  99. package/dist/insights/provider.cjs +314 -0
  100. package/dist/insights/provider.d.cts +6 -0
  101. package/dist/insights/provider.d.mts +6 -0
  102. package/dist/insights/provider.mjs +314 -0
  103. package/dist/insights/queryInput.cjs +78 -43
  104. package/dist/insights/queryInput.mjs +76 -40
  105. package/dist/insights/routes.cjs +20 -25
  106. package/dist/insights/routes.mjs +22 -27
  107. package/dist/insights/types.d.cts +27 -12
  108. package/dist/insights/types.d.mts +27 -12
  109. package/dist/node.d.cts +0 -1
  110. package/dist/node.d.mts +0 -1
  111. package/dist/package.cjs +1 -1
  112. package/dist/package.mjs +1 -1
  113. package/dist/schema/index.cjs +17 -3
  114. package/dist/schema/index.mjs +4 -4
  115. package/dist/schema/v1_0_0.cjs +74 -32
  116. package/dist/schema/v1_0_0.mjs +74 -33
  117. package/dist/storageAccess.cjs +0 -1
  118. package/dist/types/index.d.cts +0 -1
  119. package/dist/types/index.d.mts +0 -1
  120. package/package.json +7 -7
  121. package/src/adapters/databasePluginTestDatabase.ts +14 -0
  122. package/src/adapters/drizzle.spec.ts +99 -0
  123. package/src/adapters/drizzle.ts +21 -3
  124. package/src/adapters/drizzleCrud.ts +154 -2
  125. package/src/adapters/drizzleLazyDB.ts +17 -3
  126. package/src/adapters/insightsTransaction.ts +32 -0
  127. package/src/adapters/kysely.spec.ts +29 -0
  128. package/src/adapters/kysely.ts +10 -1
  129. package/src/adapters/kyselyCrud.ts +121 -0
  130. package/src/adapters/mongodb.integration.spec.ts +210 -0
  131. package/src/adapters/mongodb.spec.ts +69 -1
  132. package/src/adapters/mongodb.ts +66 -2
  133. package/src/adapters/mongodbCollections.ts +5 -0
  134. package/src/adapters/mongodbQuery.ts +32 -3
  135. package/src/adapters/mongodbReads.ts +69 -0
  136. package/src/adapters/mongodbTestClient.ts +41 -6
  137. package/src/adapters/mongodbTestFilter.ts +4 -1
  138. package/src/adapters/mongodbWrites.ts +27 -0
  139. package/src/adapters/prisma.spec.ts +97 -0
  140. package/src/adapters/prisma.ts +103 -3
  141. package/src/adapters/prismaRows.ts +35 -4
  142. package/src/adapters/prismaTestClient.ts +22 -5
  143. package/src/apiKeys.integration.spec.ts +1 -1
  144. package/src/createHotUpdaterCore.ts +21 -7
  145. package/src/db/databasePluginUpdateCheck.bench.ts +3 -0
  146. package/src/db/fixedMigrator.spec.ts +65 -0
  147. package/src/db/fixedMigratorMongo.spec.ts +56 -9
  148. package/src/db/fixedMigratorMongo.ts +10 -10
  149. package/src/db/index.spec.ts +8 -1
  150. package/src/db/schema/insightsCollation.ts +21 -0
  151. package/src/db/schema/sql.ts +8 -5
  152. package/src/db/schemaGenerators.ts +41 -13
  153. package/src/db/types.spec.ts +8 -0
  154. package/src/db/types.ts +10 -4
  155. package/src/handler-standalone.integration.spec.ts +5 -7
  156. package/src/index.ts +2 -2
  157. package/src/insights/domain.ts +37 -77
  158. package/src/insights/errors.ts +0 -7
  159. package/src/insights/eventInput.ts +60 -5
  160. package/src/insights/insights.integration.spec.ts +292 -17
  161. package/src/insights/provider.spec.ts +385 -0
  162. package/src/insights/provider.ts +591 -0
  163. package/src/insights/queryInput.ts +112 -73
  164. package/src/insights/routes.ts +37 -56
  165. package/src/insights/types.ts +47 -36
  166. package/src/schema/dsl-all-versions.spec.ts +3 -1
  167. package/src/schema/v1_0_0.spec.ts +77 -3
  168. package/src/schema/v1_0_0.ts +95 -33
  169. package/dist/insights/bounded/activeOverview.cjs +0 -87
  170. package/dist/insights/bounded/activeOverview.mjs +0 -85
  171. package/dist/insights/bounded/installationSearch.cjs +0 -42
  172. package/dist/insights/bounded/installationSearch.mjs +0 -42
  173. package/dist/insights/bounded/persistence.cjs +0 -39
  174. package/dist/insights/bounded/persistence.mjs +0 -38
  175. package/dist/insights/bounded/provider.cjs +0 -170
  176. package/dist/insights/bounded/provider.d.cts +0 -7
  177. package/dist/insights/bounded/provider.d.mts +0 -7
  178. package/dist/insights/bounded/provider.mjs +0 -170
  179. package/dist/insights/bounded/scan.cjs +0 -144
  180. package/dist/insights/bounded/scan.mjs +0 -138
  181. package/dist/insights/persistence.d.cts +0 -6
  182. package/dist/insights/persistence.d.mts +0 -6
  183. package/src/insights/bounded/activeOverview.spec.ts +0 -189
  184. package/src/insights/bounded/activeOverview.ts +0 -148
  185. package/src/insights/bounded/installationSearch.spec.ts +0 -130
  186. package/src/insights/bounded/installationSearch.ts +0 -69
  187. package/src/insights/bounded/persistence.ts +0 -43
  188. package/src/insights/bounded/provider.spec.ts +0 -219
  189. package/src/insights/bounded/provider.ts +0 -229
  190. package/src/insights/bounded/scan.ts +0 -235
  191. package/src/insights/persistence.ts +0 -10
@@ -366,21 +366,37 @@ export const releaseCatalogsV100 = table(
366
366
  },
367
367
  );
368
368
 
369
+ const insightsTextCollations = {
370
+ postgresql: '"C"',
371
+ mysql: "utf8mb4_0900_bin",
372
+ } as const;
373
+ const insightsProviders = [
374
+ "postgresql",
375
+ "mysql",
376
+ "sqlite",
377
+ "cockroachdb",
378
+ "mongodb",
379
+ ] as const;
380
+
369
381
  export const bundleEventsV100 = table(
370
382
  "bundle_events",
371
383
  {
372
384
  id: idColumn("id", "uuid"),
373
- type: stringColumn("type"),
374
- install_id: stringColumn("install_id"),
375
- user_id: stringColumn("user_id").nullable(),
385
+ type: column("type", varchar(32)),
386
+ install_id: column("install_id", varchar(255)).collate(
387
+ insightsTextCollations,
388
+ ),
389
+ user_id: column("user_id", varchar(255))
390
+ .collate(insightsTextCollations)
391
+ .nullable(),
376
392
  username: stringColumn("username").nullable(),
377
393
  from_release_id: uuid("from_release_id").nullable(),
378
394
  from_bundle_id: uuid("from_bundle_id").nullable(),
379
395
  to_release_id: uuid("to_release_id").nullable(),
380
396
  to_bundle_id: uuid("to_bundle_id"),
381
- platform: stringColumn("platform"),
397
+ platform: stringColumn("platform").collate(insightsTextCollations),
382
398
  app_version: stringColumn("app_version"),
383
- channel: stringColumn("channel"),
399
+ channel: stringColumn("channel").collate(insightsTextCollations),
384
400
  cohort: stringColumn("cohort"),
385
401
  update_strategy: stringColumn("update_strategy").nullable(),
386
402
  fingerprint_hash: stringColumn("fingerprint_hash").nullable(),
@@ -392,41 +408,32 @@ export const bundleEventsV100 = table(
392
408
  index("bundle_events_received_at_idx", ["received_at_ms", "id"]),
393
409
  index("bundle_events_install_idx", [
394
410
  "install_id",
395
- "received_at_ms",
396
- "id",
397
- ]),
398
- index("bundle_events_user_id_idx", ["user_id", "received_at_ms", "id"]),
399
- index("bundle_events_username_idx", ["username", "received_at_ms", "id"]),
400
- index("bundle_events_to_bundle_idx", [
401
- "type",
402
- "to_bundle_id",
403
- "received_at_ms",
404
- "id",
405
- ]),
406
- index("bundle_events_from_bundle_idx", [
407
- "type",
408
- "from_bundle_id",
409
- "received_at_ms",
410
- "id",
411
- ]),
412
- index("bundle_events_to_release_idx", [
413
411
  "type",
414
- "to_release_id",
415
- "received_at_ms",
416
- "id",
417
- ]),
418
- index("bundle_events_from_release_idx", [
419
- "type",
420
- "from_release_id",
421
412
  "received_at_ms",
422
413
  "id",
423
414
  ]),
415
+ index(
416
+ "bundle_events_from_bundle_idx",
417
+ [
418
+ "type",
419
+ "platform",
420
+ "channel",
421
+ "from_bundle_id",
422
+ "received_at_ms",
423
+ "id",
424
+ ],
425
+ insightsProviders,
426
+ ),
427
+ index(
428
+ "bundle_events_to_bundle_idx",
429
+ ["type", "platform", "channel", "to_bundle_id", "received_at_ms", "id"],
430
+ insightsProviders,
431
+ ),
424
432
  ],
425
433
  checks: [
426
434
  check({
427
435
  name: "bundle_events_type_check",
428
- expression:
429
- "type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED', 'UNCHANGED')",
436
+ expression: "type in ('UPDATE_APPLIED', 'RECOVERED', 'UNCHANGED')",
430
437
  sqliteInline: true,
431
438
  }),
432
439
  check({
@@ -437,7 +444,7 @@ export const bundleEventsV100 = table(
437
444
  check({
438
445
  name: "bundle_events_shape_check",
439
446
  expression:
440
- "((type in ('UPDATE_APPLIED', 'RECOVERED', 'RELEASE_ADOPTED')) and from_bundle_id is not null and update_strategy is not null and update_strategy in ('fingerprint', 'appVersion')) or (type = 'UNCHANGED' and from_bundle_id is null and update_strategy is null)",
447
+ "((type in ('UPDATE_APPLIED', 'RECOVERED')) and from_bundle_id is not null and update_strategy is not null and update_strategy in ('fingerprint', 'appVersion')) or (type = 'UNCHANGED' and from_bundle_id is null and update_strategy is null)",
441
448
  sqliteInline: true,
442
449
  }),
443
450
  check({
@@ -449,6 +456,60 @@ export const bundleEventsV100 = table(
449
456
  },
450
457
  );
451
458
 
459
+ export const bundleInstallationsV100 = table(
460
+ "bundle_installations",
461
+ {
462
+ install_id: idColumn("install_id", varchar(255)).collate(
463
+ insightsTextCollations,
464
+ ),
465
+ id: uuid("id"),
466
+ user_id: column("user_id", varchar(255))
467
+ .collate(insightsTextCollations)
468
+ .nullable(),
469
+ username: stringColumn("username").nullable(),
470
+ to_bundle_id: uuid("to_bundle_id"),
471
+ type: column("type", varchar(32)),
472
+ platform: stringColumn("platform").collate(insightsTextCollations),
473
+ app_version: stringColumn("app_version"),
474
+ channel: stringColumn("channel").collate(insightsTextCollations),
475
+ cohort: stringColumn("cohort"),
476
+ received_at_ms: float("received_at_ms"),
477
+ },
478
+ {
479
+ indexes: [
480
+ index("bundle_installations_user_id_idx", ["user_id", "install_id"]),
481
+ index("bundle_installations_received_at_idx", ["received_at_ms"]),
482
+ index(
483
+ "bundle_installations_scope_idx",
484
+ ["platform", "channel", "received_at_ms"],
485
+ insightsProviders,
486
+ ),
487
+ index(
488
+ "bundle_installations_bundle_idx",
489
+ ["platform", "channel", "to_bundle_id", "received_at_ms"],
490
+ insightsProviders,
491
+ ),
492
+ ],
493
+ checks: [
494
+ check({
495
+ name: "bundle_installations_type_check",
496
+ expression: "type in ('UPDATE_APPLIED', 'RECOVERED', 'UNCHANGED')",
497
+ sqliteInline: true,
498
+ }),
499
+ check({
500
+ name: "bundle_installations_platform_check",
501
+ expression: "platform in ('ios', 'android')",
502
+ sqliteInline: true,
503
+ }),
504
+ check({
505
+ name: "bundle_installations_received_at_check",
506
+ expression: "received_at_ms >= 0",
507
+ sqliteInline: true,
508
+ }),
509
+ ],
510
+ },
511
+ );
512
+
452
513
  export const v1_0_0 = schema({
453
514
  version: "1.0.0",
454
515
  settingsTable: HOT_UPDATER_SETTINGS_TABLE,
@@ -459,6 +520,7 @@ export const v1_0_0 = schema({
459
520
  releasesV100,
460
521
  releaseCatalogsV100,
461
522
  bundleEventsV100,
523
+ bundleInstallationsV100,
462
524
  apiKeysV100,
463
525
  createSettingsTable("1.0.0"),
464
526
  ],
@@ -1,87 +0,0 @@
1
- //#region src/insights/bounded/activeOverview.ts
2
- const HOUR_MS = 3600 * 1e3;
3
- const DAY_MS = 24 * HOUR_MS;
4
- const ACTIVE_BUNDLE_EVENT_TYPES = [
5
- "UPDATE_APPLIED",
6
- "RECOVERED",
7
- "RELEASE_ADOPTED",
8
- "UNCHANGED"
9
- ];
10
- const activeWindowDefinitions = {
11
- "24h": {
12
- bucketCount: 24,
13
- bucketSizeMs: HOUR_MS
14
- },
15
- "7d": {
16
- bucketCount: 7,
17
- bucketSizeMs: DAY_MS
18
- },
19
- "30d": {
20
- bucketCount: 30,
21
- bucketSizeMs: DAY_MS
22
- }
23
- };
24
- function getActiveWindowDefinition(window) {
25
- return activeWindowDefinitions[window];
26
- }
27
- function compareCodePoints(left, right) {
28
- if (left < right) return -1;
29
- if (left > right) return 1;
30
- return 0;
31
- }
32
- function isNewer(candidate, current) {
33
- return candidate.received_at_ms > current.received_at_ms || candidate.received_at_ms === current.received_at_ms && compareCodePoints(candidate.id, current.id) > 0;
34
- }
35
- function collectActiveInstallationOverview(request) {
36
- const definition = getActiveWindowDefinition(request.window);
37
- const windowStartMs = request.asOfMs - definition.bucketCount * definition.bucketSizeMs;
38
- const rows = request.rows.filter((row) => row.received_at_ms >= windowStartMs && row.received_at_ms < request.asOfMs);
39
- const latestByInstall = /* @__PURE__ */ new Map();
40
- for (const row of rows) {
41
- const current = latestByInstall.get(row.install_id);
42
- if (current === void 0 || isNewer(row, current)) latestByInstall.set(row.install_id, row);
43
- }
44
- const selectedRows = [...latestByInstall.values()].filter((row) => request.userId === void 0 || row.user_id === request.userId);
45
- const selectedInstallIds = new Set(selectedRows.map((row) => row.install_id));
46
- const bundleCounts = /* @__PURE__ */ new Map();
47
- for (const row of selectedRows) bundleCounts.set(row.to_bundle_id, (bundleCounts.get(row.to_bundle_id) ?? 0) + 1);
48
- const latestByBucket = Array.from({ length: definition.bucketCount }, () => /* @__PURE__ */ new Map());
49
- for (const row of rows) {
50
- if (!selectedInstallIds.has(row.install_id)) continue;
51
- const bucket = latestByBucket[Math.floor((row.received_at_ms - windowStartMs) / definition.bucketSizeMs)];
52
- if (bucket === void 0) continue;
53
- const current = bucket.get(row.install_id);
54
- if (current === void 0 || isNewer(row, current)) bucket.set(row.install_id, row);
55
- }
56
- const bundleCountsByBucket = latestByBucket.map((bucket) => {
57
- const counts = /* @__PURE__ */ new Map();
58
- for (const row of bucket.values()) counts.set(row.to_bundle_id, (counts.get(row.to_bundle_id) ?? 0) + 1);
59
- return counts;
60
- });
61
- const bundleObservationTotals = /* @__PURE__ */ new Map();
62
- for (const counts of bundleCountsByBucket) for (const [bundleId, count] of counts) bundleObservationTotals.set(bundleId, (bundleObservationTotals.get(bundleId) ?? 0) + count);
63
- return {
64
- asOfMs: request.asOfMs,
65
- window: request.window,
66
- activeInstallations: selectedInstallIds.size,
67
- series: latestByBucket.map((bucket, index) => ({
68
- bucketStartMs: windowStartMs + index * definition.bucketSizeMs,
69
- value: bucket.size
70
- })),
71
- bundleSeries: [...bundleObservationTotals].sort(([leftId, leftTotal], [rightId, rightTotal]) => rightTotal - leftTotal || compareCodePoints(leftId, rightId)).map(([bundleId]) => ({
72
- bundleId,
73
- series: bundleCountsByBucket.map((counts, index) => ({
74
- bucketStartMs: windowStartMs + index * definition.bucketSizeMs,
75
- value: counts.get(bundleId) ?? 0
76
- }))
77
- })),
78
- bundles: [...bundleCounts].map(([bundleId, installations]) => ({
79
- bundleId,
80
- installations
81
- })).sort((left, right) => right.installations - left.installations || compareCodePoints(left.bundleId, right.bundleId))
82
- };
83
- }
84
- //#endregion
85
- exports.ACTIVE_BUNDLE_EVENT_TYPES = ACTIVE_BUNDLE_EVENT_TYPES;
86
- exports.collectActiveInstallationOverview = collectActiveInstallationOverview;
87
- exports.getActiveWindowDefinition = getActiveWindowDefinition;
@@ -1,85 +0,0 @@
1
- //#region src/insights/bounded/activeOverview.ts
2
- const HOUR_MS = 3600 * 1e3;
3
- const DAY_MS = 24 * HOUR_MS;
4
- const ACTIVE_BUNDLE_EVENT_TYPES = [
5
- "UPDATE_APPLIED",
6
- "RECOVERED",
7
- "RELEASE_ADOPTED",
8
- "UNCHANGED"
9
- ];
10
- const activeWindowDefinitions = {
11
- "24h": {
12
- bucketCount: 24,
13
- bucketSizeMs: HOUR_MS
14
- },
15
- "7d": {
16
- bucketCount: 7,
17
- bucketSizeMs: DAY_MS
18
- },
19
- "30d": {
20
- bucketCount: 30,
21
- bucketSizeMs: DAY_MS
22
- }
23
- };
24
- function getActiveWindowDefinition(window) {
25
- return activeWindowDefinitions[window];
26
- }
27
- function compareCodePoints(left, right) {
28
- if (left < right) return -1;
29
- if (left > right) return 1;
30
- return 0;
31
- }
32
- function isNewer(candidate, current) {
33
- return candidate.received_at_ms > current.received_at_ms || candidate.received_at_ms === current.received_at_ms && compareCodePoints(candidate.id, current.id) > 0;
34
- }
35
- function collectActiveInstallationOverview(request) {
36
- const definition = getActiveWindowDefinition(request.window);
37
- const windowStartMs = request.asOfMs - definition.bucketCount * definition.bucketSizeMs;
38
- const rows = request.rows.filter((row) => row.received_at_ms >= windowStartMs && row.received_at_ms < request.asOfMs);
39
- const latestByInstall = /* @__PURE__ */ new Map();
40
- for (const row of rows) {
41
- const current = latestByInstall.get(row.install_id);
42
- if (current === void 0 || isNewer(row, current)) latestByInstall.set(row.install_id, row);
43
- }
44
- const selectedRows = [...latestByInstall.values()].filter((row) => request.userId === void 0 || row.user_id === request.userId);
45
- const selectedInstallIds = new Set(selectedRows.map((row) => row.install_id));
46
- const bundleCounts = /* @__PURE__ */ new Map();
47
- for (const row of selectedRows) bundleCounts.set(row.to_bundle_id, (bundleCounts.get(row.to_bundle_id) ?? 0) + 1);
48
- const latestByBucket = Array.from({ length: definition.bucketCount }, () => /* @__PURE__ */ new Map());
49
- for (const row of rows) {
50
- if (!selectedInstallIds.has(row.install_id)) continue;
51
- const bucket = latestByBucket[Math.floor((row.received_at_ms - windowStartMs) / definition.bucketSizeMs)];
52
- if (bucket === void 0) continue;
53
- const current = bucket.get(row.install_id);
54
- if (current === void 0 || isNewer(row, current)) bucket.set(row.install_id, row);
55
- }
56
- const bundleCountsByBucket = latestByBucket.map((bucket) => {
57
- const counts = /* @__PURE__ */ new Map();
58
- for (const row of bucket.values()) counts.set(row.to_bundle_id, (counts.get(row.to_bundle_id) ?? 0) + 1);
59
- return counts;
60
- });
61
- const bundleObservationTotals = /* @__PURE__ */ new Map();
62
- for (const counts of bundleCountsByBucket) for (const [bundleId, count] of counts) bundleObservationTotals.set(bundleId, (bundleObservationTotals.get(bundleId) ?? 0) + count);
63
- return {
64
- asOfMs: request.asOfMs,
65
- window: request.window,
66
- activeInstallations: selectedInstallIds.size,
67
- series: latestByBucket.map((bucket, index) => ({
68
- bucketStartMs: windowStartMs + index * definition.bucketSizeMs,
69
- value: bucket.size
70
- })),
71
- bundleSeries: [...bundleObservationTotals].sort(([leftId, leftTotal], [rightId, rightTotal]) => rightTotal - leftTotal || compareCodePoints(leftId, rightId)).map(([bundleId]) => ({
72
- bundleId,
73
- series: bundleCountsByBucket.map((counts, index) => ({
74
- bucketStartMs: windowStartMs + index * definition.bucketSizeMs,
75
- value: counts.get(bundleId) ?? 0
76
- }))
77
- })),
78
- bundles: [...bundleCounts].map(([bundleId, installations]) => ({
79
- bundleId,
80
- installations
81
- })).sort((left, right) => right.installations - left.installations || compareCodePoints(left.bundleId, right.bundleId))
82
- };
83
- }
84
- //#endregion
85
- export { ACTIVE_BUNDLE_EVENT_TYPES, collectActiveInstallationOverview, getActiveWindowDefinition };
@@ -1,42 +0,0 @@
1
- const require_scan = require("./scan.cjs");
2
- //#region src/insights/bounded/installationSearch.ts
3
- function toSearchRow(row) {
4
- return {
5
- installId: row.install_id,
6
- username: row.username,
7
- userId: row.user_id,
8
- lastKnownBundleId: row.to_bundle_id,
9
- latestStatus: row.type,
10
- platform: row.platform,
11
- appVersion: row.app_version,
12
- channel: row.channel,
13
- cohort: row.cohort,
14
- receivedAtMs: row.received_at_ms
15
- };
16
- }
17
- function matchesIdentity(row, query) {
18
- return row.install_id.toLowerCase().includes(query) || row.user_id?.toLowerCase().includes(query) === true || row.username?.toLowerCase().includes(query) === true;
19
- }
20
- function searchEventInstallations(request) {
21
- const query = request.query.toLowerCase();
22
- const matchingInstallIds = /* @__PURE__ */ new Set();
23
- for (const row of request.rows) if (query.length === 0 || matchesIdentity(row, query)) matchingInstallIds.add(row.install_id);
24
- const latestByInstall = /* @__PURE__ */ new Map();
25
- for (const row of request.rows) {
26
- if (!matchingInstallIds.has(row.install_id)) continue;
27
- const current = latestByInstall.get(row.install_id);
28
- if (current === void 0 || require_scan.compareEventNewest(row, current) < 0) latestByInstall.set(row.install_id, row);
29
- }
30
- const matchingRows = [...latestByInstall.values()].sort((left, right) => require_scan.compareCodePoints(left.install_id, right.install_id));
31
- const pageSize = Math.min(Math.max(request.limit, 0), 100);
32
- return {
33
- data: matchingRows.slice(request.offset, request.offset + pageSize).map(toSearchRow),
34
- pagination: {
35
- total: matchingRows.length,
36
- limit: request.limit,
37
- offset: request.offset
38
- }
39
- };
40
- }
41
- //#endregion
42
- exports.searchEventInstallations = searchEventInstallations;
@@ -1,42 +0,0 @@
1
- import { compareCodePoints, compareEventNewest } from "./scan.mjs";
2
- //#region src/insights/bounded/installationSearch.ts
3
- function toSearchRow(row) {
4
- return {
5
- installId: row.install_id,
6
- username: row.username,
7
- userId: row.user_id,
8
- lastKnownBundleId: row.to_bundle_id,
9
- latestStatus: row.type,
10
- platform: row.platform,
11
- appVersion: row.app_version,
12
- channel: row.channel,
13
- cohort: row.cohort,
14
- receivedAtMs: row.received_at_ms
15
- };
16
- }
17
- function matchesIdentity(row, query) {
18
- return row.install_id.toLowerCase().includes(query) || row.user_id?.toLowerCase().includes(query) === true || row.username?.toLowerCase().includes(query) === true;
19
- }
20
- function searchEventInstallations(request) {
21
- const query = request.query.toLowerCase();
22
- const matchingInstallIds = /* @__PURE__ */ new Set();
23
- for (const row of request.rows) if (query.length === 0 || matchesIdentity(row, query)) matchingInstallIds.add(row.install_id);
24
- const latestByInstall = /* @__PURE__ */ new Map();
25
- for (const row of request.rows) {
26
- if (!matchingInstallIds.has(row.install_id)) continue;
27
- const current = latestByInstall.get(row.install_id);
28
- if (current === void 0 || compareEventNewest(row, current) < 0) latestByInstall.set(row.install_id, row);
29
- }
30
- const matchingRows = [...latestByInstall.values()].sort((left, right) => compareCodePoints(left.install_id, right.install_id));
31
- const pageSize = Math.min(Math.max(request.limit, 0), 100);
32
- return {
33
- data: matchingRows.slice(request.offset, request.offset + pageSize).map(toSearchRow),
34
- pagination: {
35
- total: matchingRows.length,
36
- limit: request.limit,
37
- offset: request.offset
38
- }
39
- };
40
- }
41
- //#endregion
42
- export { searchEventInstallations };
@@ -1,39 +0,0 @@
1
- require("../../_virtual/_rolldown/runtime.cjs");
2
- let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
3
- //#region src/insights/bounded/persistence.ts
4
- function createBundleEventRow(input) {
5
- const base = {
6
- id: (0, _hot_updater_plugin_core.createUUIDv7)(),
7
- install_id: input.installId,
8
- user_id: input.userId ?? null,
9
- username: input.username ?? null,
10
- from_release_id: input.fromReleaseId,
11
- to_release_id: input.toReleaseId,
12
- to_bundle_id: input.toBundleId,
13
- platform: input.platform,
14
- app_version: input.appVersion,
15
- channel: input.channel,
16
- cohort: input.cohort,
17
- fingerprint_hash: input.fingerprintHash,
18
- sdk_version: input.sdkVersion ?? null,
19
- received_at_ms: Date.now()
20
- };
21
- switch (input.type) {
22
- case "UPDATE_APPLIED":
23
- case "RECOVERED":
24
- case "RELEASE_ADOPTED": return {
25
- ...base,
26
- type: input.type,
27
- from_bundle_id: input.fromBundleId,
28
- update_strategy: input.updateStrategy
29
- };
30
- case "UNCHANGED": return {
31
- ...base,
32
- type: input.type,
33
- from_bundle_id: null,
34
- update_strategy: null
35
- };
36
- }
37
- }
38
- //#endregion
39
- exports.createBundleEventRow = createBundleEventRow;
@@ -1,38 +0,0 @@
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 };
@@ -1,170 +0,0 @@
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;