Skip to content

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

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

feat: bundle mode with atomic deploys, seed assets, and version-gated caching#482
divyanshub024 wants to merge 2 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 that initializes, syncs, and serves screens/themes from a single versioned bundle (with resume checks and optional polling).
    • Introduced bundled artifact support with on-device persistence and seed-based hydration for faster/offline startup.
    • Updated deployment to publish screens/themes as one atomic bundle by default, optionally generating a seed asset for bundle mode; added a --legacy flag to keep the previous upload flow.
  • Bug Fixes
    • Improved build output cleanup to clear only screens/themes, preventing stale JSON from being redeployed.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds bundle mode to stac with new bundle models, storage, sync, lifecycle updating, and bundle-backed artifact fetching. Updates stac_cli to deploy bundles atomically, write a seed asset, support legacy uploads, and clear stale 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 StacBundle and StacBundleConfig plus JSON serialization helpers and barrel exports.
Initialization wiring for bundleConfig
packages/stac/lib/src/framework/stac.dart, packages/stac/lib/src/framework/stac_service.dart
Stac.initialize and StacService.initialize accept bundleConfig, store it, and start bundle updater and optional prefetch sync when enabled.
Bundle persistence store
packages/stac/lib/src/services/stac_bundle_store.dart
Adds StacBundleStore and SharedPreferencesBundleStore for schema-versioned bundle read, write, and clear operations.
Bundle sync and hydration service
packages/stac/lib/src/services/stac_bundle_service.dart, packages/stac/lib/src/services/services.dart
Adds StacBundleService for conditional sync, store and seed hydration, screen/theme lookup, clear, reset, and service exports.
Bundle updater lifecycle
packages/stac/lib/src/services/stac_bundle_updater.dart
Adds StacBundleUpdater as a lifecycle observer with resume sync, polling, and timer cancellation behavior.
StacCloud bundle-mode fetch
packages/stac/lib/src/services/stac_cloud.dart
Adds a bundle-mode branch in StacCloud that serves screen and theme JSON from StacBundleService before falling back to existing fetch logic.
Bundle model and service tests
packages/stac/test/models/stac_bundle_test.dart, packages/stac/test/services/stac_bundle_service_test.dart
Adds tests for StacBundle serialization and StacBundleService sync, hydration, updater, and clear behavior.

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
DeployService gains injectable HTTP client support, checksum computation, bundle POSTing, seed asset writing, response decoding, and bundle endpoint resolution; crypto is added as a dependency.
--legacy flag wiring
packages/stac_cli/lib/src/commands/deploy_command.dart
Adds a --legacy CLI flag and forwards it to DeployService.deploy.
Build output cleanup fix
packages/stac_cli/lib/src/services/build_service.dart
BuildService.build now clears only the screens and themes output subdirectories before generating new JSON.
Deploy tests and changelog
packages/stac_cli/test/services/deploy_service_test.dart, packages/stac_cli/CHANGELOG.md
Adds bundle deploy tests, checksum tests, and a changelog entry for the new deploy behavior.

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

Possibly related PRs

  • StacDev/stac#393: Touches the same stac_cloud.dart artifact-fetch path that this PR extends for bundle-mode fetches.
  • StacDev/stac#381: Relates to the Stac.initialize call flow that now accepts bundleConfig.

Suggested reviewers: Potatomonsta

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 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.

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