Skip to content

The builder

passnoteBuilder(appId:, salt:) (from passnote_flutter) returns a PassnoteBuilder with the production pieces installed; chain overrides, then await .open(). Each override may be set at most once.

final passnote = await passnoteBuilder(appId: 'shopping', salt: 'shopping:v1')
    .passphrase(words: 12)
    .compactionHorizon(const Duration(days: 60))
    .open();

The frozen surface and its guarantees are described in VERSIONING.md.

Pure Dart (no Flutter)

A plain Dart process uses Passnote.builder(appId:, salt:) from package:passnote after installing defaults once with PassnoteBuilder.installDefaults, filling every PassnoteDefaults slot with the production adapters from passnote_crypto and passnote_lan:

PassnoteBuilder.installDefaults(PassnoteDefaults(
  engine: (databaseId, collection, key) => SqliteCrdtTable(
      '$dataDir/$databaseId/$collection.sqlite', encryptionKey: key),
  keyStore: (appId) => FileKeyStore(File('$dataDir/$appId.keys.json')),
  transport: LanTransport.new,
  cipher: XChaChaPoly1305Cipher.new,
  deriveKey: deriveKey,
  roomTag: roomTag,
  mnemonicGenerator: (words) => generateMnemonic(words: words),
  validateMnemonic: isValidMnemonic,
  identity: (keyStore) => DeviceIdentity.load(keyStore: keyStore),
  pake: Spake2Pake.new,
  recoveryIdentity: (phrase, salt) =>
      DeviceIdentity.fromRecoveryPhrase(phrase, salt: salt),
));

There is no OS keychain in a bare process, so bring your own KeyStore. The runnable version of all of this — including an example-grade file-backed FileKeyStore and a generated per-field merge strategy in use — is examples/cli_logbook, a two-device terminal logbook. To skip the builder entirely, wire every dependency explicitly with passnoteLowLevel (see Ports).

Overrides

Method Default Purpose
.engine(factory) SQLite files (SQLCipher at rest) Storage per collection. MapCrdtTable.new selects in-memory, unpersisted.
.transport(factory) LanTransport (mDNS, native only) How peers are found and frames move. WebSocketTransport for web or explicit addresses; see Transports.
.keyStore(factory) OS keychain Where derived keys and the device identity persist.
.cipher(factory) XChaCha20-Poly1305 The authenticated cipher, built per key.
.identity(factory) X25519 + Ed25519 device identity The device keypair backing pairing and revocation.
.pake(factory) SPAKE2 (P-256, RFC 9382) The exchange behind the pairing code.
.recovery(enabled:) false Opt in to the recovery phrase APIs.
.passphrase(words:, strictness:) 24 words, warn Generated passphrase length; whether non-mnemonic passphrases are rejected.
.compactionHorizon(duration) 30 days How old a tombstone must be before compact removes it.
.maxInboundFramesPerSecond(n) 100 Per-peer inbound rate limit.

PassnoteConfig

passnoteLowLevel takes the same knobs as a PassnoteConfig:

Field Default Meaning
appId required Namespaces discovery and storage.
salt required Stable app-specific key-derivation salt ("myapp:v1"). Changing it invalidates every derived key.
passphrasePolicy 24 words, warn See .passphrase().
compactionHorizon 30 days See .compactionHorizon().
maxInboundFramesPerSecond 100 See .maxInboundFramesPerSecond().
snapshotInterval 30 minutes How often the disk engine writes its recovery snapshot; Duration.zero disables.
antiEntropyInterval 3 minutes How often the repair exchange runs; Duration.zero disables.
maxInFlightFrames 16 Send-window bound per peer.
maxClockDrift 10 minutes How far ahead of the local clock a received record may be stamped; documents beyond it are refused until the clock catches up.
recoveryEnabled false See .recovery().

Main handle operations

Member Purpose
openDatabase(id, passphrase:) Open (or create) a database; stores the derived key for reopenDatabase.
reopenDatabase(id) Reopen without the passphrase, using the stored key.
forget(id) Drop the stored key and stop syncing the database.
generatePassphrase() A fresh BIP39 word list per the policy.
startPairing(id) / pairWith(id, code) / cancelPairing() The pairing flow.
devices(id) / renameDevice / revokeDevice Roster management.
enableRecovery(id) / recoverFrom(id, phrase, passphrase:) The opt-in recovery phrase.
forceSync() Full exchange with every connected peer, now.
events PeerAppeared, PeerDisappeared, PeerIncompatible, StoreRecovered.
close() Close every database and stop the transport.