@@ -41,6 +41,26 @@ export const ENTITY_ID_START = 1024;
4141export const RELATION_SHIFT = 2 ** 42 ;
4242export const WILDCARD_TARGET_ID = 0 ;
4343
44+ /**
45+ * Internal function to decode a relation ID into raw component and target IDs
46+ * @param id The EntityId to decode
47+ * @returns Object with componentId and targetId, or null if not a relation
48+ */
49+ function decodeRelationRaw ( id : EntityId < any > ) : { componentId : number ; targetId : number } | null {
50+ if ( id >= 0 ) return null ;
51+ const absId = - id ;
52+ const componentId = Math . floor ( absId / RELATION_SHIFT ) ;
53+ const targetId = absId % RELATION_SHIFT ;
54+ return { componentId, targetId } ;
55+ }
56+
57+ /**
58+ * Check if a component ID is valid (1-1023)
59+ */
60+ function isValidComponentId ( componentId : number ) : boolean {
61+ return componentId >= 1 && componentId <= COMPONENT_ID_MAX ;
62+ }
63+
4464/**
4565 * Create a component ID
4666 * @param id Component identifier (1-1023)
@@ -128,12 +148,8 @@ export function isRelationId<T>(id: EntityId<T>): id is RelationId<T> {
128148 * Check if an ID is a wildcard relation id
129149 */
130150export function isWildcardRelationId < T > ( id : EntityId < T > ) : id is WildcardRelationId < T > {
131- if ( ! isRelationId ( id ) ) {
132- return false ;
133- }
134- const absId = - id ;
135- const targetId = absId % RELATION_SHIFT ;
136- return targetId === WILDCARD_TARGET_ID ;
151+ const decoded = decodeRelationRaw ( id ) ;
152+ return decoded !== null && decoded . targetId === WILDCARD_TARGET_ID ;
137153}
138154
139155/**
@@ -146,13 +162,18 @@ export function decodeRelationId(relationId: RelationId<any>): {
146162 targetId : EntityId < any > ;
147163 type : "entity" | "component" | "wildcard" ;
148164} {
149- if ( ! isRelationId ( relationId ) ) {
165+ const decoded = decodeRelationRaw ( relationId ) ;
166+ if ( decoded === null ) {
150167 throw new Error ( "ID is not a relation ID" ) ;
151168 }
152- const absId = - relationId ;
153169
154- const componentId = Math . floor ( absId / RELATION_SHIFT ) as ComponentId < any > ;
155- const targetId = ( absId % RELATION_SHIFT ) as EntityId < any > ;
170+ const { componentId : rawComponentId , targetId : rawTargetId } = decoded ;
171+ if ( ! isValidComponentId ( rawComponentId ) ) {
172+ throw new Error ( "Invalid component ID in relation" ) ;
173+ }
174+
175+ const componentId = rawComponentId as ComponentId < any > ;
176+ const targetId = rawTargetId as EntityId < any > ;
156177
157178 // Determine type based on targetId range
158179 if ( targetId === WILDCARD_TARGET_ID ) {
@@ -178,11 +199,8 @@ export function getIdType(
178199 if ( isRelationId ( id ) ) {
179200 try {
180201 const decoded = decodeRelationId ( id ) ;
181- // Validate that componentId and targetId are valid
182- if (
183- ! isComponentId ( decoded . componentId ) ||
184- ( decoded . type !== "wildcard" && ! isEntityId ( decoded . targetId ) && ! isComponentId ( decoded . targetId ) )
185- ) {
202+ // Validate that componentId and targetId are valid (decodeRelationId already checks componentId)
203+ if ( decoded . type !== "wildcard" && ! isEntityId ( decoded . targetId ) && ! isComponentId ( decoded . targetId ) ) {
186204 return "invalid" ;
187205 }
188206 switch ( decoded . type ) {
@@ -233,11 +251,8 @@ export function getDetailedIdType(id: EntityId<any>):
233251 if ( isRelationId ( id ) ) {
234252 try {
235253 const decoded = decodeRelationId ( id ) ;
236- // Validate that componentId and targetId are valid
237- if (
238- ! isComponentId ( decoded . componentId ) ||
239- ( decoded . type !== "wildcard" && ! isEntityId ( decoded . targetId ) && ! isComponentId ( decoded . targetId ) )
240- ) {
254+ // Validate that targetId is valid (decodeRelationId already checks componentId)
255+ if ( decoded . type !== "wildcard" && ! isEntityId ( decoded . targetId ) && ! isComponentId ( decoded . targetId ) ) {
241256 return { type : "invalid" } ;
242257 }
243258 let type : "entity-relation" | "component-relation" | "wildcard-relation" ;
@@ -290,11 +305,8 @@ export function inspectEntityId(id: EntityId<any>): string {
290305 if ( isRelationId ( id ) ) {
291306 try {
292307 const decoded = decodeRelationId ( id ) ;
293- // Validate that both component and target IDs are valid
294- if (
295- ! isComponentId ( decoded . componentId ) ||
296- ( decoded . type !== "wildcard" && ! isEntityId ( decoded . targetId ) && ! isComponentId ( decoded . targetId ) )
297- ) {
308+ // Validate that targetId is valid (decodeRelationId already checks componentId)
309+ if ( decoded . type !== "wildcard" && ! isEntityId ( decoded . targetId ) && ! isComponentId ( decoded . targetId ) ) {
298310 return `Invalid Relation ID (${ id } )` ;
299311 }
300312 const componentStr = `Component ID (${ decoded . componentId } )` ;
@@ -570,27 +582,32 @@ export function isDontFragmentComponent(id: ComponentId<any>): boolean {
570582 return dontFragmentFlags . has ( id ) ;
571583}
572584
585+ /**
586+ * Generic function to check relation flags with specific target conditions
587+ * @param id The entity/relation ID to check
588+ * @param flagBitSet The bitset for the flag
589+ * @param targetCondition Function to check target ID condition
590+ * @returns true if the condition is met, false otherwise
591+ */
592+ function checkRelationFlag (
593+ id : EntityId < any > ,
594+ flagBitSet : BitSet ,
595+ targetCondition : ( targetId : number ) => boolean ,
596+ ) : boolean {
597+ const decoded = decodeRelationRaw ( id ) ;
598+ if ( decoded === null ) return false ;
599+ const { componentId, targetId } = decoded ;
600+ return isValidComponentId ( componentId ) && targetCondition ( targetId ) && flagBitSet . has ( componentId ) ;
601+ }
602+
573603/**
574604 * Check if a relation ID is a dontFragment relation (entity-relation or component-relation with dontFragment component)
575605 * This is an optimized function that avoids the overhead of getDetailedIdType
576606 * @param id The entity/relation ID to check
577607 * @returns true if this is a dontFragment relation, false otherwise
578608 */
579609export function isDontFragmentRelation ( id : EntityId < any > ) : boolean {
580- // Only relation IDs are negative; non-relations return false immediately
581- if ( id >= 0 ) return false ;
582-
583- // Extract componentId directly from the relation encoding: -(componentId * RELATION_SHIFT + targetId)
584- // componentId = floor(absId / RELATION_SHIFT)
585- const absId = - id ;
586- const componentId = Math . floor ( absId / RELATION_SHIFT ) ;
587-
588- // Wildcard relations (targetId === 0) are not considered dontFragment relations for this check
589- const targetId = absId % RELATION_SHIFT ;
590- if ( targetId === WILDCARD_TARGET_ID ) return false ;
591-
592- // Check if componentId is valid and has dontFragment flag
593- return componentId >= 1 && componentId <= COMPONENT_ID_MAX && dontFragmentFlags . has ( componentId ) ;
610+ return checkRelationFlag ( id , dontFragmentFlags , ( targetId ) => targetId !== WILDCARD_TARGET_ID ) ;
594611}
595612
596613/**
@@ -600,17 +617,7 @@ export function isDontFragmentRelation(id: EntityId<any>): boolean {
600617 * @returns true if this is a wildcard relation with dontFragment component, false otherwise
601618 */
602619export function isDontFragmentWildcard ( id : EntityId < any > ) : boolean {
603- // Only relation IDs are negative
604- if ( id >= 0 ) return false ;
605-
606- const absId = - id ;
607- const targetId = absId % RELATION_SHIFT ;
608-
609- // Must be a wildcard relation (targetId === 0)
610- if ( targetId !== WILDCARD_TARGET_ID ) return false ;
611-
612- const componentId = Math . floor ( absId / RELATION_SHIFT ) ;
613- return componentId >= 1 && componentId <= COMPONENT_ID_MAX && dontFragmentFlags . has ( componentId ) ;
620+ return checkRelationFlag ( id , dontFragmentFlags , ( targetId ) => targetId === WILDCARD_TARGET_ID ) ;
614621}
615622
616623/**
@@ -620,24 +627,14 @@ export function isDontFragmentWildcard(id: EntityId<any>): boolean {
620627 * @returns true if this is an exclusive relation, false otherwise
621628 */
622629export function isExclusiveRelation ( id : EntityId < any > ) : boolean {
623- if ( id >= 0 ) return false ;
624- const absId = - id ;
625- const targetId = absId % RELATION_SHIFT ;
626- if ( targetId === WILDCARD_TARGET_ID ) return false ; // not an exclusive-specific relation
627- const componentId = Math . floor ( absId / RELATION_SHIFT ) ;
628- return componentId >= 1 && componentId <= COMPONENT_ID_MAX && exclusiveFlags . has ( componentId ) ;
630+ return checkRelationFlag ( id , exclusiveFlags , ( targetId ) => targetId !== WILDCARD_TARGET_ID ) ;
629631}
630632
631633/**
632634 * Check if a relation ID is a wildcard relation with exclusive component
633635 */
634636export function isExclusiveWildcard ( id : EntityId < any > ) : boolean {
635- if ( id >= 0 ) return false ;
636- const absId = - id ;
637- const targetId = absId % RELATION_SHIFT ;
638- if ( targetId !== WILDCARD_TARGET_ID ) return false ;
639- const componentId = Math . floor ( absId / RELATION_SHIFT ) ;
640- return componentId >= 1 && componentId <= COMPONENT_ID_MAX && exclusiveFlags . has ( componentId ) ;
637+ return checkRelationFlag ( id , exclusiveFlags , ( targetId ) => targetId === WILDCARD_TARGET_ID ) ;
641638}
642639
643640/**
@@ -648,57 +645,46 @@ export function isExclusiveWildcard(id: EntityId<any>): boolean {
648645 * @returns true if this is an entity-relation with cascade delete, false otherwise
649646 */
650647export function isCascadeDeleteRelation ( id : EntityId < any > ) : boolean {
651- if ( id >= 0 ) return false ;
652- const absId = - id ;
653- const targetId = absId % RELATION_SHIFT ;
654- // Wildcard relations (targetId === 0) don't cascade
655- if ( targetId === WILDCARD_TARGET_ID ) return false ;
656- // Only entity-relations cascade (targetId >= ENTITY_ID_START)
657- if ( targetId < ENTITY_ID_START ) return false ;
658- const componentId = Math . floor ( absId / RELATION_SHIFT ) ;
659- return componentId >= 1 && componentId <= COMPONENT_ID_MAX && cascadeDeleteFlags . has ( componentId ) ;
648+ return checkRelationFlag (
649+ id ,
650+ cascadeDeleteFlags ,
651+ ( targetId ) => targetId !== WILDCARD_TARGET_ID && targetId >= ENTITY_ID_START ,
652+ ) ;
660653}
661654
662655/**
663656 * Get the componentId from a relation ID without fully decoding the relation.
664657 * Returns undefined for non-relation IDs or invalid component IDs.
665658 */
666659export function getComponentIdFromRelationId < T > ( id : EntityId < T > ) : ComponentId < T > | undefined {
667- if ( id >= 0 ) return undefined ;
668- const absId = - id ;
669- const componentId = Math . floor ( absId / RELATION_SHIFT ) ;
670- if ( componentId < 1 || componentId > COMPONENT_ID_MAX ) return undefined ;
671- return componentId as ComponentId < T > ;
660+ const decoded = decodeRelationRaw ( id ) ;
661+ if ( decoded === null || ! isValidComponentId ( decoded . componentId ) ) return undefined ;
662+ return decoded . componentId as ComponentId < T > ;
672663}
673664
674665/**
675666 * Get the targetId from a relation ID without fully decoding the relation.
676667 * Returns undefined for non-relation IDs.
677668 */
678669export function getTargetIdFromRelationId ( id : EntityId < any > ) : EntityId < any > | undefined {
679- if ( id >= 0 ) return undefined ;
680- const absId = - id ;
681- return ( absId % RELATION_SHIFT ) as EntityId < any > ;
670+ const decoded = decodeRelationRaw ( id ) ;
671+ return decoded ?. targetId as EntityId < any > ;
682672}
683673
684674/**
685675 * Check if an ID is an entity-relation (relation targeting an entity, not a component or wildcard)
686676 */
687677export function isEntityRelation ( id : EntityId < any > ) : boolean {
688- if ( id >= 0 ) return false ;
689- const absId = - id ;
690- const targetId = absId % RELATION_SHIFT ;
691- return targetId >= ENTITY_ID_START ;
678+ const decoded = decodeRelationRaw ( id ) ;
679+ return decoded !== null && decoded . targetId >= ENTITY_ID_START ;
692680}
693681
694682/**
695683 * Check if an ID is a component-relation (relation targeting a component)
696684 */
697685export function isComponentRelation ( id : EntityId < any > ) : boolean {
698- if ( id >= 0 ) return false ;
699- const absId = - id ;
700- const targetId = absId % RELATION_SHIFT ;
701- return targetId >= 1 && targetId <= COMPONENT_ID_MAX ;
686+ const decoded = decodeRelationRaw ( id ) ;
687+ return decoded !== null && isValidComponentId ( decoded . targetId ) ;
702688}
703689
704690/**
0 commit comments