2026-10-21 09:25:23 +00:00
import type {
EntityReference ,
EntitySummaryResolver ,
RelationshipContext ,
RelationshipReadAccess ,
SummaryResolution ,
} from "@metis/relationship-contracts" ;
import {
EntityReferenceSchema ,
EntitySummarySchema ,
RelationshipContextSchema ,
RelationshipReadError ,
} from "@metis/relationship-contracts" ;
import { Result } from "@repo/rdx-utils" ;
import type { Sequelize } from "sequelize" ;
import { QueryTypes } from "sequelize" ;
import { z } from "zod/v4" ;
const RowSchema = z . object ( {
id : z.uuid ( ) ,
name : z.string ( ) . min ( 1 ) ,
active : z.union ( [ z . boolean ( ) , z . literal ( 0 ) , z . literal ( 1 ) ] ) . transform ( Boolean ) ,
2026-10-21 10:37:59 +00:00
address : z.string ( ) . min ( 1 ) ,
2026-10-21 09:25:23 +00:00
} ) ;
/** Una proyección por lote, sin hidratar inventarios ni cargar sus historiales. */
export class NetworkShareSummaryResolver implements EntitySummaryResolver {
readonly providerId = "resources" ;
readonly entityTypes = Object . freeze ( [ "resources:resource" ] as const ) ;
constructor (
private readonly database : Sequelize ,
private readonly access : RelationshipReadAccess
) { }
async resolveMany (
references : readonly EntityReference [ ] ,
context : RelationshipContext
) : Promise < Result < readonly SummaryResolution [ ] , RelationshipReadError > > {
if ( ! RelationshipContextSchema . safeParse ( context ) . success ) {
return Result . fail ( new RelationshipReadError ( "FORBIDDEN" ) ) ;
}
const authorized = await this . access . authorize ( context ) ;
if ( ! authorized . isSuccess ) {
return authorized ;
}
const parsed = z . array ( EntityReferenceSchema ) . max ( 100 ) . safeParse ( references ) ;
if ( ! parsed . success ) {
return Result . fail ( new RelationshipReadError ( "INVALID_QUERY" ) ) ;
}
const accepted = parsed . data . filter (
( reference ) = >
reference . companyId === context . companyId &&
reference . providerId === this . providerId &&
reference . entityType === "resources:resource"
) ;
const ids = [ . . . new Set ( accepted . map ( ( reference ) = > reference . id ) ) ] ;
const rows =
ids . length === 0
? [ ]
: z
. array ( RowSchema )
. parse (
await this . database . query (
2026-10-21 10:37:59 +00:00
"SELECT r.id,r.name,r.active,c.textValue AS address FROM resources r JOIN resource_network_shares d ON d.resourceId=r.id AND d.companyId=r.companyId JOIN resource_characteristics c ON c.resourceId=r.id JOIN resource_definitions f ON f.id=c.fieldId AND f.stockKey='field:unc' WHERE r.companyId=:companyId AND r.id IN (:ids)" ,
2026-10-21 09:25:23 +00:00
{ type : QueryTypes . SELECT , replacements : { companyId : context.companyId , ids } }
)
) ;
const byId = new Map ( rows . map ( ( row ) = > [ row . id , row ] ) ) ;
return Result . ok (
Object . freeze (
parsed . data . map ( ( reference ) : SummaryResolution = > {
const row =
reference . companyId === context . companyId &&
reference . providerId === this . providerId &&
reference . entityType === "resources:resource"
? byId . get ( reference . id )
: undefined ;
return row
? Object . freeze ( {
status : "resolved" ,
summary : EntitySummarySchema.parse ( {
reference ,
name : row.name ,
2026-10-21 10:37:59 +00:00
subtitle : row.address ,
2026-10-21 09:25:23 +00:00
state : {
code : row.active ? "active" : "inactive" ,
labelKey : row.active ? "resources:active" : "resources:inactive" ,
} ,
} ) ,
} )
: Object . freeze ( { status : "unavailable" , reference } ) ;
} )
)
) ;
}
}