release(v4.3.0): browser persistence via @shade/storage-indexeddb
Ship an official IndexedDB-backed StorageProvider so browser-based Shade
consumers persist identity, prekeys, sessions, retired identities,
peer-verification state and stream-resume rows across tab refresh and
browser restart. Closes the gap that forced browser apps onto
storage:"memory" (regenerated identity each load, orphaned device
records server-side).
- New package @shade/storage-indexeddb (4.3.0): full StorageProvider
conformance, schema v1, idb-backed; bumpPeerIdentityVersion is wrapped
in a single readwrite IDB transaction (atomic, vs SQLite's
read-then-upsert race).
- @shade/sdk resolveStorage() accepts { type: 'indexeddb', dbName? } via
dynamic import (lazy, optional dep — same pattern as
@shade/storage-postgres). Named StorageSpec type now reused by
ResolvedConfig.
- Tests: 16 new tests in shade-storage-indexeddb (StorageProvider
surface + peer-verifications + full E2EE conversation surviving a
simulated tab reload). Run on fake-indexeddb.
- Lockstep version bump 4.2.1 → 4.3.0 across all 25 packages.
- Publish scripts updated to include the new package.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/sdk",
|
||||
"version": "4.2.1",
|
||||
"version": "4.3.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -17,11 +17,12 @@ export interface ShadeConfig {
|
||||
* - "memory" — in-memory only (lost on restart, good for tests)
|
||||
* - "sqlite:/path/to/file.db" — SQLite backend
|
||||
* - { type: 'postgres', url: 'postgres://...' } — PostgreSQL backend
|
||||
* - { type: 'indexeddb', dbName?: 'my-app' } — browser IndexedDB
|
||||
* - An explicit StorageProvider instance
|
||||
*
|
||||
* Default: "memory"
|
||||
*/
|
||||
storage?: string | StorageProvider | { type: 'postgres'; url: string };
|
||||
storage?: StorageSpec;
|
||||
|
||||
/**
|
||||
* Your address on the prekey server (e.g. "alice@example.com" or "device:abc123").
|
||||
@@ -96,9 +97,16 @@ export interface ShadeKTConfig {
|
||||
witnessMaxStored?: number;
|
||||
}
|
||||
|
||||
/** Acceptable shapes for `ShadeConfig.storage`. */
|
||||
export type StorageSpec =
|
||||
| string
|
||||
| StorageProvider
|
||||
| { type: 'postgres'; url: string }
|
||||
| { type: 'indexeddb'; dbName?: string };
|
||||
|
||||
export interface ResolvedConfig {
|
||||
prekeyServer: string;
|
||||
storage: string | StorageProvider | { type: 'postgres'; url: string };
|
||||
storage: StorageSpec;
|
||||
address?: string | undefined;
|
||||
autoReplenish: { min: number; target: number; intervalMs: number } | false;
|
||||
autoRotate: false | '1d' | '7d' | '30d' | '90d';
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
backupFromString,
|
||||
} from './backup.js';
|
||||
import { computeFingerprint, deserializeIdentityKeyPair } from '@shade/core';
|
||||
import type { ResolvedConfig } from './config.js';
|
||||
import type { ResolvedConfig, StorageSpec } from './config.js';
|
||||
import {
|
||||
ShadeControlChannel,
|
||||
ShadeTransferAuthenticator,
|
||||
@@ -1484,9 +1484,7 @@ class HttpEnvelopeTransport implements ControlEnvelopeTransport {
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────
|
||||
|
||||
async function resolveStorage(
|
||||
spec: string | StorageProvider | { type: 'postgres'; url: string },
|
||||
): Promise<StorageProvider> {
|
||||
async function resolveStorage(spec: StorageSpec): Promise<StorageProvider> {
|
||||
if (typeof spec === 'object' && 'getIdentityKeyPair' in spec) {
|
||||
return spec;
|
||||
}
|
||||
@@ -1512,6 +1510,18 @@ async function resolveStorage(
|
||||
return mod.PostgresStorage.create(spec.url);
|
||||
}
|
||||
|
||||
if (typeof spec === 'object' && spec.type === 'indexeddb') {
|
||||
// Dynamic import keeps @shade/storage-indexeddb optional — Node-only
|
||||
// consumers don't need to install a browser-only adapter.
|
||||
const moduleId = '@shade/storage-indexeddb';
|
||||
const mod = (await import(moduleId)) as {
|
||||
IndexedDBStorage: { create(opts: { dbName?: string }): Promise<StorageProvider> };
|
||||
};
|
||||
const opts: { dbName?: string } = {};
|
||||
if (spec.dbName !== undefined) opts.dbName = spec.dbName;
|
||||
return mod.IndexedDBStorage.create(opts);
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported storage spec: ${JSON.stringify(spec)}`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user