Skip to content

v0.31.0

Choose a tag to compare

@dmitrizagidulin dmitrizagidulin released this 29 Oct 16:28
· 7 commits to main since this release

Changed

  • restoreAppSession is removed from the root entry and peekAppSession is
    exported in its place: the one reader of the persisted session, which never
    wipes anything and leaves the expiry to the caller (isExpired).
  • RestoredAppSession is renamed AppSession (the persistAppSession input:
    the record plus the seed).
  • snapshotWipeTargets takes sessionStoreDbName and anonStoreDbName, and
    SeedStore carries the dbName it is bound to, so a wipe deletes an injected
    session store by its own name instead of assuming <dbName>-session.
  • WipeTargets carries sweepDbName in place of idbNamePrefixes. The sweep
    no longer matches a broadcast-channel store: LocalStore.removeDatabase
    removes single-instance, as init opens, so RxDB opens no channel beside a
    replica.
  • seedStoreDbName (src/identity/seedStore.ts) and isReplicaDbName beside
    dbNameForController name the seed-store and replica database shapes once,
    for the auth store that mints them and the sweep that matches them.

Fixed

  • The entity store's write verbs (insert, update, upsert) commit to the
    Map before the persist is awaited, and the persists of one id (remove
    included) run in call order, so a read-modify-write issued before the previous
    write has persisted merges onto what that one wrote instead of onto the
    pre-write value and dropping it, and two unawaited upserts of a new id no
    longer mint two rows. A persist that rejects puts the last persisted doc back,
    and a hydrate that lands mid-write keeps showing the write.
    defineDocumentApp's update and exportFile inherit this.

  • The clear-data sweep matches only the database names this library derives from
    its dbName (<dbName>-session, <dbName>-anon, <dbName>-<8 hex>, and the
    Dexie shells RxDB opens beside a replica), so clearing one app no longer wipes
    a sibling app on the same origin whose dbName extends it.

  • LocalStore.init closes the database it opened when addCollections refuses
    (a stored schema hash that no longer matches), instead of leaving it open with
    its collections counted toward RxDB's process-wide cap.

  • The adoption merge fills a missing updatedAt and a missing writerId each
    on its own. A payload carrying updatedAt but no writerId keeps its
    original instant instead of being re-stamped to the adoption time and
    outranking newer connected edits.

  • An epoch-roster entry that names this app but fails its key-wrap check
    (IntegrityError from @interop/was-client 0.99.1) is routed as a tamper
    signal: the local store records it as the collection's spent refusal, and the
    shared-collection reader surfaces it as itself rather than as "not a
    recipient".

  • A boot over a persisted session whose grants have expired lands reconnect
    over the connected replica, with the seed intact and no replication started,
    instead of wiping the session and landing an anonymous local replica with no
    reconnect banner. The replica opens on the cached descriptors alone, and
    reconnect() renews the grants, persists the record, and starts sync. When
    that expired restore fails with KeyUnwrapError or IntegrityError (a cached
    roster that no longer wraps a key to this app, or whose entry for it fails its
    key-wrap check), the record is cleared, so the session lands a clean local
    rather than re-running the failed bring-up on every reload; any other failure
    keeps the record for a reload or the reconnect to repair.

  • A singleton duplicate that another client tombstoned while a hydrate was
    reconciling it is no longer re-removed or warned about, and deleteEntity no
    longer rewrites a tombstone a pull landed under it. A winner tombstoned while
    its losers were being removed is warned about.

  • login resolves null instead of connecting when the session changed while
    the wallet popup was open (a provider unmount with no re-mount, a
    clearLocalData, a connectWithGrants). A sync-controller stop that throws
    no longer aborts the teardown, and destroy resets the session state even
    when its teardown throws.

  • logout clears the persisted session record before the anonymous replica
    re-opens, so a re-open that fails no longer leaves the wallet seed on disk for
    a reload to hot-restore. A failed re-open, in logout and in a connected
    activation's fallback, now lands local with no replica and the failure on
    error instead of keeping the previous status with no replica attached.

  • AdoptDialog swallows the rejection of the login({ adopt }) it starts. The
    store already records a failed login on useLogin().error, so a wallet
    without App Connect support or a failed activation no longer also escapes the
    dialog as an unhandled rejection.

  • LocalStore.setDescriptorSource re-arms the refresh guard, the in-flight
    refresh, and the recorded refusals together, and a refresh that outlives its
    source records nothing.

  • A refresh whose fetched roster excludes this app is recorded and surfaces as
    KeyUnwrapError; a refresh that fails on the wire is logged and left
    un-spent.

  • Per-collection cipher changes (the refresh, applyRemoteDescriptor,
    applyCollectionMeta) are serialized, and a descriptor that is an earlier
    head of the log than the held one is not swapped back in.

  • A public (plaintext) collection row whose body is an EDV envelope (an object
    jwe member) no longer breaks the collection. The plaintext codec raises it
    as IntegrityError, the same failure a mis-bound payload id is, so hydration
    skips the row with a warning and hydrates the rest, and the conflict handler
    settles a 412 on it by last-write-wins stamp instead of failing the
    replication cycle on every retry. Any grant holder can write such a body, and
    before this every hydrate of that collection rejected.

  • The sync bootstrap guards its per-collection hook. A cipher rebuild that
    throws KeyUnwrapError (a roster rotated off this app since the cached
    descriptor) now costs that one collection, which is warned about and left on
    the cipher it opened with, while every other collection replicates. Before,
    the throw rejected startWasSync as a whole and no collection replicated
    while the session stayed connected.

  • The adopted anonymous replica is removed before its seed is cleared. The
    database goes through the wipe executor (so the Dexie shells RxDB's removal
    leaves standing go with it), and the seed is cleared only when nothing failed.
    Before, the seed was cleared first, so a failed removal (another tab holding
    the database) orphaned the replica under a name no later run could derive.

  • The connect transition's replica swap opens only for an adopt: 'merge' login
    from local, the one case anonymous data is carried over. A write queued
    during a login from reconnect now rejects instead of waiting on the swap and
    landing in the newly connected account's replica as a create.

  • reconnect() runs its post-popup epilogue (controller stop, record persist,
    sync restart, connected transition) on the lifecycle chain and re-checks the
    session first. A logout, clearLocalData, or destroy completed while the
    re-grant popup was open is no longer undone when the popup answers: the
    renewed grants are discarded with a warning, nothing is persisted, and no sync
    or expiry watch starts over the torn-down session.

  • AudienceReader.pull() no longer reports a replay (or a listing read) cut by
    a denial of a request that left under a since-replaced zcap as
    snapshot: true. Such a cut pauses nothing but leaves the read partial; the
    pull now returns snapshot: false with the pages it did read, and the
    checkpoint stays at the last fully read page. A consumer applying the partial
    set as a snapshot would have dropped every post the cut kept it from reading.

  • hydrateSingleton tombstones a duplicate row with a conditional write on the
    revision it read, and deleteEntity removes through RxDB's incremental write
    queue, so a pull that updated or tombstoned the row after it was read (the
    singleton decrypts can wait on a descriptor refresh) no longer fails the call
    with a storage conflict. A duplicate whose row moved meanwhile is skipped with
    a warning and reconciled by the next hydrate rather than removed unweighed,
    and a winner whose row moved tombstones no loser at all.