Skip to content

feat: bundle mode with atomic deploys, seed assets, and version-gated caching#482

Open
divyanshub024 wants to merge 8 commits into
devfrom
feat/bundle-deploy
Open

feat: bundle mode with atomic deploys, seed assets, and version-gated caching#482
divyanshub024 wants to merge 8 commits into
devfrom
feat/bundle-deploy

Conversation

@divyanshub024

@divyanshub024 divyanshub024 commented Jul 3, 2026

Copy link
Copy Markdown
Member

What

Adds opt-in bundle mode: instead of fetching every screen/theme with its own request, apps download all artifacts as a single versioned bundle, cache it, and only re-download when the server has a newer version.

Client (packages/stac)

  • StacBundleConfig on Stac.initialize (enabled: false by default — zero behavior change unless opted in): seedAsset, baseUrl, checkOnResume, pollingInterval.
  • StacBundleService: conditional sync (ETag/If-None-Match + ?since fallback) — 304/204 keep the cache, 200 swaps and persists; offline falls back to the cached bundle; updates stream notifies hosts when a new version lands.
  • Seed bundles: ship the bundle as a Flutter asset and the very first launch renders instantly, even offline. Hydration picks the highest version between the on-device cache and the seed.
  • StacBundleUpdater: re-checks on app-resume (default on) and on an optional foreground polling interval.
  • Renders never hit the network — the server-owned version is the only cache invalidator.

CLI (packages/stac_cli)

  • stac deploy is now atomic: one POST with all screens/themes + checksum; any failure exits non-zero with nothing partially applied. Logs artifact counts and payload size. --legacy keeps the old per-file behavior for one release.
  • Writes the seed asset (assets/stac_bundle.json) stamped with the server-authoritative version after every deploy, and warns if pubspec doesn't declare it.
  • stac build now cleans stale outputs so deleted screens can't linger in .build.

Tests

  • 21 new bundle service/updater tests + 5 model tests (packages/stac), 8 deploy service tests (packages/stac_cli) — first tests in the CLI package (test: Write tests for stac_cli #459 start).
  • dart analyze clean in both packages.

Notes

  • Requires the Stac Cloud /bundles endpoint (rolling out server-side).
  • Legacy per-screen fetching and its cache strategies are untouched; bundle mode falls through to them for artifacts missing from a bundle.

Summary by CodeRabbit

  • New Features
    • Added opt-in “bundle mode” via a new bundleConfig option to initialize, sync, and serve screens/themes from a single versioned bundle (with resume checks and optional polling).
    • Introduced bundle models/config, on-device persistence, and seed-based hydration to support faster/offline startup.
    • Updated deployment to publish screens/themes as one atomic bundle by default and write a seed asset; added --legacy to restore the previous upload flow.
  • Bug Fixes
    • Adjusted build output cleanup to clear only screens/themes, preventing redeploys with stale JSON.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29120ecd-c20e-441c-8a78-a5deb2f51501

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds opt-in bundle mode to stac, including models, persistence, synchronization, lifecycle updates, and bundle-backed artifact fetching. Updates stac_cli to deploy screens and themes atomically, write seed assets, support legacy uploads, and clean build outputs.

Changes

Stac package bundle mode

Layer / File(s) Summary
Bundle data model and configuration
packages/stac/lib/src/models/stac_bundle.dart, packages/stac/lib/src/models/stac_bundle.g.dart, packages/stac/lib/src/models/stac_bundle_config.dart, packages/stac/lib/src/models/models.dart
Adds bundle models, JSON serialization, configuration value semantics, and public exports.
Initialization wiring
packages/stac/lib/src/framework/stac.dart, packages/stac/lib/src/framework/stac_service.dart
Accepts bundleConfig, stores it, starts or stops lifecycle updating, and optionally prefetches bundles.
Persistence and synchronization
packages/stac/lib/src/services/stac_bundle_store.dart, packages/stac/lib/src/services/stac_bundle_service.dart, packages/stac/lib/src/services/services.dart
Adds schema-versioned storage, conditional sync, seed hydration, lookups, clearing, invalidation, and failure backoff.
Lifecycle and artifact fetching
packages/stac/lib/src/services/stac_bundle_updater.dart, packages/stac/lib/src/services/stac_cloud.dart
Adds resume/poll synchronization and serves bundled screen/theme JSON before legacy fetching.
Bundle validation
packages/stac/test/models/stac_bundle_test.dart, packages/stac/test/services/stac_bundle_service_test.dart, packages/stac/test/fixtures/*
Tests serialization, sync, hydration, seed precedence, lifecycle updates, clearing, project switching, and artifact fallback.

stac_cli atomic bundle deployment

Layer / File(s) Summary
Bundle deployment core
packages/stac_cli/lib/src/services/deploy_service.dart, packages/stac_cli/pubspec.yaml
Adds deterministic checksums, atomic bundle POSTing, seed asset writing, response handling, legacy support, and the crypto dependency.
Legacy deployment flag
packages/stac_cli/lib/src/commands/deploy_command.dart
Adds --legacy and forwards the selected deployment mode.
Build output cleanup
packages/stac_cli/lib/src/services/build_service.dart
Clears only screen and theme output directories before generation.
Deployment validation and release notes
packages/stac_cli/test/services/deploy_service_test.dart, packages/stac_cli/CHANGELOG.md
Adds deployment and checksum coverage and documents the 1.7.0 behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant StacBundleService
  participant StacBundleStore
  participant BundlesAPI
  App->>StacBundleService: request bundle synchronization
  StacBundleService->>StacBundleStore: hydrate cached bundle
  StacBundleService->>BundlesAPI: conditional GET /bundles
  BundlesAPI-->>StacBundleService: bundle or not-modified response
  StacBundleService->>StacBundleStore: persist updated bundle
  StacBundleService-->>App: serve screen/theme JSON
Loading
sequenceDiagram
  participant CLI
  participant DeployService
  participant BundlesAPI
  participant SeedFilesystem
  CLI->>DeployService: deploy bundle
  DeployService->>BundlesAPI: POST /bundles
  BundlesAPI-->>DeployService: versioned bundle response
  DeployService->>SeedFilesystem: write assets/stac_bundle.json
Loading

Possibly related PRs

Suggested reviewers: potatomonsta

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: bundle mode, atomic deploys, seed assets, and version-gated caching.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bundle-deploy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

- StacBundle/StacBundleConfig models; SharedPreferences bundle store
  with schema-version guard
- StacBundleService: conditional sync (ETag/since), seed-asset
  hydration (highest version wins), updates stream, offline fallback
- StacBundleUpdater: sync on app-resume + optional foreground polling
- StacCloud serves screens/themes from the bundle when enabled
  (opt-in; falls through to legacy per-artifact fetch)
- CLI: single atomic POST /bundles with checksum + payload size log,
  seed asset emission with pubspec warning, --legacy fallback flag;
  build now cleans stale .build outputs
@divyanshub024 divyanshub024 changed the title feat: bundle mode — atomic deploys, seed assets, and version-gated caching feat: bundle mode with atomic deploys, seed assets, and version-gated caching Jul 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/stac/lib/src/services/stac_bundle_service.dart (2)

104-167: 🩺 Stability & Availability | 🔵 Trivial

No backoff on repeated sync failures.

On non-2xx/non-304/204 statuses or network errors, sync() simply returns the stale bundle without recording failure state. Combined with StacBundleUpdater's fixed polling interval and resume-triggered checks, a persistently failing/rate-limiting server will be hit at the same fixed cadence indefinitely. Consider adding lightweight backoff (e.g., skip the next N scheduled checks after consecutive failures) for resilience during outages.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stac/lib/src/services/stac_bundle_service.dart` around lines 104 -
167, Add lightweight failure backoff to the bundle sync flow so repeated errors
do not keep hitting the server at a fixed cadence. Update
StacBundleService._syncInternal to record consecutive failures for
non-2xx/304/204 responses and caught exceptions, then have StacBundleUpdater
skip the next scheduled sync attempts for a short backoff window after repeated
failures. Reset the failure state after a successful sync so normal polling
resumes.

169-194: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Checksum is stored but never verified.

_bundleFromResponse captures checksum from the response into the persisted StacBundle, but nothing in this file (or the reviewed cohort) compares it against a locally computed hash of screens/themes before trusting and persisting the payload on a 200. If the intent (per the CLI's computeBundleChecksum) is end-to-end integrity verification, a corrupted/incomplete-but-valid-JSON response would currently be accepted and cached without detection.

If checksum verification is intentionally deferred to the model layer or CLI-only (deploy-time verification), this is fine — otherwise consider validating it here before swapping in the new bundle.

♻️ Possible verification step
         final bundle = _bundleFromResponse(
           Map<String, dynamic>.from(data),
           projectId: projectId,
           etagHeader: response.headers.value('etag'),
         );
         if (bundle == null) {
           Log.w('StacBundleService: Bundle response is missing a version');
           return cached;
         }
+        if (bundle.checksum != null &&
+            !_matchesChecksum(bundle)) {
+          Log.w('StacBundleService: Bundle checksum mismatch, discarding');
+          return cached;
+        }

Please confirm (or search) whether checksum verification against the bundle payload is implemented elsewhere in the StacBundle model, or whether it's deploy-time-only by design.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/stac/lib/src/services/stac_bundle_service.dart` around lines 169 -
194, The checksum captured in _bundleFromResponse is persisted but never
validated against the bundle payload before accepting a 200 response. Check
whether StacBundle or any related service already verifies the checksum; if not,
add verification in StacBundleService._bundleFromResponse before constructing
the StacBundle, comparing the response checksum to a locally computed hash of
screens/themes and rejecting mismatches. If checksum validation is intentionally
handled elsewhere, document that design clearly in this path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/stac_cli/lib/src/services/deploy_service.dart`:
- Around line 137-182: The deploy flow in deploy_service.dart currently writes
the seed asset even when the decoded 2xx response has no version, which can
overwrite a valid bundle with an invalid empty seed. Update the deploy handling
around the response body/version check in the deploy method to validate that
body['version'] is present before calling _writeSeedAsset, and either skip the
write or throw a StacException for malformed success responses; keep the
existing success/info logging and checksum handling in place.

In `@packages/stac/lib/src/framework/stac_service.dart`:
- Around line 202-207: The bundle initialization path in StacService should use
the caller’s dio and validate options up front: pass the same dio used by
StacNetworkService into the bundle sync flow so StacBundleService.sync honors
auth/interceptors/proxy settings, and guard the _bundleConfig.enabled branch so
StacBundleUpdater.start() and prefetch only run when options is present. If
bundle mode is enabled without options, fail fast during initialization with a
clear validation error instead of letting the later _projectId access in
StacBundleService or the updater throw asynchronously.

---

Nitpick comments:
In `@packages/stac/lib/src/services/stac_bundle_service.dart`:
- Around line 104-167: Add lightweight failure backoff to the bundle sync flow
so repeated errors do not keep hitting the server at a fixed cadence. Update
StacBundleService._syncInternal to record consecutive failures for
non-2xx/304/204 responses and caught exceptions, then have StacBundleUpdater
skip the next scheduled sync attempts for a short backoff window after repeated
failures. Reset the failure state after a successful sync so normal polling
resumes.
- Around line 169-194: The checksum captured in _bundleFromResponse is persisted
but never validated against the bundle payload before accepting a 200 response.
Check whether StacBundle or any related service already verifies the checksum;
if not, add verification in StacBundleService._bundleFromResponse before
constructing the StacBundle, comparing the response checksum to a locally
computed hash of screens/themes and rejecting mismatches. If checksum validation
is intentionally handled elsewhere, document that design clearly in this path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 356aed86-6eb8-47f6-ae9f-429fd5ea4c48

📥 Commits

Reviewing files that changed from the base of the PR and between e3bd43c and 11bd5a3.

⛔ Files ignored due to path filters (1)
  • packages/stac_cli/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (19)
  • packages/stac/lib/src/framework/stac.dart
  • packages/stac/lib/src/framework/stac_service.dart
  • packages/stac/lib/src/models/models.dart
  • packages/stac/lib/src/models/stac_bundle.dart
  • packages/stac/lib/src/models/stac_bundle.g.dart
  • packages/stac/lib/src/models/stac_bundle_config.dart
  • packages/stac/lib/src/services/services.dart
  • packages/stac/lib/src/services/stac_bundle_service.dart
  • packages/stac/lib/src/services/stac_bundle_store.dart
  • packages/stac/lib/src/services/stac_bundle_updater.dart
  • packages/stac/lib/src/services/stac_cloud.dart
  • packages/stac/test/models/stac_bundle_test.dart
  • packages/stac/test/services/stac_bundle_service_test.dart
  • packages/stac_cli/CHANGELOG.md
  • packages/stac_cli/lib/src/commands/deploy_command.dart
  • packages/stac_cli/lib/src/services/build_service.dart
  • packages/stac_cli/lib/src/services/deploy_service.dart
  • packages/stac_cli/pubspec.yaml
  • packages/stac_cli/test/services/deploy_service_test.dart

Comment thread packages/stac_cli/lib/src/services/deploy_service.dart
Comment thread packages/stac/lib/src/framework/stac_service.dart
…dater

Stac.initialize runs before runApp in most apps, so WidgetsBinding.instance
may not exist when bundle mode registers its lifecycle observer. Create the
binding defensively (idempotent) instead of requiring every app to add
WidgetsFlutterBinding.ensureInitialized before Stac.initialize.
- key in-memory bundle state by projectId; a re-initialize with a
  different project drops the old bundle (epoch-guarded, so in-flight
  syncs can't resurrect stale state)
- clear() re-hydrates the seed and discards racing in-flight syncs
- sync(force: true) queues behind an in-flight sync instead of being
  silently downgraded to its conditional result
- back off ensureLoaded's auto-sync for 30s after a failure and stop
  falling through to legacy per-screen fetches when the bundles
  endpoint rejected the project (404/403); memoize missing-artifact
  warnings per bundle version
- stop the updater when re-initialized with bundle mode disabled;
  re-arm the polling timer on config changes; sync without options
  returns null instead of throwing on resume/poll ticks
- mark hydration complete only on success; surface store write
  failures and non-network sync errors at warn level
- normalize trailing slash in baseUrl
- CLI: skip the seed write (loudly) when the deploy response carries
  no version; fix bare `assets` pubspec false-negative; correct the
  1.7.0 changelog wording

Adds 16 tests covering each fixed behavior.
Bundle mode fetches a project's bundle and cannot function without a
projectId. Validate at the top of initialize (before any state is
mutated) instead of starting an updater whose resume and poll ticks can
only warn. With the guarantee in place, the prefetch no longer needs its
own null check.

sync() keeps returning null for a missing-options call as defense in
depth, since it is public API and reachable without a successful
initialize.
Enable StacBundleConfig with a seed asset, add the generated
stac_bundle.json, install agent skills into the example, migrate iOS
Runner to the scene-delegate lifecycle, and bump stac to 1.5.0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant