Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
012cfbe
docs(insights): plan scaling beyond 50000 events
gronxb Aug 31, 2026
e82054e
feat(insights): add bounded native PostgreSQL event pages
gronxb Aug 31, 2026
6583298
feat(server): expose versioned admin Insights event pages
gronxb Aug 31, 2026
2fc0cbe
docs(insights): replace compatibility rollout with required query con…
gronxb Aug 31, 2026
4349a78
fix(firebase): isolate Insights writes from catalog snapshots
gronxb Aug 31, 2026
acde600
docs(insights): define committed source capture boundaries
gronxb Aug 31, 2026
496e940
Merge branch 'next' into codex/insights-scale
gronxb Aug 31, 2026
73e73b0
Merge branch 'next' into codex/insights-scale
gronxb Aug 31, 2026
43df357
fix(server): keep event cursor ordering indexable
gronxb Aug 31, 2026
ecf196f
feat(insights): prepare native Firebase and Supabase event readers
gronxb Aug 31, 2026
4703529
feat(insights): define report requests and bounded event windows
gronxb Aug 31, 2026
1bc0b36
feat(postgres): capture committed Insights source prefixes
gronxb Aug 31, 2026
6e5f501
feat(server): prepare indexed MongoDB event pages
gronxb Aug 31, 2026
68dc3a8
feat(postgres): persist and fence Insights report jobs
gronxb Aug 31, 2026
79c6a4e
feat(insights): accumulate PostgreSQL reports in bounded steps
gronxb Aug 31, 2026
35e0e81
feat(server): audit and prepare MongoDB Insights event pages
gronxb Aug 31, 2026
fd0582c
feat(insights): bind report cursors to immutable publications
gronxb Aug 31, 2026
7f88c1d
feat(postgres): paginate immutable Insights report sections
gronxb Aug 31, 2026
8061fee
feat(postgres): prepare immutable historical installation aliases
gronxb Aug 31, 2026
9c22996
feat(postgres): prepare immutable historical installation searches
gronxb Aug 31, 2026
71b8025
feat(postgres): add bounded installation detail reads
gronxb Aug 31, 2026
42d43d5
test(postgres): make version 17 matrix opt in
gronxb Aug 31, 2026
a8aaba7
feat(postgres): add bounded live installation pages
gronxb Aug 31, 2026
66b85e8
feat(server): add committed mongodb insights source
gronxb Aug 31, 2026
17fe92c
feat(cloudflare): add bounded d1 insights source
gronxb Aug 31, 2026
480adfd
test(postgres): prepare live insights schema
gronxb Aug 31, 2026
b2366da
feat(plugin-core): define scalable insights contract
gronxb Sep 1, 2026
ca38a13
feat(mock): add scalable insights model
gronxb Sep 1, 2026
6f1e14d
feat(server): prepare scalable insights routes
gronxb Sep 1, 2026
3d5ffe4
feat(server): add scalable kysely insights
gronxb Sep 1, 2026
96c0bce
feat(console): refine insights history presentation
gronxb Sep 1, 2026
c6e8d58
fix(server): type sqlite functions
gronxb Sep 1, 2026
c713e21
test(server): streamline kysely insights coverage
gronxb Sep 1, 2026
6c16056
test(insights): await publication expiry hooks
gronxb Sep 1, 2026
f75c348
chore: consolidate insights changeset
gronxb Sep 1, 2026
f566781
feat(postgres): add scalable insights queries
gronxb Sep 1, 2026
cf22114
feat(firebase): add scalable insights queries
gronxb Sep 1, 2026
561b433
feat(cloudflare): add scalable d1 insights
gronxb Sep 1, 2026
837d6ba
feat(server): add scalable mongodb insights
gronxb Sep 1, 2026
df43cd8
feat(server): add scalable drizzle insights
gronxb Sep 1, 2026
c64d8fd
feat(supabase): add scalable insights queries
gronxb Sep 1, 2026
1207ce8
feat(aws): add scalable dynamodb insights
gronxb Sep 1, 2026
0b28970
Merge remote-tracking branch 'origin/next' into codex/insights-scale
gronxb Sep 2, 2026
4b60f9a
feat(insights): complete scalable provider cutover
gronxb Sep 2, 2026
58f5fc7
feat(insights): advance bounded preparation jobs
gronxb Sep 2, 2026
ad00f12
chore(example): refresh v0.85.0 fingerprint
gronxb Sep 2, 2026
432ffc2
fix(example): align native fingerprint hashes
gronxb Sep 2, 2026
9d6dd82
fix(server): serialize Prisma SQLite appends
gronxb Sep 2, 2026
e3c18ef
fix(server): queue Prisma SQLite appends
gronxb Sep 2, 2026
b804ff1
fix(server): serialize Drizzle SQLite insights
gronxb Sep 2, 2026
8891798
fix(server): provision Prisma insights schema
gronxb Sep 2, 2026
f524718
fix(e2e): bound insights readiness polling
gronxb Sep 2, 2026
955f673
fix(mongodb): provision insights storage
gronxb Sep 2, 2026
c1bbda2
Merge remote-tracking branch 'origin/next' into codex/insights-scale
gronxb Sep 2, 2026
c708301
fix(prisma): serialize sqlite insights operations
gronxb Sep 3, 2026
c01f82f
fix(insights): preserve database provider configuration
gronxb Sep 3, 2026
c6e4803
fix(aws): refresh Insights initialization token
gronxb Sep 3, 2026
9b97650
refactor(insights): remove report lifecycle
gronxb Sep 4, 2026
8ab3696
feat(insights): keep operational views bounded
gronxb Sep 4, 2026
70e34fa
fix(aws): issue valid Insights queries
gronxb Sep 4, 2026
ef064c3
fix(example): restore current native fingerprint
gronxb Sep 4, 2026
06c1506
fix(example): sync embedded native fingerprint
gronxb Sep 4, 2026
e79ffc1
feat(insights): define storage contract and scoped reports
gronxb Sep 5, 2026
54712dc
fix(e2e): bypass synchronization for intentional iOS crash launches
gronxb Sep 5, 2026
a40e4cc
fix(schema): consolidate unreleased insights into 1.0.0
gronxb Sep 6, 2026
5719b2f
fix(e2e): scope provider cleanup to fixture releases
gronxb Sep 6, 2026
8a0a384
fix(e2e): stop native build daemons after compilation
gronxb Sep 6, 2026
42f2cc0
refactor(insights): keep provider contract minimal
gronxb Sep 6, 2026
39992ed
perf(e2e): build only app android test artifacts
gronxb Sep 6, 2026
f12c0f9
perf(e2e): allow two concurrent bundle deploys
gronxb Sep 6, 2026
1e4b57d
refactor(insights): trim redundant contract coverage
gronxb Sep 6, 2026
e978bb6
test(e2e): wait for refreshed release state
gronxb Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions .changeset/browse-all-insights-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
---

Add Overview / Events navigation in Insights to browse event history without
an installation search, reporting-period filter, or bundle filter. Include all
event types in newest-first order, with pagination, refresh, and links to
installation history that preserve the source page and scroll position. Use
readable local timestamps, copyable short identifiers, and semantic event
labels and colors. Keep the existing Insights scan limit and report an
error rather than silently returning partial history when it is exceeded.
an installation search or bundle filter. Include all event types in newest-first
order with cursor pagination, refresh, and links to installation history that
preserve the source page and scroll position. Use readable local timestamps,
copyable short identifiers, semantic event labels, and responsive mobile cards.
35 changes: 35 additions & 0 deletions .changeset/define-insights-storage-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
"@hot-updater/plugin-core": minor
"@hot-updater/server": minor
"@hot-updater/console": minor
"@hot-updater/aws": minor
"@hot-updater/cloudflare": minor
"@hot-updater/firebase": minor
"@hot-updater/postgres": minor
"@hot-updater/supabase": minor
"@hot-updater/cli-tools": patch
"@hot-updater/mock": minor
"@hot-updater/test-utils": minor
---

Define the required Insights persistence contract as `record`, `listEvents`,
object-based `findInstallations`, `countInstallations`, and `countEvents`. Core
owns report preparation, filters, windows, cursors, and summaries; providers
implement atomic report/latest-state storage, fixed indexed queries, and scalar
counts. Duplicate event IDs are first-write-wins and never update installation
state again.

Add scoped recent-reporting counts and selected-bundle applied, recovered-from,
and adopted report counts with matching event drill-down in Console. Recovery
from B to A belongs to B's recovery count while latest state names A. Counts
remain independent live measurements and do not claim an exact share or success
rate.

Include all Insights indexes and native writers in the initial `1.0.0` schema.
MongoDB Insights requires native transactions on a replica set or sharded
cluster. Regenerate standalone ORM schemas and apply emitted Prisma collation
SQL where required.

Prisma SQL Server Insights explicitly rejects before database I/O because its
string identity/order semantics do not meet this contract; other models remain
available. MongoDB counts require version 5+ snapshot reads.
9 changes: 0 additions & 9 deletions .changeset/fix-insights-scan-cursor.md

This file was deleted.

2 changes: 1 addition & 1 deletion .detoxrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ module.exports = {
process.env.HOT_UPDATER_E2E_ANDROID_TEST_BINARY_PATH ||
"examples/v0.85.0/android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk",
build:
`cd examples/v0.85.0/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release -PreactNativeArchitectures=${androidArchitectures} -PHOT_UPDATER_E2E_DEBUGGABLE=true -PMIN_BUNDLE_ID=00000000-0000-7000-8000-000000000000`,
`cd examples/v0.85.0/android && ./gradlew --no-daemon :app:assembleRelease :app:assembleReleaseAndroidTest -Pkotlin.compiler.execution.strategy=in-process -DtestBuildType=release -PreactNativeArchitectures=${androidArchitectures} -PHOT_UPDATER_E2E_DEBUGGABLE=true -PMIN_BUNDLE_ID=00000000-0000-7000-8000-000000000000`,
},
},
devices: {
Expand Down
147 changes: 0 additions & 147 deletions docs/architecture/insights-mobile-review.md

This file was deleted.

123 changes: 92 additions & 31 deletions docs/architecture/insights.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Built-in Insights

Insights is a first-party server domain backed by the official
`database.models.insights` port. It is not a server plugin and does not declare its
own provider, schema lifecycle, or universal component adapter.
Insights is a server domain backed by `database.models.insights`. The database
stores immutable reports and one latest report per installation; the server
assembles operational views and selected-bundle deployment evidence.

```ts
createHotUpdater({
Expand All @@ -11,36 +11,97 @@ createHotUpdater({
});
```

`createHotUpdater` always mounts event ingestion on `handlers.client` and
Insights queries on `handlers.admin`. React Native clients send automatic
lifecycle reports by default and can opt out with
`HotUpdater.init({ insights: false })`.
API keys authenticate event ingestion, Release Catalog, and artifact
requests, but they do not grant Insights query access.
`createHotUpdater` mounts ingestion on `handlers.client` and queries on
`handlers.admin`. The admin handler does not authenticate itself: mount it
behind framework authentication, or call the Insights provider from an
authenticated server surface, as the Console does. API keys authorize client
requests and ingestion, not admin queries. React Native sends lifecycle reports
by default; `HotUpdater.init({ insights: false })` opts out.

The admin handler does not authenticate itself. Mount it only behind framework
authentication, or use the database-backed Insights provider directly from an
authenticated server surface, as the Console does.
## Provider responsibility

`clientAccess` is a required, explicit authentication policy.
Client update and Insights ingestion routes are always present on
`handlers.client`, while mounting `handlers.admin` is the explicit opt-in for
admin HTTP routes.
Custom database authors implement five operations, all with object inputs:

The database plugin owns physical storage and migration for `bundle_events`.
The server owns event input validation, bounded scans, aggregation, installation
search, and HTTP responses. Every database provider therefore exposes the same
logical persistence contract:
| Method | Responsibility |
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `record({ event, installation })` | Atomically store the event and advance latest state; first event ID wins |
| `listEvents({ filter, sinceMs, beforeReceivedAtMs, after, limit })` | Indexed newest-first global, installation-movement, or bundle-outcome history |
| `findInstallations({ installId } or { userId, afterInstallId, limit })` | Exact latest-state lookup or current-user page |
| `countInstallations({ platform, channel, sinceMs, bundleId })` | Count recent latest rows, optionally naming one bundle |
| `countEvents({ filter, sinceMs, beforeReceivedAtMs })` | Count accepted reports matching one raw bundle/type/scope filter |

```ts
models: {
insights: {
append(row): Promise<void>;
scan({ beforeReceivedAtMs, after, limit }): Promise<readonly BundleEventRow[]>;
},
}
```
The [custom database guide](../content/docs/%28latest%29/database-plugins/custom-database.mdx#insights)
specifies ordering, atomicity, idempotency, visibility, pagination, and test
requirements. Public types and boundary validation come from
`@hot-updater/plugin-core`. The internal CRUD adapter is an implementation aid
for bundled providers, not an additional interface that custom providers must
implement.

Core creates the report ID, receipt time, and full installation candidate. It
owns movement semantics, scope/window selection, opaque cursors, and UI labels.
Providers translate fixed predicates and persist data; they do not implement
summary objects, outcome classifications, percentages, top-N groups, or charts.

## Product views

The Console provides:

- scoped reporting-installation counts over 24 hours, 7 days, or 30 days;
- selected-bundle reporting installations plus applied, recovered-from, and
adopted report counts;
- outcome drill-down using exactly the same scope and receipt interval;
- all-event browsing and exact installation/current-user lookup;
- bundle movement history for a selected installation.

`getReportingOverview({ platform, channel, window, bundleId? })` returns one
scope measurement and, when a bundle is selected, four bundle measurements.
Each scalar has `count` and `measuredAtMs`. The response also includes `sinceMs`
and `beforeReceivedAtMs`, which bind outcome drill-down pages. The admin HTTP
route is `GET /overview` relative to the admin handler mount. The global event
method is `listEvents`; exact installation lookup takes `{ installId }`.

Recovery from B to A contributes a recovered-from report to B, while the latest
installation row names A. Adoption of a Release with the same bundle contributes
an adoption report, not an application. `UNCHANGED` reports update latest state
but do not increment those outcome counters.

Counts describe reports received by the server, not all devices or unique
update attempts. Offline devices and failed sends are absent. Independent live
counts do not establish an exact share, success rate, or deployment completion.
The UI displays them independently and does not clamp them into a ratio.

Event pages sort descending by `(received_at_ms, id)`, apply filters before a
limit of at most 101, and use an exclusive keyset cursor. Receipt intervals are
`[sinceMs, beforeReceivedAtMs)`. Native continuation pages must be exhausted
before returning a short result. Core does not aggregate raw history. The
Console keeps previous cursors in session memory; only the current cursor and
filter bounds appear in its URL.

Latest-state counts never read event history. DynamoDB traverses canonical
installation IDs so an installation cannot be counted twice when its last-report
time advances. Its cost grows with stored installation rows, including rows
outside the selected window. Other providers use native aggregate queries;
returning one scalar does not imply constant work or latency.

## Initial storage setup

Schema `1.0.0` includes atomic storage and every Insights access path from the
first initialization. Standalone SQL tooling initializes empty storage and
leaves an initialized `1.0.0` database unchanged. Generate ORM schema artifacts
before deployment. Prisma PostgreSQL/MySQL require the emitted companion
collation SQL. MongoDB requires version 5 or later on a replica set or sharded
cluster for native transactions and snapshot counts.

Secondary indexes may lag. Exact installation reads use canonical state;
current-user queries validate index candidates against that state so an old
association is not returned. Newly assigned users can briefly have missing
results. Counts and pages become complete once writes and indexes converge;
a fixed receipt cutoff is not a commit watermark or a cross-request snapshot.

## SQL Server limitation

Scans are ordered by `(received_at_ms, id)` and are capped at 50,000 matching
rows to keep built-in aggregation bounded. The Console reads the same server
domain and no longer binds a separate Insights package or provider.
The Prisma SQL Server adapter retains its other models, but its five Insights
methods reject before database I/O. SQL Server's padded string equality can
merge IDs that differ by trailing spaces; its default Unicode/UUID ordering
also differs from this contract. There is no silent fallback to weaker identity
or pagination semantics. Use a supported Insights provider for these views.
9 changes: 4 additions & 5 deletions docs/content/docs/(latest)/database-plugins/aws.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ For AWS installations created by `npx hot-updater init`, DynamoDB is selected
by default and this table is created or validated automatically.

For custom installations, create one on-demand table manually with the
overloaded index used for update checks and patch relations:
overloaded index used for patch relations and installation movement history:

```bash
aws dynamodb create-table \
Expand Down Expand Up @@ -170,10 +170,9 @@ const hotUpdater = createHotUpdater({

The Lambda or server role needs `Query` access to the update index,
`BatchGetItem`/`GetItem` access to Bundle, patch, and API key partitions,
and `PutItem`/`Query` access to the Insights event partition. CLI and Console
writers need the transaction and write permissions required to manage Bundle
metadata and API keys. The AWS adapter does not impose an Insights
retention period.
and `GetItem`/`Query`/`TransactWriteItems` access to Insights partitions. CLI
and Console writers need the transaction and write permissions required to
manage Bundle metadata and API keys.

The provider does not impose a total bundle, patch, or patch-relationship count
limit. Each serialized metadata item is limited to 8 KiB, and counters and
Expand Down
Loading
Loading