API reference › @evolu/common › local‑first/Shared › syncStateToOwnerSyncStatus
function syncStateToOwnerSyncStatus(
state: SyncState | null,
name: string & Brand<"UrlSafeString"> & Brand<"Name">,
ownerId: string & Brand<"Id"> & Brand<"OwnerId">,
): OwnerSyncStatus;
Defined in: packages/common/src/local-first/Shared.ts:1028
Tells what an app shows about syncing an owner in a database: Error when a
relay has one, holding the newest failure of any relay or, without one, the
newest skipped change; otherwise the first of Syncing, Synced, and
Offline that a relay has, or NoRelays. Accepts null, the store's value
before the first snapshot. See OwnerSyncStatus for what to show.
The same status is the same object: statuses other than Error are shared
constants, and an Error status is the same object for the same error
object, which SyncStateDep.syncState keeps between snapshots while it
is unchanged. So compare statuses with ===.
Example
import {
assertEqual,
assertSame,
createId,
createUnknownError,
Millis,
testCreateDeps,
testName,
} from "@evolu/common";
import {
syncStateToOwnerSyncStatus,
testAppOwner,
type PendingSyncRoute,
type SyncConnection,
type SyncRoute,
type SyncState,
type SyncTransportId,
} from "@evolu/common/local-first";
const deps = testCreateDeps();
const primaryId = createId<"SyncTransport">(deps);
const backupId = createId<"SyncTransport">(deps);
const stateOf = (
connections: ReadonlyArray<SyncConnection>,
routes: ReadonlyArray<SyncRoute>,
): SyncState => ({
transports: connections.map((connection, index) => ({
type: "WebSocket",
id: index === 0 ? primaryId : backupId,
label: "wss://relay.example",
connection,
})),
tenants: [
{
type: "Active",
name: testName,
owners: [{ type: "Writable", ownerId: testAppOwner.id, routes }],
},
],
});
const pending = (transportId: SyncTransportId): PendingSyncRoute => ({
type: "Pending",
transportId,
failure: null,
skippedError: null,
completeAt: null,
lastSentAt: null,
lastReceivedAt: null,
});
const statusOf = (state: SyncState | null) =>
syncStateToOwnerSyncStatus(state, testName, testAppOwner.id);
// Before the first snapshot, nothing syncs the owner.
assertEqual(statusOf(null), { type: "NoRelays" });
// A first connection is syncing; a lost one is offline.
const connecting: SyncConnection = { type: "Connecting" };
assertEqual(statusOf(stateOf([connecting], [pending(primaryId)])), {
type: "Syncing",
});
const disconnected: SyncConnection = {
type: "Disconnected",
disconnectedAt: Millis.orThrow(2000),
openedAt: Millis.orThrow(1000),
error: null,
};
assertEqual(statusOf(stateOf([disconnected], [pending(primaryId)])), {
type: "Offline",
});
// A quota failure on one relay shows before a newer skipped change on
// another, because the app can act on it.
const quotaError = {
type: "ProtocolQuotaError",
ownerId: testAppOwner.id,
at: Millis.orThrow(3000),
} as const;
const open: SyncConnection = {
type: "Open",
openedAt: Millis.orThrow(3400),
error: null,
};
assertEqual(
statusOf(
stateOf(
[disconnected, open],
[
{ ...pending(primaryId), failure: quotaError },
{
type: "Settled",
transportId: backupId,
skippedError: {
type: "DecryptWithXChaCha20Poly1305Error",
error: createUnknownError(new Error("invalid tag")),
at: Millis.orThrow(4000),
},
completeAt: null,
lastSentAt: Millis.orThrow(3500),
lastReceivedAt: Millis.orThrow(4000),
},
],
),
),
{ type: "Error", error: quotaError },
);
// The same error gives the same status object.
const quotaState = stateOf(
[disconnected],
[{ ...pending(primaryId), failure: quotaError }],
);
assertSame(statusOf(quotaState), statusOf(quotaState));