Skip to content

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

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

feat: bundle mode with atomic deploys, seed assets, and version-gated caching#482
divyanshub024 wants to merge 3 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 plus on-device persistence with seed-based hydration; added runtime syncing/updating and bundle-aware artifact fetching.
    • Updated deployment to publish screens/themes as one atomic bundle by default, including writing a seed asset; added --legacy to keep the previous upload flow.
  • Bug Fixes
    • Updated build output cleanup to clear only screens/themes to avoid redeploying stale JSON.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 29a2ce4c-0d1a-4d17-9824-99584e498557

📥 Commits

Reviewing files that changed from the base of the PR and between 78c361f and e24e705.

📒 Files selected for processing (1)
  • packages/stac/lib/src/services/stac_bundle_updater.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/stac/lib/src/services/stac_bundle_updater.dart

📝 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 data/configuration models, JSON serialization, 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 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, and service exports.
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 tests
packages/stac/test/models/stac_bundle_test.dart, packages/stac/test/services/stac_bundle_service_test.dart
Tests serialization, sync, hydration, seed precedence, lifecycle updates, and clearing.

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 checksum generation, 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/checksum coverage and documents the 1.7.0 behavior.

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

Possibly related PRs

  • StacDev/stac#393: Modifies the same stac_cloud.dart artifact-fetch path.
  • StacDev/stac#435: Modifies the same initialization entry points with an additional global configuration parameter.

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 change: bundle mode with 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 on lines +202 to +207
if (_bundleConfig.enabled) {
StacBundleUpdater.start();
if (_bundleConfig.prefetchOnInit && options != null) {
unawaited(StacBundleService.sync());
}
}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
ast-grep outline packages/stac/lib/src/services/stac_bundle_service.dart
rg -n -B2 -A5 'static\s+Dio\s+(get\s+)?dio' packages/stac/lib/src/services/stac_bundle_service.dart

Repository: StacDev/stac

Length of output: 220


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## relevant files\n'
git ls-files | rg 'packages/stac/lib/src/.*(stac_service\.dart|bundle|updater|network|service)'

printf '\n## locate bundle service/updater symbols\n'
rg -n 'class StacBundleService|class StacBundleUpdater|static .*dio|dio\s*=' packages/stac/lib/src

printf '\n## inspect initialize block\n'
sed -n '180,240p' packages/stac/lib/src/framework/stac_service.dart

printf '\n## inspect bundle-related implementations\n'
for f in $(git ls-files | rg 'packages/stac/lib/src/.*(bundle|updater|network|service).*\.dart'); do
  echo
  echo "### $f"
  wc -l "$f"
done

Repository: StacDev/stac

Length of output: 5339


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## StacBundleService relevant sections\n'
sed -n '1,120p' packages/stac/lib/src/services/stac_bundle_service.dart
printf '\n---\n'
sed -n '120,240p' packages/stac/lib/src/services/stac_bundle_service.dart
printf '\n---\n'
sed -n '240,330p' packages/stac/lib/src/services/stac_bundle_service.dart

printf '\n## StacBundleUpdater relevant sections\n'
sed -n '1,180p' packages/stac/lib/src/services/stac_bundle_updater.dart

Repository: StacDev/stac

Length of output: 13675


Propagate dio to bundle mode, and fail fast when options is missing. packages/stac/lib/src/framework/stac_service.dart:202-207

  • dio: only reaches StacNetworkService; StacBundleService.sync() still uses its own default client, so bundle requests won’t honor caller auth/interceptors/proxy settings.
  • When bundleConfig.enabled is set without options, StacBundleUpdater.start() still runs and the first resume/poll sync throws from _projectId outside the try, instead of surfacing as an init-time validation error.
🤖 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/framework/stac_service.dart` around lines 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.

…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.
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