### Full library setup and initialization Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/configuration.md A complete example demonstrating NDK initialization, database setup, and usage of the AppendOnlyLists class. ```dart import 'package:broadcast_queue_shim_for_ndk/broadcast_queue_shim_for_ndk.dart'; import 'package:ndk/ndk.dart'; import 'package:nip_append_only_list/nip_append_only_list.dart'; import 'package:sembast/sembast_io.dart'; Future main() async { // 1. Initialize NDK with event cache final ndk = Ndk( NdkConfig( eventVerifier: Bip340EventVerifier(), cache: await SembastCacheManager.create(databasePath: 'data'), ), ); // 2. Open persistence databases final outboxDb = await databaseFactoryIo.openDatabase('outbox.db'); final projectionDb = await databaseFactoryIo.openDatabase('projection.db'); // 3. Create outbox (durable delivery queue) final outbox = OfflineBroadcast.withNdk(ndk, db: outboxDb); outbox.start(); // 4. Create projection store final projection = ProjectionStore(projectionDb); // 5. Create AppendOnlyLists with all layers wired final lists = AppendOnlyLists( ndk: ndk, outbox: outbox, projection: projection, maxEventBytes: 32 * 1024, // Conservative for portability ); // 6. Login a signer ndk.accounts.loginPrivateKey(pubkey: myPubkey, privkey: myPrivkey); // 7. Use the lists await lists.add( listName: 'fruits', entries: const [ AppendOnlyListEntry(tag: 't', value: 'apple'), ], ); final state = await lists.getList(pubkey: myPubkey, listName: 'fruits'); print('List: ${state.entries}'); // 8. Cleanup await lists.dispose(); await outbox.dispose(); await ndk.destroy(); } ``` -------------------------------- ### Usage examples for add() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Examples demonstrating adding public, mixed, and custom-signed entries to a list. ```dart // Add public entries await lists.add( listName: 'fruits', entries: const [ AppendOnlyListEntry(tag: 't', value: 'apple'), AppendOnlyListEntry(tag: 't', value: 'banana'), ], ); // Add mixed public and private entries await lists.add( listName: 'fruits', entries: const [ AppendOnlyListEntry(tag: 't', value: 'apple'), AppendOnlyListEntry(tag: 't', value: 'cherry', private: true), ], ); // With explicit relay and signer final broadcast = await lists.add( listName: 'fruits', entries: const [AppendOnlyListEntry(tag: 't', value: 'date')], relays: ['wss://relay.example.com'], signer: customSigner, ); ``` -------------------------------- ### Examples for getList Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Usage examples for reading from the projection, forcing a refresh, and decrypting entries. ```dart // Read from projection (offline, fast) var state = await lists.getList( pubkey: 'alice', listName: 'fruits', ); // Force refresh from relays state = await lists.getList( pubkey: 'alice', listName: 'fruits', forceRefresh: true, ); // Decrypt private entries if signer is available state = await lists.getList( pubkey: 'alice', listName: 'fruits', signer: customSigner, ); ``` -------------------------------- ### Example for watchList Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Usage example for subscribing to list updates and canceling the subscription. ```dart final subscription = lists.watchList( pubkey: 'alice', listName: 'fruits', ).listen((state) { print('State updated: ${state.entries}'); }); // Later, cancel the subscription await subscription.cancel(); ``` -------------------------------- ### Usage example for remove() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Example demonstrating the removal of entries from a list based on tag and value pairs. ```dart await lists.remove( listName: 'fruits', entries: const [ AppendOnlyListEntry(tag: 't', value: 'banana'), ], ); ``` -------------------------------- ### Initialize and manage projection state Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/ProjectionStore.md Setup the database connection and perform basic load and save operations for a specific list. ```dart import 'package:ndk/ndk.dart'; import 'package:nip_append_only_list/nip_append_only_list.dart'; import 'package:sembast/sembast_io.dart'; // Initialize projection store final projectionDb = await databaseFactoryIo.openDatabase('projection.db'); final projection = ProjectionStore(projectionDb); // Load existing state or empty var state = await projection.load(pubkey: 'alice', listName: 'fruits'); // Persist updated state await projection.save(state); ``` -------------------------------- ### Creating Entries for Lists Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListEntry.md Example of creating a list of entries to be added to an append-only list. ```dart const entries = [ AppendOnlyListEntry(tag: 't', value: 'apple'), AppendOnlyListEntry(tag: 't', value: 'banana'), AppendOnlyListEntry(tag: 't', value: 'cherry', private: true), // encrypted ]; await lists.add(listName: 'fruits', entries: entries); ``` -------------------------------- ### Usage Example Source: https://github.com/nogringo/nip-append-only-list/blob/main/README.md Example demonstrating how to use the AppendOnlyLists class to add, remove, read, and watch list entries. ```dart import 'package:broadcast_queue_shim_for_ndk/broadcast_queue_shim_for_ndk.dart'; import 'package:ndk/ndk.dart'; import 'package:nip_append_only_list/nip_append_only_list.dart'; import 'package:sembast/sembast_io.dart'; // or sembast_web on web final ndk = Ndk(NdkConfig( eventVerifier: Bip340EventVerifier(), cache: await SembastCacheManager.create(databasePath: 'data'), )); final outboxDb = await databaseFactoryIo.openDatabase('outbox.db'); final projectionDb = await databaseFactoryIo.openDatabase('projection.db'); final outbox = OfflineBroadcast.withNdk(ndk, db: outboxDb); outbox.start(); final lists = AppendOnlyLists( ndk: ndk, outbox: outbox, projection: ProjectionStore(projectionDb), // Optional: cap the size of consolidation events so relays don't // reject huge fresh-Adds or deletion bundles. Default 32 KB. // maxEventBytes: 32 * 1024, ); // Log an account into NDK so write methods pick up its signer // automatically. Alternatives: `ndk.accounts.loginExternalSigner(...)` // for a NIP-46 bunker / Amber / NIP-07 signer, or simply pass `signer:` // explicitly on every call. ndk.accounts.loginPrivateKey(pubkey: myPubkey, privkey: myPrivkey); // Add three entries to a list named "fruits". // `relays` is optional; when omitted, the author's NIP-65 write relays // are resolved automatically via NDK. await lists.add( listName: 'fruits', entries: const [ AppendOnlyListEntry(tag: 't', value: 'apple'), AppendOnlyListEntry(tag: 't', value: 'banana'), AppendOnlyListEntry(tag: 't', value: 'cherry', private: true), // encrypted ], ); // Remove one. await lists.remove( listName: 'fruits', entries: const [AppendOnlyListEntry(tag: 't', value: 'banana')], ); // Read - works offline, no signer needed for entries already projected. final state = await lists.getList( pubkey: myPubkey, listName: 'fruits', ); print(state.entries); // {apple, cherry (private)} // Reactive view (initial snapshot + live updates). final sub = lists.watchList( pubkey: myPubkey, listName: 'fruits', ).listen((s) => print(s.entries)); // Periodically compact: emits a fresh Add capturing current state and // NIP-09-deletes the superseded events. await lists.consolidate(listName: 'fruits'); await sub.cancel(); await lists.dispose(); ``` -------------------------------- ### copyWith Example Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListEntry.md Demonstrates creating a new entry with modified fields using copyWith. ```dart final entry = AppendOnlyListEntry(tag: 't', value: 'apple'); final privateVersion = entry.copyWith(private: true); // privateVersion.tag == 't' // privateVersion.value == 'apple' // privateVersion.private == true ``` -------------------------------- ### Building Append-Only List Events Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/Filters-and-Event-Builders.md Examples demonstrating how to build events for adding or removing list entries, including handling private entries and custom timestamps. ```dart import 'package:ndk/ndk.dart'; import 'package:nip_append_only_list/nip_append_only_list.dart'; // Build an Add with public entries final unsigned = await buildAppendOnlyEvent( op: AppendOnlyListOp.add, listName: 'fruits', entries: const [ AppendOnlyListEntry(tag: 't', value: 'apple'), AppendOnlyListEntry(tag: 't', value: 'banana'), ], pubkey: 'alice', ); // Sign and broadcast final signed = await signer.sign(unsigned); await ndk.cache.saveEvent(signed); // Build an Add with mixed public and private entries final mixed = await buildAppendOnlyEvent( op: AppendOnlyListOp.add, listName: 'fruits', entries: const [ AppendOnlyListEntry(tag: 't', value: 'apple'), AppendOnlyListEntry(tag: 't', value: 'cherry', private: true), ], pubkey: 'alice', signer: signer, // required for private entries ); // Build a Remove (privacy flag ignored) final removal = await buildAppendOnlyEvent( op: AppendOnlyListOp.remove, listName: 'fruits', entries: const [ AppendOnlyListEntry(tag: 't', value: 'apple'), ], pubkey: 'alice', ); // Specify a custom timestamp final custom = await buildAppendOnlyEvent( op: AppendOnlyListOp.add, listName: 'fruits', entries: const [AppendOnlyListEntry(tag: 't', value: 'date')], pubkey: 'alice', createdAt: 1234567890, ); ``` -------------------------------- ### Parsing AppendOnlyListEvent Examples Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListEvent.md Demonstrates parsing raw events with and without decrypted content. ```dart import 'package:ndk/ndk.dart'; import 'package:nip_append_only_list/nip_append_only_list.dart'; // Parse without decrypted content final rawEvent = Nip01Event( kind: 1990, pubKey: 'abc123...', tags: [['d', 'fruits'], ['t', 'apple'], ['t', 'banana']], content: '', // or encrypted content createdAt: 1234567890, ); final parsed = AppendOnlyListEvent.parse(rawEvent); if (parsed != null) { print('Op: ${parsed.op}'); print('List: ${parsed.listName}'); print('Entries: ${parsed.entries}'); } // Parse with decrypted private entries final plaintext = '[["t", "cherry"], ["t", "date"]]'; final parsed2 = AppendOnlyListEvent.parse(rawEvent, plaintext: plaintext); if (parsed2 != null) { // Now parsed2.entries includes the private entries print('Entries: ${parsed2.entries}'); } ``` -------------------------------- ### Evaluate EntryStat membership Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/EntryStat.md Examples demonstrating how different timestamp combinations affect the isPresent result. ```dart // Entry was added at t=100, never removed final stat1 = EntryStat(lastAddAt: 100, lastRemoveAt: null); print(stat1.isPresent); // true // Entry was added at t=100, removed at t=200 final stat2 = EntryStat(lastAddAt: 100, lastRemoveAt: 200); print(stat2.isPresent); // false // Entry was removed at t=100, then re-added at t=200 final stat3 = EntryStat(lastAddAt: 200, lastRemoveAt: 100); print(stat3.isPresent); // true ``` -------------------------------- ### Equality Operator Example Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListEntry.md Shows that equality is based on tag and value, ignoring the private flag. ```dart final a = AppendOnlyListEntry(tag: 't', value: 'apple', private: false); final b = AppendOnlyListEntry(tag: 't', value: 'apple', private: true); assert(a == b); // true - private flag is ignored ``` -------------------------------- ### Creating a Modified Entry Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListEntry.md Example of using copyWith to change the privacy status of an existing entry. ```dart final original = AppendOnlyListEntry(tag: 'p', value: 'alice'); final asPrivate = original.copyWith(private: true); ``` -------------------------------- ### Retrieving Current Entries Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListState.md Example of accessing the computed set of currently present entries from a list state. ```dart final state = await lists.getList(pubkey: 'alice', listName: 'fruits'); print(state.entries); // Set of currently-present entries ``` -------------------------------- ### Initialize AppendOnlyLists Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/REFERENCE.md Sets up the NDK, database connections, and the main AppendOnlyLists controller. ```dart import 'package:broadcast_queue_shim_for_ndk/broadcast_queue_shim_for_ndk.dart'; import 'package:ndk/ndk.dart'; import 'package:nip_append_only_list/nip_append_only_list.dart'; import 'package:sembast/sembast_io.dart'; final ndk = Ndk(NdkConfig( eventVerifier: Bip340EventVerifier(), cache: await SembastCacheManager.create(databasePath: 'data'), )); final outboxDb = await databaseFactoryIo.openDatabase('outbox.db'); final projectionDb = await databaseFactoryIo.openDatabase('projection.db'); final outbox = OfflineBroadcast.withNdk(ndk, db: outboxDb); outbox.start(); final lists = AppendOnlyLists( ndk: ndk, outbox: outbox, projection: ProjectionStore(projectionDb), ); ndk.accounts.loginPrivateKey(pubkey: myPubkey, privkey: myPrivkey); ``` -------------------------------- ### Initialize ProjectionStore Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/ProjectionStore.md Instantiate the store using an existing sembast database instance. ```dart import 'package:nip_append_only_list/nip_append_only_list.dart'; import 'package:sembast/sembast_io.dart'; final db = await databaseFactoryIo.openDatabase('projection.db'); final projection = ProjectionStore(db); ``` -------------------------------- ### Initialize Sembast databases Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/configuration.md Configure file-backed databases for production or in-memory databases for testing environments. ```dart import 'package:sembast/sembast_io.dart'; final projectionDb = await databaseFactoryIo.openDatabase('projection.db'); final outboxDb = await databaseFactoryIo.openDatabase('outbox.db'); ``` ```dart import 'package:sembast/sembast_memory.dart'; final projectionDb = await newDatabaseFactoryMemory().openDatabase('projection.db'); final outboxDb = await newDatabaseFactoryMemory().openDatabase('outbox.db'); ``` -------------------------------- ### Complete AppendOnlyLists workflow Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Demonstrates the full lifecycle from initialization and login to adding, watching, removing, and consolidating entries. ```dart import 'package:broadcast_queue_shim_for_ndk/broadcast_queue_shim_for_ndk.dart'; import 'package:ndk/ndk.dart'; import 'package:nip_append_only_list/nip_append_only_list.dart'; import 'package:sembast/sembast_io.dart'; // 1. Initialize final ndk = Ndk(NdkConfig( eventVerifier: Bip340EventVerifier(), cache: await SembastCacheManager.create(databasePath: 'data'), )); final outboxDb = await databaseFactoryIo.openDatabase('outbox.db'); final projectionDb = await databaseFactoryIo.openDatabase('projection.db'); final outbox = OfflineBroadcast.withNdk(ndk, db: outboxDb); outbox.start(); final lists = AppendOnlyLists( ndk: ndk, outbox: outbox, projection: ProjectionStore(projectionDb), ); // 2. Login ndk.accounts.loginPrivateKey(pubkey: myPubkey, privkey: myPrivkey); // 3. Add entries await lists.add( listName: 'fruits', entries: const [ AppendOnlyListEntry(tag: 't', value: 'apple'), AppendOnlyListEntry(tag: 't', value: 'banana', private: true), ], ); // 4. Watch for changes lists.watchList(pubkey: myPubkey, listName: 'fruits') .listen((state) { print('List changed: ${state.entries}'); }); // 5. Remove an entry await lists.remove( listName: 'fruits', entries: const [AppendOnlyListEntry(tag: 't', value: 'banana')], ); // 6. Consolidate await lists.consolidate(listName: 'fruits'); // 7. Cleanup await lists.dispose(); ``` -------------------------------- ### Initialize NDK instance Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/configuration.md Configures the NDK instance with a Bip340 event verifier and a cache manager. SembastCacheManager provides file-backed storage, while MemCacheManager is suitable for in-memory caching. ```dart import 'package:ndk/ndk.dart'; import 'package:nip_append_only_list/nip_append_only_list.dart'; final ndk = Ndk( NdkConfig( eventVerifier: Bip340EventVerifier(), // Required for signature verification cache: await SembastCacheManager.create( databasePath: 'data', // Or MemCacheManager for in-memory cache ), ), ); ``` -------------------------------- ### ProjectionStore Constructor Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/configuration.md Initializes the ProjectionStore with a Sembast database instance and an optional custom store name. ```APIDOC ## ProjectionStore(db, storeName) ### Description Constructs a ProjectionStore to handle database persistence for list projections. ### Parameters - **db** (Database) - Required - Sembast database instance. - **storeName** (String) - Optional - Name of the main sembast store (Default: 'append_only_list_projection'). ### Example ```dart final projection = ProjectionStore( projectionDb, storeName: 'my_custom_store_name', ); ``` ``` -------------------------------- ### Manual CRDT folding with EntryStat Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/EntryStat.md Demonstrates how to manually apply Add and Remove events to an EntryStat instance to track entry presence over time. ```dart var appleStats = const EntryStat(); // Receive Add event for apple at t=100 appleStats = appleStats.applyAdd(100, private: false); print(appleStats.isPresent); // true // Receive Remove event at t=200 appleStats = appleStats.applyRemove(200); print(appleStats.isPresent); // false // Receive concurrent Add at t=150 (from another device, delayed) // Since 150 < 200 (the Remove), apple stays removed appleStats = appleStats.applyAdd(150, private: false); print(appleStats.isPresent); // false // Receive Add at t=250 (re-add after removal) appleStats = appleStats.applyAdd(250, private: true); print(appleStats.isPresent); // true print(appleStats.privateOnLastAdd); // true ``` -------------------------------- ### Project Directory Structure Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/MANIFEST.md Visual representation of the project documentation file hierarchy. ```text /workspace/home/output/ ├── README.md ← Start here ├── INDEX.md ← Navigation guide ├── REFERENCE.md ← Architecture & overview ├── MANIFEST.md ← This file ├── types.md ← Type definitions ├── configuration.md ← Setup & options ├── errors.md ← Error handling └── api-reference/ ← Class documentation ├── AppendOnlyListEntry.md ├── AppendOnlyListEvent.md ├── AppendOnlyListState.md ├── EntryStat.md ├── AppendOnlyLists.md ├── ProjectionStore.md ├── AppendOnlyListOp-and-Kinds.md └── Filters-and-Event-Builders.md ``` -------------------------------- ### Configure private key login Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/configuration.md Authenticates a user using a hex-encoded public and private key pair. ```dart ndk.accounts.loginPrivateKey( pubkey: 'hex-pubkey-here', privkey: 'hex-privkey-here', ); ``` -------------------------------- ### Project Directory Structure Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/REFERENCE.md Visual representation of the library's file hierarchy and module responsibilities. ```text lib/ ├── nip_append_only_list.dart # Public exports ├── src/ │ ├── entry.dart # AppendOnlyListEntry │ ├── kinds.dart # AppendOnlyListOp, constants │ ├── state.dart # AppendOnlyListState, EntryStat │ ├── event_codec.dart # AppendOnlyListEvent, buildAppendOnlyEvent() │ ├── filters.dart # listFilter(), deletionFilter() │ ├── projection_store.dart # ProjectionStore │ └── append_only_lists.dart # AppendOnlyLists ``` -------------------------------- ### ProjectionStore Methods Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/ProjectionStore.md Methods for managing list states, tombstones, and decrypted plaintexts within the ProjectionStore. ```APIDOC ## ProjectionStore ### load(pubkey: String, listName: String) -> Future Loads the current state for a specific pubkey and list name. ### save(state: AppendOnlyListState) -> Future Persists the provided AppendOnlyListState. ### delete(pubkey: String, listName: String) -> Future Deletes the state associated with the given pubkey and list name. ### loadTombstones(pubkey: String, listName: String) -> Future> Retrieves the set of tombstones for a specific pubkey and list name. ### addTombstones(pubkey: String, listName: String, ids: Set) -> Future Adds a set of tombstone IDs for a specific pubkey and list name. ### loadDecryptedPlaintext(eventId: String) -> Future Retrieves the decrypted plaintext for a specific event ID. ### loadDecryptedPlaintexts(eventIds: Iterable) -> Future> Retrieves a map of decrypted plaintexts for a collection of event IDs. ### saveDecryptedPlaintext(eventId: String, plaintext: String) -> Future Saves the decrypted plaintext for a specific event ID. ### deleteDecryptedPlaintext(eventIds: Iterable) -> Future Deletes the decrypted plaintexts for the provided event IDs. ### update(pubkey: String, listName: String, mutator: Function) -> Future Updates the state for a specific pubkey and list name using a provided mutator function. ``` -------------------------------- ### Handle NIP-44 Encryption Failure Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/errors.md Validate signer initialization and NIP-44 support when encryption returns null. ```dart final cipher = await signer.encryptNip44( plaintext: plaintext, recipientPubKey: signer.getPublicKey(), ); if (cipher == null) { throw StateError('Encryption failed'); } ``` -------------------------------- ### watchList() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Streams state updates as new events arrive, emitting an initial snapshot followed by updates on changes. ```APIDOC ## watchList() ### Description Streams state updates as new events arrive. Emits an initial snapshot immediately, then re-emits on every relevant Add/Remove. ### Parameters - **pubkey** (String) - Required - The author's public key. - **listName** (String) - Required - The list name. - **signer** (EventSigner?) - Optional - Optional signer for decryption. - **relays** (List?) - Optional - Explicit relays to query. ### Returns A stream of AppendOnlyListState. ### Example ```dart final subscription = lists.watchList( pubkey: 'alice', listName: 'fruits' ).listen((state) { print('State updated: ${state.entries}'); }); ``` ``` -------------------------------- ### Usage of deletionFilter Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/Filters-and-Event-Builders.md Demonstrates fetching NIP-09 deletion events for an author and performing an incremental sync. ```dart import 'package:ndk/ndk.dart'; import 'package:nip_append_only_list/nip_append_only_list.dart'; // Fetch all NIP-09 deletions targeting append-only events final filter = deletionFilter(pubkey: 'alice'); final response = ndk.requests.query(filter: filter); // Incremental sync final lastDeletionCheckTime = 1234567890; final incrementalFilter = deletionFilter( pubkey: 'alice', since: lastDeletionCheckTime, ); ``` -------------------------------- ### watchList(pubkey, listName, [signer, relays]) Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/REFERENCE.md Streams state updates for an append-only list as new events arrive. ```APIDOC ## watchList(pubkey, listName, [signer, relays]) ### Description Streams state updates for an append-only list as new events arrive. ### Returns `Stream` ``` -------------------------------- ### Watch list with backfill Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Subscribes to list updates, receiving an initial snapshot followed by historical backfill and live updates. ```dart final subscription = lists.watchList( pubkey: 'alice', listName: 'fruits', ).listen((state) { // Initial snapshot from projection // Then paginated historical backfill // Then live updates as they arrive print('${state.entries.length} entries'); }); ``` -------------------------------- ### save() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/ProjectionStore.md Persists the given state to the store. ```APIDOC ## save() ### Description Persists the given state to the store for its (pubkey, listName) pair. ### Parameters - **state** (AppendOnlyListState) - Required - The state to persist. ``` -------------------------------- ### applyAdd(int createdAt, {required bool private}) Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/EntryStat.md Returns a new EntryStat with an Add event applied. Updates lastAddAt if the new timestamp is strictly later, and sets privateOnLastAdd to reflect the encryption style. ```APIDOC ## applyAdd(int createdAt, {required bool private}) ### Description Returns a new EntryStat with an Add event applied. Updates lastAddAt if the new timestamp is strictly later, and sets privateOnLastAdd to reflect the encryption style. ### Parameters - **createdAt** (int) - Required - The Add event's created_at timestamp in seconds since Unix epoch. - **private** (bool) - Required - Whether the Add was encrypted. ### Returns A new EntryStat object. ### Example ```dart var stat = const EntryStat(); stat = stat.applyAdd(100, private: false); ``` ``` -------------------------------- ### Configure ProjectionStore with custom store name Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/configuration.md Define a custom name for the Sembast store, which automatically generates associated tombstone and plaintext stores. ```dart final projection = ProjectionStore( projectionDb, storeName: 'my_custom_store_name', ); // Creates stores: my_custom_store_name, my_custom_store_name_tombstones, my_custom_store_name_plaintext ``` -------------------------------- ### AppendOnlyLists Constructor Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/configuration.md Initializes the AppendOnlyLists class with required NDK, outbox, and projection store dependencies, along with optional event size constraints. ```APIDOC ## AppendOnlyLists(ndk, outbox, projection, maxEventBytes) ### Description Constructs an instance of AppendOnlyLists to manage append-only list operations. ### Parameters - **ndk** (Ndk) - Required - NDK instance for cache management and signer resolution. - **outbox** (OfflineBroadcast) - Required - Durable outgoing-event queue. - **projection** (ProjectionStore) - Required - Sembast-backed cleartext projection store. - **maxEventBytes** (int) - Optional - Soft byte cap for events during consolidation (Default: 32 * 1024). ### Example ```dart final lists = AppendOnlyLists( ndk: ndk, outbox: outbox, projection: ProjectionStore(projectionDb), maxEventBytes: 64 * 1024, ); ``` ``` -------------------------------- ### Method Definitions Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListEntry.md Definitions for the copyWith, equality operator, and hashCode methods. ```dart AppendOnlyListEntry copyWith({ String? tag, String? value, bool? private, }); ``` ```dart @override bool operator ==(Object other); ``` ```dart @override int get hashCode; ``` -------------------------------- ### Usage of listFilter Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/Filters-and-Event-Builders.md Demonstrates fetching all events for a specific list and performing an incremental sync using a timestamp. ```dart import 'package:ndk/ndk.dart'; import 'package:nip_append_only_list/nip_append_only_list.dart'; // Fetch all events for alice's fruits list final filter = listFilter( pubkey: 'alice', listName: 'fruits', ); final response = ndk.requests.query(filter: filter); // Incremental sync: fetch only events newer than last known final lastSyncTime = 1234567890; final incrementalFilter = listFilter( pubkey: 'alice', listName: 'fruits', since: lastSyncTime, ); ``` -------------------------------- ### load() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/ProjectionStore.md Reads the persisted state for a specific author and list name. ```APIDOC ## load() ### Description Reads the persisted state for (pubkey, listName), or returns an empty state if no projection exists yet. ### Parameters - **pubkey** (String) - Required - The author's public key (hex). - **listName** (String) - Required - The list name. ### Returns - **AppendOnlyListState** - The persisted state or an empty state if not found. ``` -------------------------------- ### add() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Publishes a kind 1990 Add event to append entries to a specified list. ```APIDOC ## add(listName, entries, relays, signer) ### Description Publishes a kind 1990 Add event covering the given entries. This method supports both public and private (NIP-44 encrypted) entries. ### Parameters - **listName** (String) - Required - The list name. - **entries** (List) - Required - Entries to add. - **relays** (List?) - Optional - Explicit relays to use. If null, defaults to NIP-65 write relays. - **signer** (EventSigner?) - Optional - Signer to use. If omitted, uses the currently logged-in NDK account signer. ### Returns - **QueuedBroadcast** - A representation of the enqueued event. ``` -------------------------------- ### watchList() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Returns a stream that emits updates for a specific list, including initial snapshots, historical backfill, and live updates. ```APIDOC ## watchList(pubkey, listName) ### Description Subscribes to changes for a specific list. The stream provides the initial projection snapshot, followed by paginated historical backfill and live updates. ### Parameters - **pubkey** (String) - The public key of the list owner. - **listName** (String) - The name of the list to watch. ``` -------------------------------- ### getList Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListState.md Retrieves the current state of a list for a specific user and list name. ```APIDOC ## getList ### Description Retrieves the current state of an append-only list for a given public key and list name. ### Parameters - **pubkey** (String) - Required - The public key of the list owner. - **listName** (String) - Required - The name of the list to retrieve. ### Response - **state** (AppendOnlyListState) - The current state object containing entries and pending decryption event IDs. ``` -------------------------------- ### Resolve relays via NIP-65 Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/configuration.md Use NDK to automatically resolve relays based on the author's NIP-65 user relay list. ```dart // getList() and watchList() automatically resolve NIP-65 relays via NDK final state = await lists.getList(pubkey: 'alice', listName: 'fruits'); // If NIP-65 list not found, falls back to projection-only (no network) ``` -------------------------------- ### Inspect projection directly Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/REFERENCE.md Loads and prints the raw state from the projection store for debugging purposes. ```dart final state = await lists.projection.load(pubkey: 'alice', listName: 'fruits'); print('Entries: ${state.entries}'); print('Pending decryptions: ${state.pendingDecryptionEventIds}'); ``` -------------------------------- ### getList() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Retrieves the current state of a list, supporting offline-first access. ```APIDOC ## getList(pubkey, listName) ### Description Fetches the current state of a list. This method works offline using the local projection store. ### Parameters - **pubkey** (String) - The public key of the list owner. - **listName** (String) - The name of the list. ``` -------------------------------- ### consolidate(listName, [relays, signer]) Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/REFERENCE.md Compacts the list by publishing fresh Add events and applying NIP-09 deletions. ```APIDOC ## consolidate(listName, [relays, signer]) ### Description Compacts the list by publishing fresh Add events and applying NIP-09 deletions. ### Returns `Future` ``` -------------------------------- ### Building and signing append-only events Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/Filters-and-Event-Builders.md Construct an unsigned event using buildAppendOnlyEvent and sign it using an NDK signer. ```dart import 'package:ndk/ndk.dart'; import 'package:nip_append_only_list/nip_append_only_list.dart'; final signer = /* ... */; // Build the unsigned event final unsigned = await buildAppendOnlyEvent( op: AppendOnlyListOp.add, listName: 'fruits', entries: const [ AppendOnlyListEntry(tag: 't', value: 'apple'), AppendOnlyListEntry(tag: 't', value: 'secret', private: true), ], pubkey: signer.getPublicKey(), signer: signer, ); print('Event before signing: ${unsigned.toJson()}'); // Sign it final signed = await signer.sign(unsigned); print('Signed event id: ${signed.id}'); print('Event tags: ${signed.tags}'); print('Encrypted content: ${signed.content}'); ``` -------------------------------- ### Configure AppendOnlyLists with custom maxEventBytes Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/configuration.md Adjust the soft byte cap for events during consolidation to accommodate specific relay size limits. ```dart final lists = AppendOnlyLists( ndk: ndk, outbox: outbox, projection: ProjectionStore(projectionDb), maxEventBytes: 64 * 1024, // Increase to 64 KB for larger relays ); ``` -------------------------------- ### Merge event streams Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListState.md Demonstrates how multiple event sources can be folded into equivalent OR-Set states for idempotent merging. ```dart final cachedEvents = [/* events from NDK cache */]; final remoteEvents = [/* events freshly fetched from relay */]; // Both sources fold into the same OR-Set state final state1 = AppendOnlyListState.fromEvents( cachedEvents, listName: 'fruits', pubkey: 'alice', ); final state2 = AppendOnlyListState.fromEvents( remoteEvents, listName: 'fruits', pubkey: 'alice', ); // The two states are equivalent OR-Sets (idempotent merge) ``` -------------------------------- ### Resolve No Signer Available Error Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/errors.md Provide a signer explicitly or ensure an account is logged into the NDK instance. ```dart await lists.add( listName: 'fruits', entries: [...], signer: mySigner, ); ``` ```dart ndk.accounts.loginPrivateKey(pubkey: myPubkey, privkey: myPrivkey); await lists.add(listName: 'fruits', entries: [...]); ``` -------------------------------- ### loadTombstones() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/ProjectionStore.md Loads the set of NIP-09-tombstoned event ids. ```APIDOC ## loadTombstones() ### Description Loads the set of NIP-09-tombstoned event ids for (pubkey, listName). ### Parameters - **pubkey** (String) - Required - The author's public key. - **listName** (String) - Required - The list name. ### Returns - **Set** - A set of event ids in hex. ``` -------------------------------- ### getList() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Retrieves the current list state from the projection and raw event cache, with optional relay fallback. ```APIDOC ## getList() ### Description Returns the current list state from the projection and raw event cache. Falls back to a remote query when the local view is empty or forceRefresh is set. ### Parameters - **pubkey** (String) - Required - The author's public key. - **listName** (String) - Required - The list name. - **signer** (EventSigner?) - Optional - Optional signer for decryption. - **forceRefresh** (bool) - Optional - When true, always query relays even if the projection has entries. - **timeout** (Duration) - Optional - Relay query timeout (default: 5 seconds). - **relays** (List?) - Optional - Explicit relays to query. ### Returns An AppendOnlyListState representing the current list. ### Example ```dart var state = await lists.getList( pubkey: 'alice', listName: 'fruits', forceRefresh: true ); ``` ``` -------------------------------- ### add(listName, entries, [relays, signer]) Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/REFERENCE.md Publishes a kind 1990 Add event to the specified list. ```APIDOC ## add(listName, entries, [relays, signer]) ### Description Publishes a kind 1990 Add event to the specified list. ### Returns `Future` ``` -------------------------------- ### getList(pubkey, listName, [signer, forceRefresh, timeout, relays]) Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/REFERENCE.md Reads the current state of an append-only list from the projection and cache, with optional relay synchronization. ```APIDOC ## getList(pubkey, listName, [signer, forceRefresh, timeout, relays]) ### Description Reads the current state of an append-only list from the projection and cache, with optional relay synchronization. ### Returns `Future` ``` -------------------------------- ### dispose() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Closes every active watchList() stream. The injected outbox and ndk remain owned by the caller and are not disposed. ```APIDOC ## dispose() ### Description Closes every active `watchList()` stream. The injected `outbox` and `ndk` remain owned by the caller and are not disposed. ### Signature `Future dispose();` ### Example ```dart await lists.dispose(); ``` ``` -------------------------------- ### Define add() method signature Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md The signature for the add method used to publish kind 1990 events. ```dart Future add({ required String listName, required List entries, List? relays, EventSigner? signer, }); ``` -------------------------------- ### consolidate() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Consolidates the state of a specific list. ```APIDOC ## consolidate(listName) ### Description Performs a consolidation operation on the specified list. ### Parameters - **listName** (String) - The name of the list to consolidate. ``` -------------------------------- ### add Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Adds entries to an append-only list. ```APIDOC ## Future add({required String listName, required List entries, List? relays, EventSigner? signer}) ### Description Appends new entries to the specified list. ### Parameters - **listName** (String) - Required - The name of the list. - **entries** (List) - Required - The entries to add. - **relays** (List) - Optional - Relays to broadcast to. - **signer** (EventSigner) - Optional - The signer for the event. ``` -------------------------------- ### Trace event operations in Dart Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListEvent.md Inspect event operations and metadata such as creation time and encryption status. ```dart final events = [/* parsed AppendOnlyListEvent instances */]; for (final ev in events) { if (ev.op == AppendOnlyListOp.add) { print('Added: ${ev.entries.map((e) => e.value).join(", ")}'); } else { print('Removed: ${ev.entries.map((e) => e.value).join(", ")}'); } print('At: ${DateTime.fromMillisecondsSinceEpoch(ev.createdAt * 1000)}'); print('Has encrypted content: ${ev.hasEncryptedContent}'); } ``` -------------------------------- ### Dependencies Source: https://github.com/nogringo/nip-append-only-list/blob/main/README.md Dependencies required for the nip_append_only_list package. ```yaml dependencies: nip_append_only_list: ^0.1.0 ndk: ^0.8.4-dev.1 broadcast_queue_shim_for_ndk: ^0.2.0 sembast: ^3.8.7 ``` -------------------------------- ### consolidate Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyLists.md Compacts the list by emitting a fresh Add event and deleting superseded events. ```APIDOC ## consolidate ### Description Compacts the list by emitting a fresh Add capturing the current state and NIP-09 deletes every superseded event. Reduces relay storage and query cost for long-running lists. ### Parameters - **listName** (String) - Required - The list name. - **relays** (List?) - Optional - Explicit relays. - **signer** (EventSigner?) - Optional - Optional signer. ### Example ```dart await lists.consolidate(listName: 'fruits'); ``` ``` -------------------------------- ### Load Tombstones Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/ProjectionStore.md Retrieve the set of NIP-09 tombstoned event IDs for a specific list. ```dart final tombstones = await projection.loadTombstones( pubkey: 'alice', listName: 'fruits', ); print('Deleted events: ${tombstones.length}'); ``` -------------------------------- ### Resolve General Signer Capability Error Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/errors.md Verify the signer is initialized and capable of signing before performing write operations. ```dart if (signer.canSign()) { await lists.add(listName: 'fruits', entries: [...], signer: signer); } else { throw StateError('Signer not ready'); } ``` -------------------------------- ### Initialize empty AppendOnlyListState Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListState.md Factory method to create an empty state instance. Use this when initializing a new list before processing any events. ```dart factory AppendOnlyListState.empty({ required String listName, required String pubkey, }); ``` ```dart final state = AppendOnlyListState.empty( listName: 'fruits', pubkey: 'alice', ); ``` -------------------------------- ### Work with Event Kinds Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListOp-and-Kinds.md Access constants for event kinds and tags. ```dart // All append-only kinds print(appendOnlyKinds); // [1990, 1991] // Specific kinds print(kindAdd); // 1990 print(kindRemove); // 1991 // List identifier tag print(dTag); // 'd' ``` -------------------------------- ### ProjectionStore Class Definition Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/ProjectionStore.md Defines the interface for managing persistent list states and decrypted plaintexts in a sembast database. ```dart class ProjectionStore { ProjectionStore( Database db, {String storeName = 'append_only_list_projection'}, ); Future load({ required String pubkey, required String listName, }); Future save(AppendOnlyListState state); Future delete({ required String pubkey, required String listName, }); Future> loadTombstones({ required String pubkey, required String listName, }); Future addTombstones({ required String pubkey, required String listName, required Set ids, }); Future loadDecryptedPlaintext(String eventId); Future> loadDecryptedPlaintexts( Iterable eventIds, ); Future saveDecryptedPlaintext({ required String eventId, required String plaintext, }); Future deleteDecryptedPlaintext(Iterable eventIds); Future update({ required String pubkey, required String listName, required AppendOnlyListState Function(AppendOnlyListState current) mutator, }); } ``` -------------------------------- ### loadDecryptedPlaintexts() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/ProjectionStore.md Fetches all cached plaintexts for the given event IDs in a single query. ```APIDOC ## loadDecryptedPlaintexts(Iterable eventIds) ### Description Batch-lookup variant that fetches all cached plaintexts for the given event IDs in a single query. More efficient than calling loadDecryptedPlaintext() repeatedly. ### Parameters - **eventIds** (Iterable) - Required - Event IDs in hex. ### Returns A Map where keys are event IDs and values are plaintexts. Event IDs with no cached plaintext are absent from the result. ``` -------------------------------- ### Configure external signer Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/configuration.md Authenticates using an external signer such as Amber, a browser extension, or a NIP-46 bunker. ```dart final externalSigner = /* get from Amber, extension, or bunker */; ndk.accounts.loginExternalSigner(signer: externalSigner); ``` -------------------------------- ### Performing incremental synchronization Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/Filters-and-Event-Builders.md Track the last sync time to fetch only new events during subsequent queries. ```dart import 'package:ndk/ndk.dart'; import 'package:nip_append_only_list/nip_append_only_list.dart'; var lastSyncTime = 0; // Fetch all events initially var events = []; var response = ndk.requests.query( filter: listFilter( pubkey: 'alice', listName: 'fruits', ), ); await for (final event in response.stream) { events.add(event); if (event.createdAt > lastSyncTime) { lastSyncTime = event.createdAt; } } // Later, fetch only newer events response = ndk.requests.query( filter: listFilter( pubkey: 'alice', listName: 'fruits', since: lastSyncTime, ), ); await for (final event in response.stream) { events.add(event); if (event.createdAt > lastSyncTime) { lastSyncTime = event.createdAt; } } ``` -------------------------------- ### Load List State Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/ProjectionStore.md Retrieve the persisted state for a specific author and list name. ```dart final state = await projection.load( pubkey: 'alice', listName: 'fruits', ); print('Entries: ${state.entries.length}'); ``` -------------------------------- ### loadDecryptedPlaintext() Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/ProjectionStore.md Returns the previously-decrypted NIP-44 plaintext for a given event id. ```APIDOC ## loadDecryptedPlaintext() ### Description Returns the previously-decrypted NIP-44 plaintext for the given event id, or null if not found. ### Parameters - **eventId** (String) - Required - The event id in hex. ### Returns - **String?** - The plaintext string or null. ``` -------------------------------- ### Working with List State Source: https://github.com/nogringo/nip-append-only-list/blob/main/_autodocs/api-reference/AppendOnlyListEntry.md Iterating through entries retrieved from a list state. ```dart final state = await lists.getList(pubkey: myPubkey, listName: 'fruits'); for (final entry in state.entries) { print('${entry.tag}=${entry.value} (private: ${entry.private})'); } ```