mirror of
https://github.com/zapstore/zapstore.git
synced 2026-10-05 20:48:24 +00:00
103 lines
2.7 KiB
Markdown
103 lines
2.7 KiB
Markdown
# Zapstore — Architecture
|
|
|
|
## Core Principle
|
|
|
|
Architecture exists to prevent accidental coupling and hidden ownership.
|
|
Each layer has clear responsibilities and must not exceed them.
|
|
|
|
## Layers
|
|
|
|
### zapstore (this Flutter app)
|
|
|
|
- Flutter UI and application orchestration
|
|
- Navigation, presentation, and user interaction
|
|
- Coordinates use cases across dependencies
|
|
- Must not contain domain rules or persistence logic
|
|
|
|
### Dart dependencies
|
|
|
|
#### models
|
|
|
|
- Domain models for Nostr events, kinds, zaps, releases, and NWC
|
|
- Parsing, validation, signing, encryption, and verification
|
|
- Pure domain logic only
|
|
- Must not depend on storage, networking, isolates, or UI
|
|
|
|
#### purplebase
|
|
|
|
- Local-first storage and indexing (SQLite)
|
|
- Relay synchronization and subscription lifecycle management
|
|
- Background work and isolate execution
|
|
- Must not depend on UI or presentation logic
|
|
|
|
## Dependency Rules
|
|
|
|
- zapstore → purplebase → models
|
|
- Reverse dependencies are forbidden
|
|
- UI widgets must not manage relay connections, storage, or background jobs
|
|
|
|
## Ownership & Orchestration
|
|
|
|
- Relay pools and subscriptions are owned by purplebase
|
|
- Background work lifecycle is explicit and cancellable
|
|
- zapstore orchestrates flows but does not own low-level resources
|
|
|
|
## Common Patterns
|
|
|
|
### Widget watching data
|
|
|
|
```dart
|
|
// Watch a query provider — reactive, auto-disposes
|
|
final state = ref.watch(
|
|
query<Profile>(
|
|
authors: {pubkey},
|
|
source: const LocalAndRemoteSource(relays: {'social'}),
|
|
),
|
|
);
|
|
|
|
return switch (state) {
|
|
StorageLoading() => CircularProgressIndicator(),
|
|
StorageError(:final exception) => Text('Error: $exception'),
|
|
StorageData(:final models) => ProfileWidget(models.first),
|
|
};
|
|
|
|
// Nested queries with `and` — loads relationships
|
|
final appState = ref.watch(
|
|
query<App>(
|
|
tags: {'#d': {identifier}},
|
|
and: (app) => {
|
|
app.latestRelease.query(
|
|
source: const LocalAndRemoteSource(relays: 'AppCatalog', stream: false),
|
|
and: (release) => {release.latestMetadata.query()},
|
|
),
|
|
},
|
|
source: const LocalAndRemoteSource(relays: 'AppCatalog'),
|
|
subscriptionPrefix: 'app-detail',
|
|
),
|
|
);
|
|
```
|
|
|
|
### Imperative queries (notifiers/services)
|
|
|
|
```dart
|
|
// One-shot query via storage extension
|
|
final apps = await ref.storage.query(
|
|
RequestFilter<App>(authors: {pubkey}, limit: 20).toRequest(),
|
|
);
|
|
```
|
|
|
|
### Saving and publishing (from callbacks)
|
|
|
|
```dart
|
|
onPressed: () async {
|
|
await ref.storage.save({signedModel});
|
|
await ref.storage.publish({signedModel});
|
|
}
|
|
```
|
|
|
|
### Subscription prefix naming
|
|
|
|
All queries using the `AppCatalog` relay group MUST prefix their `subscriptionPrefix` with `app-`. This is used in the backend.
|
|
|
|
For detailed API, see models/purplebase READMEs in pub cache.
|