You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
329 lines
25 KiB
329 lines
25 KiB
# AUDIT_OPENCODE
|
|
|
|
## Resumen ejecutivo
|
|
|
|
El ecosistema Active es un framework propio **sólido y bien diseñado** (8.2/10). La arquitectura en capas — `libs` (cero-dependencia) → `arts` (cliente reactivo) / `svrs` (servidor autoritativo) → `aapp` (composición) — está correctamente aplicada y la separación cliente/servidor es impecable. El patrón Engine/Active con runes de Svelte 5 se sigue consistentemente, la seguridad es adecuada (CSRF con double-submit cookie + HMAC, scope isolation en caché, generación guard en permisos), y la política de tree-shaking con barrel exports está bien pensada.
|
|
|
|
Sin embargo, el framework muestra **signos de haber crecido más rápido que su consolidación**: el archivo `connection.ts` tiene 865 líneas y merece ser partido, `engine-auth.ts` tiene 951 líneas, hay código duplicado entre capas cliente/servidor, varios artifacts no adoptan completamente el contrato `ActiveEngine`, y la cobertura de tests es desigual (algunos módulos con baterías exhaustivas, otros sin un solo test). La documentación de diseño (DESIGN_CONN.md) referencia archivos que ya no existen.
|
|
|
|
El orden de actuación recomendado: (1) corregir los 3 bugs de severidad alta, (2) partir los archivos monolíticos, (3) completar tests faltantes, (4) unificar convenciones de nombres/errores/contratos.
|
|
|
|
---
|
|
|
|
## Hallazgos críticos
|
|
|
|
### HC-1: `Http` engine nunca se libera en `ActiveApp.dispose()` — fuga de recursos
|
|
- **Archivo:** `src/arts/aapp/active-app.svelte.ts:110,264`
|
|
- **Severidad:** Alta | **Clasificación:** bug confirmado | **Esfuerzo:** Bajo (1 línea)
|
|
- **Explicación:** `createEngineHttp()` se construye en línea 110 pero `Http.dispose()` no aparece en el cascade de `dispose()` (líneas 264-286). Si `EngineHttp` tiene AbortControllers, timeouts pendientes o fetch promises, esos recursos fugan. El orden correcto es Cache → Timers → **Http** → Frontend → Dom → Formats → Storage → Lang → Logger.
|
|
- **Propuesta:** Añadir `Http.dispose()` entre `Timers.dispose()` y `teardownPersistence()`.
|
|
|
|
### HC-2: `connection.ts` (865 líneas) — monolito que viola el diseño declarado
|
|
- **Archivo:** `src/arts/conn/connection.ts`
|
|
- **Severidad:** Alta | **Clasificación:** refactor | **Esfuerzo:** Alto (4-6h)
|
|
- **Explicación:** El archivo contiene state machine, transport lifecycle, heartbeat, reconnect, auth, buffering, channels, session bridge y browser lifecycle. `DESIGN_CONN.md:440-457` declara explícitamente archivos separados (`reconnect.ts`, `heartbeat.ts`, `backpressure.ts`, `ack.ts`) que **no existen**. El diseño original se consolidó en un solo archivo, dificultando el mantenimiento y testing aislado.
|
|
- **Propuesta:** Extraer `reconnect.ts` (líneas 332-357, 670-710), `heartbeat.ts` (359-392), `buffer.ts` (431-456), `auth.ts` (504-541), `request.ts` (543-577). Mantener `connection.ts` como orquestador.
|
|
|
|
### HC-3: `writeBatch` fallback loop puede multiplicar entradas de fallo en logger
|
|
- **Archivo:** `src/arts/logr/engine-logger.ts:377`
|
|
- **Severidad:** Alta | **Clasificación:** bug confirmado | **Esfuerzo:** Bajo (30min)
|
|
- **Explicación:** Cuando `writeBatch` no está definido, `flushTransport` llama a `writeOne` por cada item en el buffer. Si el transport falla en cada `writeOne`, se crea una entrada sintética de fallo POR CADA ITEM. Sin `failureThrottleMs`, esto multiplica el volumen de logs catastróficamente.
|
|
- **Propuesta:** Registrar fallo a nivel de flush — si `writeOne` falla durante un flush batch, detener iteración y emitir una sola entrada de fallo para el batch.
|
|
|
|
### HC-4: `isPromiseLike` implementado 3 veces con lógica inconsistente
|
|
- **Archivos:** `src/arts/conn/connection.ts:128` vs `src/arts/sium/core/internals.ts:23` vs `src/libs/standard-schema.ts:72`
|
|
- **Severidad:** Alta | **Clasificación:** bug confirmado | **Esfuerzo:** Bajo (15min)
|
|
- **Explicación:** La versión en `connection.ts` usa `'then' in value` que retorna `true` para objetos como `{ then: 42 }` que NO son thenables, causando que `sendFrame` haga `await` de un no-promise. Las otras dos versiones usan `typeof value.then === 'function'` que es correcto. Tres implementaciones con firmas diferentes.
|
|
- **Propuesta:** Todas las implementaciones deben importar desde `$libs/standard-schema`. Eliminar las locales.
|
|
|
|
---
|
|
|
|
## Hallazgos medios
|
|
|
|
### HM-1: `Can.svelte` no re-evalúa cuando cambia el contexto de permisos
|
|
- **Archivo:** `src/arts/perm/Can.svelte:30-49`
|
|
- **Severidad:** Media | **Clasificación:** bug confirmado | **Esfuerzo:** Bajo
|
|
- **Explicación:** El `$effect` depende de `action`, `resource`, `context`, `optimistic` — pero NO del `permissions` context. Si se llama `setPermissionsContext()` después de montar `<Can/>`, el componente no re-evalúa.
|
|
- **Propuesta:** Leer `permissions.currentSnapshot.version` dentro del effect como dependencia reactiva.
|
|
|
|
### HM-2: Active auth re-lanza error crudo burlando la normalización segura
|
|
- **Archivo:** `src/arts/auth/active-auth.svelte.ts:185-186`
|
|
- **Severidad:** Media | **Clasificación:** bug confirmado / seguridad | **Esfuerzo:** Bajo
|
|
- **Explicación:** `catch (error) { lastError = normalizeClientError(error); throw error; }` — re-lanza el error original, que puede contener stack traces o datos internos. Si el caller captura directamente en vez de leer `Auth.lastError`, recibe el error inseguro.
|
|
- **Propuesta:** Lanzar `AuthInvalidResponseError` o `AuthRequestFailedError` con el mensaje normalizado, no el error original.
|
|
|
|
### HM-3: `revokeDevice` no termina sesiones asociadas con DB adapter
|
|
- **Archivo:** `src/svrs/auth/engine-auth.ts:513-532`
|
|
- **Severidad:** Media | **Clasificación:** bug confirmado / seguridad | **Esfuerzo:** Medio
|
|
- **Explicación:** `revokeDevice()` en el adapter de memoria sí revoca session bindings, pero el DB adapter (`db.ts:108-112`) solo actualiza el registro del dispositivo — las sesiones bindings quedan activas. Un dispositivo revocado podría mantener sesiones válidas.
|
|
- **Propuesta:** Mover la lógica de revocación de session bindings al engine (no al adapter). Llamar `sess.end()` para sesiones asociadas al dispositivo revocado.
|
|
|
|
### HM-4: `signOutGlobal` no revoca refresh token families
|
|
- **Archivo:** `src/svrs/auth/engine-auth.ts:306-334`
|
|
- **Severidad:** Media | **Clasificación:** bug confirmado / seguridad | **Esfuerzo:** Bajo
|
|
- **Explicación:** `signOutGlobal` revoca session bindings pero NO las refresh token families del actor. Un refresh token emitido antes del logout global podría potencialmente rotar a nuevas sesiones. `refresh-rotation.ts` tiene `revokeRefreshFamily` pero no se llama.
|
|
- **Propuesta:** Añadir `store.revokeRefreshFamily()` durante `signOutGlobal`.
|
|
|
|
### HM-5: `mono-lang.svelte.ts` `register()` retorna tipo falseado
|
|
- **Archivo:** `src/arts/lang/mono-lang.svelte.ts:139-142`
|
|
- **Severidad:** Media | **Clasificación:** bug confirmado | **Esfuerzo:** Bajo
|
|
- **Explicación:** `register()` crea `ActiveLang<LangNode>` pero lo castea `as unknown as ActiveLang<LangNode & { [K in NS]: M }>`. La instancia retornada no tiene conocimiento real del namespace — `lang.t('shop.product')` devolvería el path literal, no una traducción.
|
|
- **Propuesta:** Hacer que mono-lang's `register` realmente mergee módulos en un schema interno, o tipar el retorno como `ActiveLang<LangNode>` sin pretensión de type safety.
|
|
|
|
### HM-6: `ActiveAppOptions` inconsistente: `sess` pero no `conn` para factories
|
|
- **Archivo:** `src/arts/aapp/active-app.svelte.ts:155-261`
|
|
- **Severidad:** Media | **Clasificación:** simplificación / coherencia | **Esfuerzo:** Medio
|
|
- **Explicación:** El constructor acepta `sess` como opción con `onSignedOut()`, pero `conn` (Connections) no tiene opción equivalente para inyectar configuración inicial. Esto fuerza a llamar `App.createActiveConnections()` sin poder preconfigurar. La asimetría con `sess`/`auth`/`perm` rompe el patrón de factories.
|
|
- **Propuesta:** Aceptar `connections?: Omit<ConnectionsOptions, 'timers' | 'logger'>` en `ActiveAppOptions`.
|
|
|
|
### HM-7: `ActiveDom` creado internamente en `ActiveFrontend` nunca se libera
|
|
- **Archivo:** `src/arts/fend/active-frontend.svelte.ts:82,224-229`
|
|
- **Severidad:** Media | **Clasificación:** bug confirmado | **Esfuerzo:** Bajo
|
|
- **Explicación:** Cuando `applyDom === true` (default) y no se pasa `dom`, se crea `ActiveDom` interno que adjunta un `resize` listener a `window`. `ActiveFrontend.dispose()` no llama a `dom.dispose()`, filtrando el listener hasta que se cierre la página.
|
|
- **Propuesta:** Guardar referencia al `dom` creado internamente y llamar `dom.dispose()` en el método `dispose()`.
|
|
|
|
### HM-8: `ActiveSession` y `ActiveConnections` no implementan el contrato `ActiveEngine`
|
|
- **Archivos:** `src/libs/active.ts`, `src/arts/sess/`, `src/arts/conn/`
|
|
- **Severidad:** Media | **Clasificación:** refactor / coherencia | **Esfuerzo:** Bajo
|
|
- **Explicación:** `ActiveEngine<TSnapshot, TError>` es implementado por `ActiveAuth`, `ActivePermissions`, `ActiveCache` pero NO por `ActiveSession` ni `ActiveConnections` — ambos tienen `loading`, `lastError`, `snapshot()`, `dispose()` y `onChange()`. El contrato está a medio adoptar.
|
|
- **Propuesta:** Extender `ActiveSession` y `ActiveConnections` con `ActiveEngine` o eliminar el contrato parcial y documentar que es solo para "network-augmented" artifacts.
|
|
|
|
### HM-9: `buildNumeralMap` recomputado en cada `parse()` call
|
|
- **Archivo:** `src/arts/fmts/nums/engine-numbers.ts:37-44,119-123`
|
|
- **Severidad:** Media | **Clasificación:** optimización | **Esfuerzo:** Bajo
|
|
- **Explicación:** Cada llamada a `parse()` invoca `buildNumeralMap(locale)` que crea `Intl.NumberFormat`, formatea un número constante y construye un `Map` iterando caracteres. Este mapa es constante por locale.
|
|
- **Propuesta:** Cachear el numeral map por locale, similar al `formatCache`.
|
|
|
|
### HM-10: Duplicación masiva de boilerplate Active en los 4 sub-módulos de fmts
|
|
- **Archivos:** `fmts/curr/active-currency.svelte.ts`, `fmts/dates/active-dates.svelte.ts`, `fmts/nums/active-numbers.svelte.ts`, `fmts/unts/active-units.svelte.ts`
|
|
- **Severidad:** Media | **Clasificación:** refactor | **Esfuerzo:** Medio
|
|
- **Explicación:** Cuatro archivos comparten ~80% de estructura idéntica: `version = $state(0)`, `SvelteSet` para listeners, `notifyPreferences()`, `syncLocale()`, `unsubscribeLocale`, `dispose()`. ~100 líneas cada uno con ~60 líneas de boilerplate.
|
|
- **Propuesta:** Crear helper genérico `createReactiveSubEngine<E>(engine, subs)` en `fmts/helpers.ts`. Cada wrapper bajaría a ~30 líneas.
|
|
|
|
### HM-11: `unref` pattern duplicado 3 veces
|
|
- **Archivos:** `src/arts/http/retry.ts:68`, `src/arts/http/timeout.ts:52,68`
|
|
- **Severidad:** Media | **Clasificación:** refactor | **Esfuerzo:** Bajo
|
|
- **Explicación:** `(id as unknown as { unref?: () => void }).unref?.()` aparece 3 veces.
|
|
- **Propuesta:** Extraer a `tryUnref(handle: unknown)` en `$libs/timers`.
|
|
|
|
---
|
|
|
|
## Hallazgos menores
|
|
|
|
### HL-1: `libs/times/index.ts` — módulo vacío (dead code)
|
|
- **Archivo:** `src/libs/times/index.ts`
|
|
- **Severidad:** Baja | **Clasificación:** bug confirmado | **Esfuerzo:** Bajo
|
|
- **Propuesta:** Poblar con utilidades de tiempo o eliminar el directorio y alias.
|
|
|
|
### HL-2: `resolveDir` duplicado entre `arts/fend/locale-defaults.ts` y `libs/dom/locale.ts`
|
|
- **Archivos:** `src/arts/fend/locale-defaults.ts:8-10`, `src/libs/dom/locale.ts:1-5`
|
|
- **Clasificación:** refactor | **Esfuerzo:** Bajo
|
|
- **Propuesta:** Mover `resolveDir` y `RTL_LOCALES` a `libs/dom/locale.ts`. Re-exportar desde fend.
|
|
|
|
### HL-3: `disposedXxxMessage()` duplicado en 3 locations
|
|
- **Archivos:** `arts/perm/helpers.ts:3-5`, `svrs/perm/helpers.ts:3-5`, `svrs/cach/helpers.ts:3-5`
|
|
- **Clasificación:** refactor | **Esfuerzo:** Bajo
|
|
- **Propuesta:** Extraer a `$libs/active` como `disposedMessage(artifact, method)`.
|
|
|
|
### HL-4: `isLangBranch` vive en `helpers.ts` pero pertenece a `guards.ts`
|
|
- **Archivo:** `src/arts/lang/helpers.ts:171-179`
|
|
- **Clasificación:** refactor | **Esfuerzo:** Bajo
|
|
- **Propuesta:** Mover a `guards.ts`.
|
|
|
|
### HL-5: `toError` helper duplicado
|
|
- **Archivos:** `src/arts/sess/engine-session.ts:792-794`
|
|
- **Clasificación:** refactor | **Esfuerzo:** Bajo
|
|
- **Propuesta:** Mover a `$libs/reactive/utils`.
|
|
|
|
### HL-6: `NodeJS.Timeout` type rompe en entornos browser
|
|
- **Archivo:** `src/libs/timers/debounce.ts:3`
|
|
- **Clasificación:** bug confirmado | **Esfuerzo:** Bajo
|
|
- **Propuesta:** Usar `ReturnType<typeof setTimeout>`.
|
|
|
|
### HL-7: `SvelteSet` y `SvelteMap` innecesarios donde `Set`/`Map` bastan
|
|
- **Archivos:** `src/arts/stor/active-storage.svelte.ts:46,115`
|
|
- **Clasificación:** optimización | **Esfuerzo:** Bajo
|
|
- **Explicación:** `listeners` y `userSubs` solo se iteran imperativamente (`.forEach`, `.values()`), nunca en `$derived` o template. La reactividad de SvelteSet/Map no se aprovecha.
|
|
- **Propuesta:** Reemplazar con `Set` y `Map` planos.
|
|
|
|
### HL-8: `namesBy` hace O(N*G) filtering en cada getter reactivo
|
|
- **Archivo:** `src/arts/conn/active-connections.svelte.ts:36-76`
|
|
- **Clasificación:** optimización | **Esfuerzo:** Bajo
|
|
- **Propuesta:** Precomputar arrays categorizados con un solo `$derived`.
|
|
|
|
### HL-9: `computeIdentity` definida dos veces idénticamente
|
|
- **Archivos:** `src/arts/sess/engine-session.ts:188-191`, `src/arts/sess/active-session.svelte.ts:31-34`
|
|
- **Clasificación:** refactor | **Esfuerzo:** Bajo
|
|
- **Propuesta:** El wrapper active debe delegar a `engine.identity` en vez de recomputar.
|
|
|
|
### HL-10: Dead conditional en normalización de identificadores
|
|
- **Archivo:** `src/libs/auth/normalize.ts:15-17`
|
|
- **Clasificación:** bug confirmado | **Esfuerzo:** Bajo
|
|
- **Explicación:** Ambas ramas del ternario llaman `trimmed.toLocaleLowerCase()`. El condicional está muerto.
|
|
- **Propuesta:** Eliminar condicional o aplicar normalización diferente por rama.
|
|
|
|
### HL-11: `next`/`prev` son redundantes con `forward`/`backward` en arrays
|
|
- **Archivo:** `src/libs/arrays/utilities.ts:89-169`
|
|
- **Clasificación:** refactor | **Esfuerzo:** Bajo
|
|
- **Propuesta:** Reimplementar `next`/`prev` como wrappers de `forward(array, index, 1, loop)`.
|
|
|
|
### HL-12: `libs/http/index.ts` y todas las barrels de `libs/*` usan `export *`
|
|
- **Archivos:** `src/libs/*/index.ts`
|
|
- **Severidad:** Baja | **Clasificación:** simplificación / coherencia
|
|
- **Explicación:** El `arts/README.md` afirma que "All barrels use named re-exports" — esto es falso para toda la capa `libs/`. Para libs de utilidades es aceptable, pero para `libs/auth` (400+ líneas de tipos), `libs/perm`, `libs/cach` penaliza el tree-shaking.
|
|
- **Propuesta:** Actualizar README para reflejar la realidad: "libs barrels usan `export *`; arts barrels usan named re-exports." Opcional: convertir las libs grandes a named re-exports.
|
|
|
|
### HL-13: `libs/numbers/utilities.ts` — parámetro confuso `numerator`
|
|
- **Archivo:** `src/libs/numbers/utilities.ts:24`
|
|
- **Clasificación:** refactor | **Esfuerzo:** Bajo
|
|
- **Propuesta:** Renombrar a `mod(value: number, modulus: number)`.
|
|
|
|
### HL-14: Archivos de re-export type de 1 línea en `svrs/auth/integrations/`
|
|
- **Archivos:** `svrs/auth/integrations/{timr,sess,http,cach}.ts` (1 línea cada uno)
|
|
- **Clasificación:** simplificación | **Esfuerzo:** Bajo
|
|
- **Propuesta:** Eliminar archivos intermedios. Re-exportar directamente desde `svrs/auth/index.ts`.
|
|
|
|
---
|
|
|
|
## Refactorizaciones recomendadas
|
|
|
|
| # | Descripción | Archivo(s) | Esfuerzo |
|
|
|---|-------------|-----------|----------|
|
|
| R1 | Partir `connection.ts` (865 líneas) en módulos separados | `src/arts/conn/connection.ts` | Alto |
|
|
| R2 | Partir `engine-auth.ts` (951 líneas) extrayendo password flow, session binding, OAuth | `src/svrs/auth/engine-auth.ts` | Alto |
|
|
| R3 | Extraer boilerplate Active de fmts en `createReactiveSubEngine()` | `src/arts/fmts/*/active-*.svelte.ts` | Medio |
|
|
| R4 | Extraer `getLocale`/`setLocale` duplicado en 4 engines fmts | `src/arts/fmts/*/engine-*.ts` | Medio |
|
|
| R5 | Unificar `resolveDir` + `Direction` en `libs/dom/locale.ts` | fend/locale-defaults.ts, libs/dom/locale.ts | Bajo |
|
|
| R6 | Extraer `disposedXxxMessage()` a helper compartido | perm/helpers.ts, cach/helpers.ts | Bajo |
|
|
| R7 | Mover `readField`, `toError`, `escapeId` a `libs/` | sess/engine-session.ts, adom/roving-focus-group | Bajo |
|
|
| R8 | Convertir `libs/auth/index.ts` de `export *` a named re-exports | `src/libs/auth/index.ts` | Medio |
|
|
| R9 | Actualizar `DESIGN_CONN.md` para reflejar la implementación real | `src/arts/conn/DESIGN_CONN.md` | Medio |
|
|
| R10 | Alinear `activeEngine` contract: extender `ActiveSession`/`ActiveConnections` | `src/libs/active.ts` | Bajo |
|
|
|
|
---
|
|
|
|
## Simplificaciones recomendadas
|
|
|
|
| # | Descripción | Archivo | Esfuerzo |
|
|
|---|-------------|---------|----------|
|
|
| S1 | Eliminar `close()` duplicado de `engine-connections` (alias de `closeConnection`) | `src/arts/conn/engine-connections.ts:163-165` | Bajo |
|
|
| S2 | Eliminar `FormatsLocaleSource` (type alias muerto de `LocaleSource`) | `src/arts/fmts/types.ts:10` | Bajo |
|
|
| S3 | Consolidar `AUTO_VALUE`/`AUTO_CURRENCY`/`AUTO_UNIT_SYSTEM` (todos son `'auto'`) | `fmts/consts.ts`, `curr/consts.ts`, `unts/consts.ts` | Bajo |
|
|
| S4 | Reemplazar `SvelteSet`/`SvelteMap` innecesarios con `Set`/`Map` | `active-storage.svelte.ts:46,115` | Bajo |
|
|
| S5 | Eliminar archivos de 1 línea en `svrs/auth/integrations/*` | `svrs/auth/integrations/` | Bajo |
|
|
| S6 | Eliminar `svrs/auth/context.ts` (usado solo en test page) | `svrs/auth/context.ts` | Bajo |
|
|
| S7 | `noop()` debería aceptar rest args para compatibilidad universal | `libs/funcs/noop.ts:4` | Bajo |
|
|
| S8 | Simplificar `subscribe()` delegando a `addTransport` en logger | `src/arts/logr/engine-logger.ts:543-551` | Bajo |
|
|
|
|
---
|
|
|
|
## Optimizaciones recomendadas
|
|
|
|
| # | Descripción | Archivo | Esfuerzo |
|
|
|---|-------------|---------|----------|
|
|
| O1 | Cachear `buildNumeralMap` por locale (recomputado en cada `parse()`) | `fmts/nums/engine-numbers.ts:119-123` | Bajo |
|
|
| O2 | Precomputar `namesBy` en un solo `$derived` en vez de 5 filtros O(N) | `conn/active-connections.svelte.ts:36-76` | Bajo |
|
|
| O3 | `getLogs` clona todas las entradas antes de filtrar — filtrar primero | `logr/engine-logger.ts:473-490` | Bajo |
|
|
| O4 | `Object.keys(globalContext).length > 0` aloca array en hot path | `logr/engine-logger.ts:194-199` | Bajo |
|
|
| O5 | `sameSnapshot` usa `JSON.stringify` en cada cambio externo — shortcut con `generation` | `sess/engine-session.ts:786-789` | Bajo |
|
|
| O6 | `refines.ts` `regex()` clona RegExp innecesariamente sin flags `g`/`y` | `sium/core/refines.ts:229` | Bajo |
|
|
| O7 | `cookieAdapter.get()` re-parsea `document.cookie` en cada lectura | `stor/adapters/cookie.ts:94-96` | Medio |
|
|
| O8 | Timers sin cancelar en `body-scroll-lock` durante HMR reloads | `adom/body-scroll-lock.svelte.ts:150-169` | Bajo |
|
|
| O9 | `$effect` sin debounce en `Can.svelte` para cambios rápidos de props | `perm/Can.svelte:30-48` | Bajo |
|
|
| O10 | `decisionKey()` llamada incluso para cache hits — diferir tras cache miss | `perm/client.ts:252-254` | Bajo |
|
|
|
|
---
|
|
|
|
## Incoherencias de arquitectura
|
|
|
|
1. **Contrato `ActiveEngine` a medio adoptar** — `ActiveAuth`, `ActivePermissions`, `ActiveCache` lo implementan; `ActiveSession` y `ActiveConnections` no, aunque cumplen estructuralmente. O se adopta universalmente o se elimina.
|
|
|
|
2. **Clases vs factories en `adom`** — `BodyScrollLock`, `DOMContext`, `RovingFocusGroup` son clases con `new`; el resto del ecosistema usa `createEngine*`/`createActive*`. Inconsistencia de API.
|
|
|
|
3. **Naming plural vs singular** — `createEngineTimers` (plural) vs `createEngineHttp` (singular). Solo `timr` usa plural.
|
|
|
|
4. **Errores: clases vs string-templates** — `timr`/`http`/`conn`/`perm` usan clases Error con type guards; `fmts` usa string factories. Inconsistente para `catch` programático.
|
|
|
|
5. **Patrón de errores `disposed`** — `CachDisposedError`, `PermDisposedError`, `AuthDisposedError` vs `STORAGE_ERRORS.DISPOSED` (string). Sin patrón unificado.
|
|
|
|
6. **`DESIGN_CONN.md` referencia archivos inexistentes** — `reconnect.ts`, `heartbeat.ts`, `backpressure.ts`, `ack.ts`, `presence.ts`, `app-integration.ts` no existen. El diseño se consolidó sin actualizar la documentación.
|
|
|
|
7. **`libs/times/` — alias en config pero directorio vacío** — el alias `$libs/times` resuelve a un `index.ts` de 0 bytes.
|
|
|
|
8. **`AappAlreadyCreatedError` no se usa para sesión** — `createActiveSession()` lanza `SessAlreadyCreatedError`, no `AappAlreadyCreatedError` como los demás factories.
|
|
|
|
9. **Sin `DESIGN_*.md` para http, fmts, stor** — solo `timr` y `conn` tienen documentos de diseño detallados.
|
|
|
|
---
|
|
|
|
## Tests faltantes
|
|
|
|
### Sin tests (crítico)
|
|
| Módulo | Archivos sin tests |
|
|
|--------|-------------------|
|
|
| `libs/timers` | `backoff.ts`, `debounce.ts` (0 tests) |
|
|
| `arts/conn` | `active-connections.svelte.ts` (sin archivo de test) |
|
|
| `arts/conn` | `websocket.ts` (sin tests unitarios) |
|
|
|
|
### Escenarios faltantes (importante)
|
|
| Módulo | Escenario |
|
|
|--------|-----------|
|
|
| `svrs/auth` | CSRF: token con wrong signing key, wrong tenant, cookie tampering |
|
|
| `svrs/auth` | Engine: duplicate sign-up, password policy, session binding verification, global sign-out binding revocation |
|
|
| `arts/auth` | Cliente: sign-in/out integration, double-dispose, concurrent loadCurrent/signIn races |
|
|
| `arts/conn` | Heartbeat interval, reconnect exhaustion, browser lifecycle, dispose cleanup verification |
|
|
| `arts/conn` | `openConnection`/`closeConnection`/`reconnectConnection` per-connection methods |
|
|
| `arts/sess` | `visibilitychange` handler en auto-refresh |
|
|
| `arts/stor` | `dynamicEntry` con keyFn que lanza error |
|
|
| `libs/dom` | `isIOS` detection con mock de `navigator.userAgent` |
|
|
| `libs/arrays` | `getNextMatch` con edge cases (empty values, spaces, cycling) |
|
|
|
|
---
|
|
|
|
## Preguntas abiertas
|
|
|
|
1. **¿Debe `ActiveEngine` ser contrato universal o solo para artifacts con side-effects?** — Actualmente a medio adoptar. O se extiende a Session/Connections o se documenta como específico de "network-augmented" artifacts.
|
|
|
|
2. **¿Mantener clases en `adom` o migrar a factories?** — `BodyScrollLock`, `DOMContext`, `RovingFocusGroup` usan `new`; el resto usa `create*()`. La inconsistencia actual confunde.
|
|
|
|
3. **¿Cuál es el plan para `libs/times/`?** — Directorio vacío con alias en config. ¿Se puebla con duration math, `delay()`, `sleep()` o se elimina?
|
|
|
|
4. **¿Nivel de madurez de OAuth y MFA?** — El README dice "no deben documentarse como production-ready". `verifyMfaChallenge` siempre lanza error. OAuth tiene incompatibilidad con DB adapter. ¿Roadmap?
|
|
|
|
5. **¿Estándar de idioma para documentación?** — `adom/README.md` está en español, `stor/README.md` en inglés. Sin estándar definido.
|
|
|
|
6. **¿Mover validación de sesión en SSR al engine?** — `readSessionFromCookies` no valida schema; depende del caller pasar por `adoptServer`. ¿Debería el helper ser más defensivo?
|
|
|
|
7. **¿Estrategia de barrels?** — El README dice "named re-exports" para todos los barrels pero `libs/*` usa `export *`. ¿Actualizar README o convertir libs?
|
|
|
|
---
|
|
|
|
## Veredicto
|
|
|
|
**El ecosistema Active es un framework sólido, bien diseñado y con fundamentos arquitectónicos excelentes.** La separación en capas, el patrón Engine/Active, la política de tree-shaking, el aislamiento de scope en caché y permisos, y la implementación de CSRF son de calidad profesional.
|
|
|
|
**Lo que frena la calidad hoy:**
|
|
|
|
1. **Deuda de consolidación** — Archivos monolíticos (`connection.ts` 865 líneas, `engine-auth.ts` 951 líneas) que contradicen su propio diseño documentado. La duplicación de boilerplate entre sub-módulos de fmts y entre capas cliente/servidor indica que el framework creció sin pausas de refactorización.
|
|
|
|
2. **Cobertura de tests desigual** — Algunos módulos tienen baterías exhaustivas (50 tests en `engine-timers.test.ts`, 622 líneas en `engine-http.test.ts`); otros tienen cero tests (`libs/timers`, `active-connections`, `websocket`). Las áreas sin tests son precisamente donde hay más bugs potenciales (conexiones, reconexión, heartbeats).
|
|
|
|
3. **Convenciones inconsistentes** — Nombres plural/singular, clases vs factories, errores clase vs string, contrato `ActiveEngine` a medio adoptar. Esto crea fricción para nuevos contribuidores y hace que el código parezca menos cohesionado de lo que realmente es.
|
|
|
|
**Orden de actuación recomendado:**
|
|
|
|
1. **Semana 1-2 — Corrección de bugs:** HC-1 (Http dispose), HC-3 (writeBatch loop), HC-4 (isPromiseLike), HM-1 (Can reactivity), HM-2 (auth error re-throw)
|
|
2. **Semana 3-4 — Refactors estructurales:** Partir `connection.ts`, extraer boilerplate fmts, añadir Http.dispose()
|
|
3. **Semana 5-6 — Seguridad:** HM-3 (revokeDevice DB), HM-4 (signOutGlobal refresh families), HM-6 (session fixation docs)
|
|
4. **Semana 7-8 — Tests:** Añadir tests para `libs/timers`, `active-connections`, heartbeat, CSRF edge cases
|
|
5. **Mes 2-3 — Unificación:** Adoptar `ActiveEngine` universalmente o eliminarlo, unificar naming (plural→singular en timr), estandarizar errores (clases everywhere), actualizar DESIGN_CONN.md
|
|
|
|
**Lo que está excepcionalmente bien:**
|
|
- Separación cliente/servidor: cero imports cruzados `$arts`↔`$svrs` (fuera de tests de integración)
|
|
- CSRF: double-submit cookie + HMAC-SHA256 + timing-safe comparison + `__Host-` prefix
|
|
- Scope isolation en caché: `SCOPE_ACTOR`, `SCOPE_TENANT`, `SCOPE_PERMISSION` con hash en key
|
|
- Generation guard en permisos: previene escritura de resultados stale en snapshots posteriores
|
|
- Sistema de timers deterministas con fake clock injection para testing
|
|
- Tree-shaking: separación `.svelte.ts` vs `.ts`, barrels con named re-exports en `arts/`
|
|
- Documentación de diseño: `DESIGN_TIMR.md` (1530 líneas) y `DESIGN_CONN.md` (1770 líneas) son ejemplares
|