Releases: interop-alliance/was-react
Release list
v0.23.0
Changed
- The App Connect request vocabulary and the app-key credential issuer
(serializedAppUrl,issueAppKeyCredential, the credential's pinned wire
constants, the wallet-request types) are imported from
@interop/wallet-request0.1.0; wallet-core 0.70.0 no longer ships the
requestsubpath.@interop/wallet-corestays forkeyringandidentity.
v0.22.2
Changed
- The
rxdbpeer floor is^17.5.0. Under 17.4 the Dexie storage never evicted
a closed connection from its name cache, sologout({ wipe: true })followed
by a login in the same page reopened the replica on a closed Dexie instance
and failed withDatabaseClosedError(rxdb #8793, fixed in 17.5.0).
v0.22.1
Changed
-
createWasSyncPortreturns the client's port as is. The runtimeputMeta
probe and the feed-page cast are gone, since@interop/was-client0.54.0
types the port as what it implements (putMetarequired,Jsonbodies), so a
divergence at the seam is now a compile error. -
setLoggeralso installs the logger into@interop/was-sync, so the
replication driver's diagnostics (the controller core, conflict handling, push
and pull) arrive under the app's namespace. The driver's per-calllogoption
is gone with@interop/was-sync's adoption of the logging port. -
The descriptor seam imports (
EncryptionDescriptorCache,
EncryptionDescriptorSource,DescriptorRefreshPolicy,
createRefreshingEdvDocCipher) come from@interop/was-client/edv, their
owner since@interop/wallet-core0.69.0 stopped re-exporting them from
./descriptors. -
WasRemoteStore.declareCollectionIndexesreads the public collection's
description first and skips the PUT when the storedplaintext.indexes
already matches the configured list, like the encryption-descriptor and
blinded-index declarations, so a returning session writes nothing. A read that
fails still leads to the PUT. -
The collection-description PUT body is typed as the client's
CollectionDescriptionrather than an untyped record, so a drift from the
shape the server reads is a compile error.
Fixed
-
A public collection's equality indexes are declared under the collection
description'splaintextmember (plaintext: { indexes }), the shape
was-teaching-server0.26.0 reads (after@interop/storage-core0.10.0). A
top-levelindexesis no longer read by the server, so the bootstrap's
declaration was being accepted and dropped, and equality queries on a public
collection failed with a 400. -
test/node/conditionalWrites.test.tsfollows the opaqueETagcontract: the
assumed rows carry the ackedetag/metaEtag, which the push handler
echoes asIf-Match, instead of rebuilding a validator fromversion. The
dev server moves towas-teaching-server0.29.0, whoseETagcarries the
generation prefixparseEtagreads the revision from.
v0.22.0
Added
- The logging port.
setLogger(logger)on the package root installs the logger
every call site in this package emits through and returns the one installed
before it; the structuralLoggertype (debug,info,warn,error,
each(msg, data?)) is exported beside it. An app wires it once at bootstrap,
e.g.setLogger(createLogger('wr'))with@interop/logger.@interop/logger
is a devDependency only: the published artifact carries no reference to it.
Changed
- Every
console.*call site insrc/(39, across 11 files) emits through the
port. An unwired consumer keeps today's channel and level through the console
fallback, but not byte-identical output: messages carry a[was-react]
prefix, values previously interpolated into the message text (collection ids,
statuses, database names) now arrive in one trailingdataobject, and an
Error rides asdata.err. Anything matching console text by substring should
not anchor at position 0. - A failed wallet-presentation verification no longer logs the presentation
body. An App Connect response embeds the app-key credential and its seed,
which no log sink may receive; the per-check failures are still logged.
v0.21.0
Changed
- The WAS replication driver now lives in
@interop/was-syncand is a
dependency of this package. What moved: the changes-feed pull handler, the
conditional-write push handler, thereplicateRxCollectionwiring, the
feed-backed conflict re-read, the synced-document schema, the conflict-handler
seam with its last-write-wins default, the writer-id mint, and the controller
core.src/sync/is gone;docCipher.tsandwasSyncPort.tsmoved to
src/storage/, which is where this package's key handling stays. SyncController(src/storage/syncController.ts) is now a binding over the
package'screateSyncController. Its public shape is unchanged
(start({ remoteStore, localStore, onRemoteChange, onAuthError }),reSync,
a terminalstop), and so are the skip of an uncovered collection, the status
keying on the logical collection key, and the failed bring-up that flags every
collection and rethrows. One behavior is new: the periodic re-sync now skips
its tick while the browser reports itself offline, instead of firing into a
known-down network.storage/writerId.tsis a thin binding over the package's mint: it supplies
DEFAULT_STORAGE_KEY_PREFIXandlocalStorage.getWriterIdand
clearPersistedWriterIdkeep their names and their optional-prefix signature.isAuthErrorkeeps its name and is re-exported from@interop/was-sync/rxdb.
It is still exported from this package's root.SyncStatusis re-exported from@interop/was-client/sync, which now owns
the four-string vocabulary the replication driver and the wallets' own sync
engine both report. The Zustand status store,deriveSyncRollup, and
useSyncStatusare unchanged.- The "master" names became "primary" in the package:
MasterStateis
PrimaryState,MasterReadCacheisPrimaryReadCache, and
withFeedMasterReadiswithFeedPrimaryRead. RxDB's ownassumedMasterState
andrealMasterStatefields are RxDB's API and are untouched. - The replica schema is the package's merged one, which adds
createdBy. RxDB
hashes a schema and refuses to open a replica whose stored hash differs at the
sameversion, and it ships atversion: 0with no migration strategy. Every
existing local replica must therefore be cleared once: clear local data and
log in again, and the collections re-pull from WAS. Anything written only
locally and never synced is lost with it.
Removed
The root barrel no longer re-exports the driver. Every name below is gone from
@interop/was-react; import it from the package named beside it.
- From
@interop/was-sync:syncedDocSchema,makeLwwConflictHandler,
lwwFields, and the typesLwwFields,SyncCheckpoint,SyncedDoc,
WireDoc,WasSyncBasePort,WasSyncPort, andMasterState/
MasterReadCache(asPrimaryState/PrimaryReadCache). - From
@interop/was-sync/rxdb:createWasReplication,createPullHandler,
wireDocToRxDoc,createPushHandler, the typePushWriteAck, and
withFeedMasterRead(aswithFeedPrimaryRead). - From
@interop/was-client/sync:errorStatus,errorMessage,formatEtag,
isEncryptedEnvelope,isUnknownEpochError,WasSyncConflictError,
WasSyncAuthError, and the typeJson(also available from
@interop/was-sync). These were pass-throughs; the client owns them. - From
@interop/was-client/edv:hasKeyEpochs,epochRostersEqual, also
pass-throughs.
createDocCipher, createPlaintextDocCodec, createUnprovisionedDocCipher,
the type DocCipher, and createWasSyncPort stay exported from this package's
root: they hold this library's key handling and the client seam, which the
driver never touches.
Dependencies
@interop/was-sync^0.1.0, added.rxdb ^17is still a required peer:LocalStoreis exported from the root
entry and imports it. A replica-less app can drop it once that entry point is
split (WR-44).
Changed
- The last-write-wins comparison is
remotePayloadWinsfrom
@interop/social-core, andsrc/sync/lww.tsis deleted. Its stamp accessor
(lwwFields,LwwFields) moves tosrc/sync/types.tsand still ships from
the root barrel. This is a behavior change on the three sites that compare
outside conflict resolution: the anonymous-to-connected adopt, the
duplicate-id pick inhydrateSingleton, and the entity store's patch. On a
pair where exactly oneupdatedAtparses, the parseable side no longer wins;
the raw strings compare lexically. One rule now, the one every wallet's stored
data already converges under. - A
412-refused delete whose re-read body is unchanged is re-issued against
the current ETag. A locally created row pushed with a revision the server has
since moved past would otherwise be refused forever and stay live on the
server. Every other412is still a real conflict. bodiesEqualcompares JCS-canonicalized JSON. A stored body a host
re-serializes with a different key order no longer reads as a change, which is
what keeps the delete retry firing and stops a spuriousPUTon an immutable
content-addressed row.- The push handler and the sync controller's
isAuthErrormatch the port's
typed signals byerr.name, throughisSyncConflictError,isSyncAuthError,
andisUnknownEpochErrorfrom@interop/was-client/sync, never by
instanceof. An error raised by a second physical copy of the client is
classified the same as this copy's.err.statusis read off the matched value
as a plain property. - The server-managed
createdBycreator DID is carried across the sync layer:
it joinsOptionalBodyFields,WireDoc, andSyncedDoc, and rides the pull
mapping (live documents and tombstones) and the assembled conflict entry. The
RxDB schema does not declare it yet; that lands with the merged schema when
the driver moves into its own package. MasterState.deletedis optional. A feed-backed read sets it; a clientget
resolvesnullfor a tombstone and sets nothing, and the push handler reads
an absent flag asfalse.getWriterIdno longer adopts a value left under the pre-rename
<prefix>clientIdkey, andclearPersistedWriterIdno longer removes that
key. WherelocalStoragecannot answer,getWriterIdmints a fresh id per
call instead of remembering one for the process: a shared fallback stamped one
label into two accounts' histories in the same tab.docCipher.tsno longer declares its own unknown-epoch predicate or its own
base cipher shape.isUnknownEpochErrorships from@interop/was-client/sync
(still re-exported from the root barrel), andDocCipherextends the
client's, adding the requiredencryptUpdateand the optional blinded-index
applyMeta.- Dependency floors:
@interop/was-client^0.49.0,@interop/wallet-core
^0.67.0,@interop/webkms-client^14.7.5(matching wallet-core's).
@interop/social-coreandjson-canonicalizeare added.
Removed
- The root barrel no longer exports
remotePayloadWins. It is
@interop/social-core's name, and one name has one owner; import it from
there.
v0.20.1
Fixed
- A transient failure of a collection-description read (a dropped connection, a
5xx) is no longer taken for "no encryption descriptor".
WasRemoteStore.readCollectionEncryptionanswersundefinedonly for a
not-found response or a description with noencryptionmember; any other
failure is thrown, wrapped with the collection id. A login fails its connected
activation on it (falling back tolocalwith the anonymous replica intact
for a retry), a hot restore opens that collection fail-closed and still lands
connected, the sync bootstrap skips the collection for the session with a
warning, and the unknown-epoch refresh warns and leaves the cipher as it was. - A transient failure of a collection's
/metaread is no longer taken for "no
metadata".WasRemoteStore.readCollectionMetaanswersundefinedonly for a
not-found response or a backend without metadata support (501); any other
failure is thrown, wrapped with the collection id, and the sync bootstrap's
blinded-index schema install warns and skips that collection. - Neither read retries on its own: the HTTP client underneath already retries
transient status codes and network errors, so a second retry layer only
multiplied the attempts and the delay before a failure surfaced. - A session that loses the attach-time storage-context claim (two providers
booting at once, or a keyed remount overlapping the old provider's teardown)
now closes the replica it opened before failing, instead of leaving an open
RxDB database behind. StorageContext.detachStorenow releases the active storage-context pointer
when that context holds it, so every teardown (destroy(), logout,
clear-data, the connected activation's fallback tolocal) releases it.
Previously nothing did, and after a provider unmount the facades kept
resolving the retired context; they now throw until the next boot claims a
live one.- A connected activation that fails and whose fallback to
localalso fails now
surfaces the activation's own error; the fallback's is warned about.
v0.20.0
Changed
- BREAKING: The process-wide storage holders are replaced by a
session-scopedStorageContext(src/storage/storageContext.ts), created by
createAuthStoreand exposed asstorageContexton the auth store's state.
It owns the open replica, the remote store, the writer id, the sync status
store, and the re-hydrate timers, and itswhileAttachedis the one staleness
mechanism the change patches, the scheduled re-hydrates, andhasLocalData
run on. The app-facing facades (requireStore,hasStore,
requireRemoteStore,hasRemoteStore,requireWriterId,stampLww,
publicUrlFor, the entity-store verbs) now resolve the active context, and
opening a replica while another context still holds one throws (before the
database is opened) instead of clobbering it.createAuthStoreskips its
creation-time activation while another session is live, so a keyed provider
remount fails the new provider's boot rather than throwing during render. hasLocalData()keeps resolvingfalse(with a warning) when a count fails
against the attached replica, rather than rejecting into the login handler.- Tearing down a session now cancels the pending re-hydrates before draining the
sync controller, so a debounced re-hydrate cannot fire against a replica that
is about to close. - BREAKING: Removed
setLocalStore,clearLocalStore,setRemoteStore,
clearRemoteStore,setWriterId,clearWriterId,useSyncStatusStore,
hydrateAll,clearAllEntityStores,patchFromChange,scheduleRehydrate,
andcancelScheduledRehydrates. Test harnesses attach a store through
new StorageContext({ registry, writerId }).attachStore(store); the
re-hydrate verbs areStorageContextmethods; sync status is
storageContext.syncStatus(a vanilla zustand store,
createSyncStatusStore). - BREAKING:
SyncControllertakes asyncStatusstore in its constructor;
mergeAdoptedtakes thewriterIdexplicitly. getWriterIdmoved tosrc/storage/writerId.tsbeside the new
clearPersistedWriterId(localStorage only); the running session's id is
replaced byStorageContext.resetWriterId.issueSeedCredentialdelegates to wallet-core'sissueAppKeyCredential(the
issuer the wallet mints with) instead of re-implementing the app-key
credential's shape; its signature and return value are unchanged. Requires
@interop/wallet-core>= 0.66.0. The test teaching server is bumped to
0.25.1, which verifies theeddsa-jcs-2022zcap proofs newer wallet-core
releases sign with.
Added
- New exports:
StorageContext,activateStorageContext,
deactivateStorageContext,hasStorageContext,requireStorageContext,
createSyncStatusStore,clearPersistedWriterId, and theSyncStatusStore
type.
v0.19.0
Added
clearLocalData()now resolves with aLocalWipeReport, describing what was
removed and, on an engine that could not confirm a deletion, what stays
unverified.- New exports:
executeLocalWipeandsnapshotWipeTargets
(src/session/localWipe.ts) andclearWriterId
(src/storage/storageManager.ts), plus theLocalWipeReportand
WipeTargetstypes. - Test coverage confirming that conditional writes (
ifMatch/ifNoneMatch)
on encrypted collections now produce an RxDB conflict entry on a lost race,
rather than the silent last-write-wins the preconditions previously degraded
to on this class of collection. - Test coverage for connecting to a wallet account anchored by a standing unlock
credential with no enrolled durable clients: a deeper delegation chain, an
ephemeral, non-stable presentation holder, a clamped grant expiry, a mid-life
authorization failure routing toreconnect, and a key epoch this app cannot
open degrading a shared read to a warn-and-skip and a descriptor-less private
collection to its fail-closed cipher. A wallet-composed counterpart case now
exercises the same annex-shaped grant against@interop/wallet-core's real
request/response composition.
Fixed
clearLocalData()no longer orphans the anonymous replica: run from a
connected session, it used to remove only the open replica, discard the
anonymous seed, and mint a fresh identity, stranding the previous anonymous
replica under a database name nothing could derive again. It now snapshots
every database name before deleting anything and reaches both replicas.logout({ wipe: true })no longer leaves empty database shells behind: RxDB's
own removal clears a collection's table but leaves its IndexedDB database
standing, and the erase grade now deletes those shells too.- The writer id is now cleared by
clearLocalData(); it previously survived
every wipe grade.
Changed
- The logout and clear-data dialog copy is more precise about what each grade
erases: the logout dialog names the connected replica specifically, and the
clear-data dialog distinguishes the connected case (the synced copy survives)
from the local-only case (this browser is the only copy). - Upgraded
@interop/wallet-coreto ^0.56.0 and@interop/was-clientto
^0.44.5.
v0.18.1
Changed
hasKeyEpochsis now re-exported from@interop/was-client/edvinstead of
being implemented here, and the local store's descriptor-refresh comparison
uses that subpath'sepochRostersEqual(also re-exported). Requires
@interop/was-client>= 0.37.0.
v0.18.0
Added
- Documents written through the sync path now carry blinded
indexedentries:
the sync bootstrap reads each hmac-bearing private collection's stored/meta
(after declaring its blinded-index attributes) and installs the persisted
index schema on that collection's document cipher
(LocalStore.applyCollectionMeta), so app writes are findable by equality
queries. The installed schema survives cipher rebuilds (epoch rotations).
Documents sealed before the schema was installed remain unfindable until
rewritten. WasRemoteStore.readCollectionMetareads a collection's raw stored/meta
value.
Changed
- Upgraded
@interop/was-clientto 0.36.0 (adds theapplyMeta/ build-time
metasupport oncreateEdvDocCipherthat the above uses).