Extract storage adapter registry

master
dev 5 months ago
parent 59cccd55a6
commit e6333d4c82

@ -14,6 +14,7 @@ Estado al cierre:
- `svrs/auth/adapters/memory-store.ts` queda como composition root de 39 lineas; la logica se reparte en stores internos de credentials, flows, linked accounts, sessions/devices, refresh y state/snapshot.
- `arts/perm/client.ts` delega transporte HTTP/JSON en `client-http.ts`; el cliente queda centrado en cache, snapshot y fallback.
- `arts/timr/engine-timers.ts` delega `TimerHandle` cancel/reschedule en `timer-handle.ts`.
- `arts/stor/engine-storage.ts` delega IDs de adapter, bus keys y suscripciones cross-tab en `adapter-registry.ts`.
- Integracion total ampliada: `Auth.signOut()` valida anonimizacion, invalidacion de `Permissions` y evento `Cache.invalidate`.
- Tanda focalizada verde: `npx vitest run src/arts/conn src/libs/cach src/arts/cach src/svrs/cach src/arts/auth src/svrs/auth src/libs/auth src/arts/aapp/test/ecosystem.integration.test.ts` -> 19 archivos, 82 tests.
- `/test/ecosystem` revisado en navegador: carga sin errores de consola, `ar` cambia a `rtl`, Formats se actualiza por locale, Perm cambia con rol `viewer`, Cach re-scopea por locale y Conn loopback publica/recibe.

@ -0,0 +1,51 @@
import { ENTRY_CHANGE_SOURCE_EXTERNAL } from './consts';
import type { EntryBus } from './entry-bus';
import type { SyncStorageAdapter } from './types';
export interface StorageAdapterRegistry {
busKeyFor(adapter: SyncStorageAdapter, fullKey: string): string;
defaultsKeyFor(adapter: SyncStorageAdapter, fullKey: string): string;
ensureSubscription(adapter: SyncStorageAdapter, syncTabs: boolean): void;
dispose(): void;
}
export function createStorageAdapterRegistry(bus: EntryBus): StorageAdapterRegistry {
const adapterIds = new WeakMap<SyncStorageAdapter, number>();
const adapterDisposers = new Map<number, () => void>();
let adapterIdSeq = 0;
function getAdapterId(target: SyncStorageAdapter): number {
let id = adapterIds.get(target);
if (id === undefined) {
adapterIdSeq += 1;
id = adapterIdSeq;
adapterIds.set(target, id);
}
return id;
}
function busKeyFor(target: SyncStorageAdapter, fullKey: string): string {
return `${getAdapterId(target)}\u0000${fullKey}`;
}
function defaultsKeyFor(target: SyncStorageAdapter, fullKey: string): string {
return `${getAdapterId(target)}:${fullKey}`;
}
function ensureSubscription(target: SyncStorageAdapter, syncTabs: boolean): void {
if (!syncTabs || !target.onChange) return;
const id = getAdapterId(target);
if (adapterDisposers.has(id)) return;
const detach = target.onChange((changedKey, newValue) => {
bus.publish(busKeyFor(target, changedKey), newValue, ENTRY_CHANGE_SOURCE_EXTERNAL);
});
adapterDisposers.set(id, detach);
}
function dispose(): void {
adapterDisposers.forEach((detach) => detach());
adapterDisposers.clear();
}
return { busKeyFor, defaultsKeyFor, ensureSubscription, dispose };
}

@ -1,7 +1,7 @@
import { createMemoryAdapter } from './adapters/memory';
import { createStorageAdapterRegistry } from './adapter-registry';
import {
DEFAULT_VERSION,
ENTRY_CHANGE_SOURCE_EXTERNAL,
ENTRY_CHANGE_SOURCE_LOCAL,
NAMESPACE_SEPARATOR,
STORAGE_OP_DESERIALIZE,
@ -52,8 +52,7 @@ export function createEngineStorage(options: EngineStorageOptions = {}): EngineS
const diagnostics = createStorageDiagnostics(options.logger);
const onError = options.onError;
const bus: EntryBus = createEntryBus();
const adapterIds = new WeakMap<SyncStorageAdapter, number>();
let adapterIdSeq = 0;
const adapterRegistry = createStorageAdapterRegistry(bus);
// First-write-wins registry for conflict diagnostics. Refcounted so the
// entry is forgotten once every live registration is disposed — avoids
@ -71,39 +70,6 @@ export function createEngineStorage(options: EngineStorageOptions = {}): EngineS
adapter: SyncStorageAdapter;
}>();
// One subscription per adapter for cross-tab events. Per-entry adapter
// overrides get their own channel so two backends using the same key don't
// leak events into each other.
const adapterDisposers = new Map<number, () => void>();
function getAdapterId(target: SyncStorageAdapter): number {
let id = adapterIds.get(target);
if (id === undefined) {
adapterIdSeq += 1;
id = adapterIdSeq;
adapterIds.set(target, id);
}
return id;
}
function busKeyFor(target: SyncStorageAdapter, fullKey: string): string {
return `${getAdapterId(target)}\u0000${fullKey}`;
}
function registryKeyFor(target: SyncStorageAdapter, fullKey: string): string {
return `${getAdapterId(target)}:${fullKey}`;
}
function ensureAdapterSubscription(target: SyncStorageAdapter, syncTabs: boolean): void {
if (!syncTabs || !target.onChange) return;
const id = getAdapterId(target);
if (adapterDisposers.has(id)) return;
const detach = target.onChange((changedKey, newValue) => {
bus.publish(busKeyFor(target, changedKey), newValue, ENTRY_CHANGE_SOURCE_EXTERNAL);
});
adapterDisposers.set(id, detach);
}
function report(ctx: Omit<StorageErrorContext, 'adapter'>, entryAdapterName: string): void {
const errorContext = { ...ctx, adapter: entryAdapterName };
emitStorageDiagnostic(diagnostics, STORAGE_DIAGNOSTIC_EVENTS.ERROR, errorContext);
@ -124,8 +90,8 @@ export function createEngineStorage(options: EngineStorageOptions = {}): EngineS
entryOptions.namespace === false ? undefined : (entryOptions.namespace ?? namespace);
const fullKey = entryNamespace ? `${entryNamespace}${NAMESPACE_SEPARATOR}${key}` : key;
const syncTabs = entryOptions.syncTabs ?? entryAdapter.onChange !== undefined;
const busKey = busKeyFor(entryAdapter, fullKey);
ensureAdapterSubscription(entryAdapter, syncTabs);
const busKey = adapterRegistry.busKeyFor(entryAdapter, fullKey);
adapterRegistry.ensureSubscription(entryAdapter, syncTabs);
// Disallow `undefined` defaults — the missing-vs-stored ambiguity is
// not worth carrying through the API surface.
@ -147,7 +113,7 @@ export function createEngineStorage(options: EngineStorageOptions = {}): EngineS
// Refcounted so the registry forgets the slot once the last entry on
// it is disposed; otherwise re-creating the same key with a different
// default would emit a stale warning even when nobody owns it anymore.
const registryKey = registryKeyFor(entryAdapter, fullKey);
const registryKey = adapterRegistry.defaultsKeyFor(entryAdapter, fullKey);
const previous = registeredDefaults.get(registryKey);
if (previous !== undefined) {
try {
@ -427,8 +393,7 @@ export function createEngineStorage(options: EngineStorageOptions = {}): EngineS
dispose() {
if (disposed) return;
disposed = true;
adapterDisposers.forEach((detach) => detach());
adapterDisposers.clear();
adapterRegistry.dispose();
entryDisposers.forEach((d) => d());
entryDisposers.clear();
bus.dispose();

Loading…
Cancel
Save

Powered by TurnKey Linux.