From aa8a7403a57e4abb8c8a67b59cd42b5bdd8ce36d Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 29 Apr 2026 14:19:10 +0200 Subject: [PATCH] Extract storage defaults registry --- NEXT_STEPS.md | 1 + src/arts/stor/defaults-registry.ts | 53 ++++++++++++++++++++++++++++++ src/arts/stor/engine-storage.ts | 43 +++++++----------------- 3 files changed, 65 insertions(+), 32 deletions(-) create mode 100644 src/arts/stor/defaults-registry.ts diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md index 7411692..be8474f 100644 --- a/NEXT_STEPS.md +++ b/NEXT_STEPS.md @@ -15,6 +15,7 @@ Estado al cierre: - `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`. + - `arts/stor/engine-storage.ts` delega el registro de defaults conflictivos en `defaults-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. diff --git a/src/arts/stor/defaults-registry.ts b/src/arts/stor/defaults-registry.ts new file mode 100644 index 0000000..00a1b48 --- /dev/null +++ b/src/arts/stor/defaults-registry.ts @@ -0,0 +1,53 @@ +import { STORAGE_OP_READ } from './consts'; +import { STORAGE_ERRORS } from './errors'; +import type { StorageErrorContext } from './types'; + +export interface RegisterStorageDefaultInput { + readonly registryKey: string; + readonly key: string; + readonly fullKey: string; + readonly initialDefault: unknown; + readonly adapterName: string; + report(ctx: Omit, adapterName: string): void; +} + +export interface StorageDefaultsRegistry { + register(input: RegisterStorageDefaultInput): () => void; +} + +export function createStorageDefaultsRegistry(): StorageDefaultsRegistry { + const defaults = new Map(); + + function register(input: RegisterStorageDefaultInput): () => void { + const previous = defaults.get(input.registryKey); + if (previous !== undefined) { + try { + if (JSON.stringify(previous.value) !== JSON.stringify(input.initialDefault)) { + input.report( + { + key: input.key, + fullKey: input.fullKey, + op: STORAGE_OP_READ, + error: new Error(STORAGE_ERRORS.ENTRY_DEFAULTS_MISMATCH(input.key)) + }, + input.adapterName + ); + } + } catch { + /* circular default — skip the diagnostic */ + } + previous.refs += 1; + } else { + defaults.set(input.registryKey, { value: input.initialDefault, refs: 1 }); + } + + return () => { + const slot = defaults.get(input.registryKey); + if (slot === undefined) return; + slot.refs -= 1; + if (slot.refs <= 0) defaults.delete(input.registryKey); + }; + } + + return { register }; +} diff --git a/src/arts/stor/engine-storage.ts b/src/arts/stor/engine-storage.ts index 561ec16..cf6114b 100644 --- a/src/arts/stor/engine-storage.ts +++ b/src/arts/stor/engine-storage.ts @@ -1,5 +1,6 @@ import { createMemoryAdapter } from './adapters/memory'; import { createStorageAdapterRegistry } from './adapter-registry'; +import { createStorageDefaultsRegistry } from './defaults-registry'; import { DEFAULT_VERSION, ENTRY_CHANGE_SOURCE_LOCAL, @@ -53,12 +54,7 @@ export function createEngineStorage(options: EngineStorageOptions = {}): EngineS const onError = options.onError; const bus: EntryBus = createEntryBus(); const adapterRegistry = createStorageAdapterRegistry(bus); - - // First-write-wins registry for conflict diagnostics. Refcounted so the - // entry is forgotten once every live registration is disposed — avoids - // false positives when a key is re-created with a different default - // after the previous owner cleaned up. - const registeredDefaults = new Map(); + const defaultsRegistry = createStorageDefaultsRegistry(); const entryDisposers = new Set<() => void>(); // Track every entry created so `clear()` can wipe only what this storage // owns and notify the bus uniformly. `key` is the original (pre-namespace) @@ -114,27 +110,14 @@ export function createEngineStorage(options: EngineStorageOptions = {}): EngineS // 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 = adapterRegistry.defaultsKeyFor(entryAdapter, fullKey); - const previous = registeredDefaults.get(registryKey); - if (previous !== undefined) { - try { - if (JSON.stringify(previous.value) !== JSON.stringify(initialDefault)) { - report( - { - key, - fullKey, - op: STORAGE_OP_READ, - error: new Error(STORAGE_ERRORS.ENTRY_DEFAULTS_MISMATCH(key)) - }, - entryAdapter.name - ); - } - } catch { - /* circular default — skip the diagnostic */ - } - previous.refs += 1; - } else { - registeredDefaults.set(registryKey, { value: initialDefault, refs: 1 }); - } + const unregisterDefault = defaultsRegistry.register({ + registryKey, + key, + fullKey, + initialDefault, + adapterName: entryAdapter.name, + report + }); const isRaw = entryOptions.raw === true; const version = isRaw ? DEFAULT_VERSION : (entryOptions.version ?? DEFAULT_VERSION); @@ -320,11 +303,7 @@ export function createEngineStorage(options: EngineStorageOptions = {}): EngineS registeredEntries.add(registration); const dispose = (): void => { registeredEntries.delete(registration); - const slot = registeredDefaults.get(registryKey); - if (slot !== undefined) { - slot.refs -= 1; - if (slot.refs <= 0) registeredDefaults.delete(registryKey); - } + unregisterDefault(); localDisposers.splice(0).forEach((fn) => fn()); }; entryDisposers.add(dispose);