174 lines
10 KiB
Markdown
174 lines
10 KiB
Markdown
# Contratos de relaciones tipadas v1
|
|
|
|
`@metis/relationship-contracts`, en `packages/relationship-contracts`, define una
|
|
superficie común de **lectura**. No sustituye los agregados, relaciones, servicios,
|
|
comandos ni tablas actuales. El paso 2.3 registra el primer proveedor real: `assets:directory-membership`,
|
|
compuesto en el servidor con resolutores de Assets y Resources. Véase
|
|
[proveedor de relaciones tipadas](proveedor-de-relaciones-tipadas.md) para su uso,
|
|
autorización, SQL, pruebas y límites. No se añaden endpoints nuevos.
|
|
|
|
## Dependencias y entrada pública
|
|
|
|
El paquete exporta contratos y schemas desde su única entrada pública:
|
|
|
|
```ts
|
|
import type {
|
|
EntityReference,
|
|
RelationshipQueryService,
|
|
EntitySummaryResolver,
|
|
} from "@metis/relationship-contracts";
|
|
import { createRelationshipRegistry } from "@metis/relationship-contracts";
|
|
```
|
|
|
|
Un consumidor deberá declarar `@metis/relationship-contracts: workspace:*` en sus
|
|
propias dependencias cuando exista. Assets, Resources y el servidor consumen ya este paquete. Las pruebas del paquete consumen esa misma entrada pública.
|
|
|
|
Las únicas dependencias de ejecución son `@repo/rdx-utils` (el `Result` existente)
|
|
y Zod 4.3.6 (ya utilizado en el repositorio). Configuración TypeScript y tipos de
|
|
Node son dependencias de desarrollo. No hay nueva biblioteca externa ni dependencia
|
|
de React, HTTP, ORM, base de datos o implementaciones de módulos.
|
|
|
|
## Referencias e identificadores
|
|
|
|
`EntityReference` contiene `providerId`, `entityType`, `id` y `companyId`. Los dos
|
|
últimos son cadenas UUID de transporte: el schema aplica `z.uuid()`, como los DTO
|
|
actuales y `UniqueID.create`, y normaliza a minúsculas sin cambiar la identidad.
|
|
No se generan identidades nuevas aquí. Los futuros propietarios seguirán utilizando
|
|
`UniqueID` en dominio y UUID v7 para nuevas entidades.
|
|
|
|
Los identificadores de proveedor y los segmentos namespaced usan minúsculas,
|
|
números y guiones, comienzan con letra y no admiten espacios ni guiones terminales.
|
|
Ejemplos de forma: `assets:asset`, `resources:resource`, `assets:member-of-domain`.
|
|
Son ejemplos conceptuales, no tipos registrados. El prefijo de `entityType` debe
|
|
coincidir con `providerId`; el de `typeId`, con `ownerProviderId`. Los nombres
|
|
visibles, hostname, dirección IP y nombres de tabla no sustituyen UUID ni IDs de
|
|
contrato. El ámbito v1 es exclusivamente una compañía; un catálogo global conserva
|
|
sus contratos propios y no usa una compañía ficticia.
|
|
|
|
Los schemas son estrictos e infieren los tipos públicos mediante Zod. Referencias,
|
|
definiciones, páginas y resúmenes validados son inmutables, también en sus objetos
|
|
y arrays internos. Los valores construidos directamente en TypeScript deben seguir
|
|
validándose al cruzar una frontera externa.
|
|
|
|
## Definiciones y registro
|
|
|
|
`EndpointRule` declara tipo de entidad y capacidad opcional. Una capacidad es una
|
|
condición funcional del extremo, **no un permiso del operador**. El registro
|
|
comprueba su namespace de proveedor o su declaración explícita en el catálogo de
|
|
capacidades del tercer argumento; comprobar que una entidad ofrece esa capacidad corresponde
|
|
al propietario.
|
|
|
|
`RelationshipDefinition` declara propietario, extremos, etiquetas traducibles,
|
|
dirección, cardinalidad activa, representación, historial y modo de escritura:
|
|
|
|
- `directed`: origen y destino tienen papeles distintos. La consulta `outgoing`
|
|
ancla el origen, `incoming` el destino y `either` cualquiera de ambos.
|
|
- `symmetric`: ambos extremos tienen la misma regla y los mismos límites. Todos
|
|
los sentidos de consulta buscan las relaciones incidentes al ancla. La vista
|
|
mantiene sus extremos estables; no se invierten al consultar desde el destino.
|
|
- La cardinalidad admite enteros positivos o `null` (sin límite). Es metadato
|
|
declarativo: no crea restricciones SQL ni valida concurrencia o datos existentes.
|
|
- `record` referencia una relación persistida con UUID; `property` identifica
|
|
entidad propietaria y propiedad; `derived` usa una clave estable del proveedor.
|
|
Una derivación solo admite `read-only`. Una propiedad puede modificarse mediante
|
|
un comando específico del propietario, nunca por un CRUD genérico.
|
|
- `history` declara `none`, `lifecycle` o `revisions`; no implementa un historial.
|
|
`stateCode` pertenece al vocabulario del propietario, no a un enum global.
|
|
`roleCode` es un código semántico opcional; su vocabulario se valida en el
|
|
propietario. No es un contenedor arbitrario de metadatos.
|
|
|
|
`createRelationshipRegistry(providers, definitions, capabilities?)` devuelve el `Result` habitual.
|
|
Construye una copia profundamente inmutable con `contractVersion: 1`, listas de
|
|
proveedores, capacidades y definiciones. Puede construirse vacío. Rechaza definiciones inválidas,
|
|
propietarios incompatibles con su ID, IDs duplicados, proveedores duplicados y
|
|
namespaces no registrados de propietarios, extremos o capacidades.
|
|
|
|
Los errores de construcción son `RelationshipRegistryError`, subclase de `Error`,
|
|
con códigos `INVALID_REGISTRY`, `DUPLICATE_PROVIDER`, `DUPLICATE_RELATIONSHIP` y
|
|
`UNREGISTERED_PROVIDER`. Una definición inválida se rechaza antes de comprobar el
|
|
resto del catálogo. No se publica el detalle de datos inválidos en el error.
|
|
|
|
El composition root aporta explícitamente proveedores y definiciones al arrancar.
|
|
El registro solo contiene metadatos: no contiene instancias, repositorios, callbacks,
|
|
resolutores ni descubrimiento de plugins. Los servicios se inyectarán directamente,
|
|
según el [patrón de composición](composicion-de-modulos.md).
|
|
|
|
## Consultas y resolución por lotes
|
|
|
|
`RelationshipQueryService.list(query, context)` devuelve
|
|
`Promise<Result<RelationshipPage, RelationshipReadError>>`. La consulta exige tipo,
|
|
ancla, sentido, `includeEnded` y tamaño entre 1 y 100. Cursor opcional opaco, no
|
|
vacío y de hasta 2048 caracteres; `nextCursor: null` señala fin de recorrido.
|
|
El proveedor limita la página al tamaño solicitado, mantiene orden estable y evita
|
|
duplicar una relación simétrica o autorreferente en `either`. El cursor debe estar
|
|
vinculado a la consulta, compañía y autorización aplicables; el schema valida solo
|
|
su forma, no su autenticidad ni significado.
|
|
|
|
`validateRelationshipQuery(input, context, registry)` valida la estructura, el
|
|
ámbito de compañía, que el tipo esté registrado y que el ancla sea compatible con
|
|
el sentido solicitado. Devuelve `INVALID_QUERY` o `FORBIDDEN` sin detalles de
|
|
entidades. No consulta datos, capacidades ni autorizaciones.
|
|
|
|
`RelationshipOrigin`, `RelationshipView` y `RelationshipPage` representan resultados
|
|
sin nombres embebidos en las relaciones. El schema de vista impide extremos de
|
|
compañías diferentes y propietarios de propiedad de otra compañía. Los proveedores
|
|
siguen siendo responsables de comprobar definición, representación, identidad de la
|
|
propiedad, estado y ámbito de todas las filas respecto al contexto y la consulta.
|
|
El schema de página no sustituye esa validación contextual.
|
|
|
|
`EntitySummaryResolver.resolveMany(references, context)` resuelve un lote por
|
|
proveedor. Devuelve un resultado por referencia, en el mismo orden, conservando las
|
|
repeticiones. Debe evitar consultas por fila y validar ámbito y tipos soportados.
|
|
`EntitySummary` contiene referencia, nombre, subtítulo opcional y estado opcional
|
|
(código y clave de traducción). No incorpora React, iconos ni rutas del navegador.
|
|
|
|
`SummaryResolution` distingue `resolved` de `unavailable`. Esta última variante
|
|
solo contiene la referencia: no revela nombre ni distingue entidad inexistente de
|
|
entidad inaccesible. La falta de acceso a una entidad no debe provocar un resumen
|
|
parcial con información privada. Un fallo del lote completo puede devolver
|
|
`UNAVAILABLE`; los adaptadores deben gestionar y registrar su causa internamente.
|
|
|
|
`RelationshipReadError` extiende `Error` porque el `Result` del repositorio exige
|
|
`E extends Error`. Expone `INVALID_QUERY`, `FORBIDDEN` o `UNAVAILABLE`, sin mensajes
|
|
libres ni detalles internos. No se introduce otro formato de Result.
|
|
|
|
## Seguridad y límites
|
|
|
|
`RelationshipContext` contiene `operatorId`, `companyId` y `requestId`. Solo lo
|
|
construye el servidor a partir de una sesión autenticada y del ámbito autorizado.
|
|
No se acepta como parte del body del navegador. Su schema valida estructura, no
|
|
prueba autenticación, pertenencia o permisos; el tipado no es una credencial.
|
|
|
|
El proveedor debe autorizar la lectura de ambos extremos antes de exponer una
|
|
relación y filtrar todas las consultas por la compañía del contexto. Poder leer
|
|
el origen no autoriza leer el destino. El resolutor aplica además la autorización
|
|
de cada resumen; nunca utiliza la relación como permiso implícito. Las referencias
|
|
de otra compañía se rechazan, sin devolver relaciones ni resúmenes de esa compañía.
|
|
Los identificadores de cliente atendido no son el tenant del prestador de soporte.
|
|
|
|
Este paquete no añade ACL. El runtime del paso 2.3 valida la sesión vigente mediante
|
|
Identity y la compañía mediante Companies, antes de cada lectura. Es un acceso
|
|
general de operadores autenticados a compañías atendidas, sin ACL granular.
|
|
La fábrica interna de contextos y la autorización se describen en el documento del
|
|
proveedor; el schema por sí solo sigue sin ser una credencial. Los grupos de un
|
|
directorio externo no confieren autorizaciones internas de Metis Desk.
|
|
|
|
## Comprobación y siguiente paso
|
|
|
|
Las pruebas de contrato verifican referencias, namespaces, inmutabilidad,
|
|
definiciones, duplicados, proveedores, cardinalidades simétricas, orígenes,
|
|
compañías, sentidos, paginación, errores Result, lotes y entrada pública. Las
|
|
implementaciones dentro de pruebas son fixtures identificadas; no se exportan como
|
|
proveedores reales. También se comprueban los grafos de dependencias locales e importaciones internas.
|
|
El script usa el runner nativo de Node con `tsx` y `--test-isolation=none`; las
|
|
pruebas no alteran estado global y se ejecutan en el mismo proceso. En este entorno,
|
|
el modo con subprocesos solo informó el fichero como una prueba, sin enumerar los
|
|
casos; se verificó expresamente la ejecución de cada caso sin ese aislamiento.
|
|
|
|
Los pasos 2.1 y 2.2 incorporan dominios Windows y pertenencias, respectivamente.
|
|
El [paso 2.3](proveedor-de-relaciones-tipadas.md) adapta sus datos existentes al
|
|
contrato común con paginación SQL y resolución por lotes. La presentación y los
|
|
adaptadores HTTP del siguiente consumidor permanecen fuera de este paso. SMB
|
|
podrá utilizar los contratos mediante sus propios tipos y políticas, sin compartir
|
|
tablas ni comandos de escritura por obligación.
|