11import type { Format } from "../types/format.js" ;
22import type { Resolver } from "../types/resolver.js" ;
3+ import type { LoaderSys , LoadOptions } from "./index.js" ;
34import { getAtPath , parsePointer } from "./pointer.js" ;
4- import type { LoadOptions , LoaderSys } from "./index.js" ;
55
66/**
77 * Experimental: a Typed-OM-like representation of DTCG tokens.
@@ -81,7 +81,7 @@ export type EvalContext = {
8181 doc : GroupNode ;
8282 memo : WeakMap < object , ValueNode > ;
8383 resolving : Set < object > ;
84- aliasMemo : WeakMap < object , string [ ] | ErrorNode > ;
84+ aliasMemo : WeakMap < object , string [ ] > ;
8585 pointerMemo : WeakMap < object , string [ ] | ErrorNode > ;
8686} ;
8787
@@ -98,15 +98,15 @@ export const createEvalContext = (doc: GroupNode): EvalContext => ({
9898export type PathLike = string | readonly string [ ] ;
9999
100100/** Splits a dot-path (`"a.b.c"`) into segments (or returns an existing segment array). */
101- export const toDotPath = ( path : PathLike ) : string [ ] => ( typeof path === "string" ? ( path ? path . split ( "." ) : [ ] ) : [ ... path ] ) ;
101+ export const toDotPath = ( path : PathLike ) : readonly string [ ] => ( typeof path === "string" ? ( path ? path . split ( "." ) : [ ] ) : path ) ;
102102
103103/** Returns the entry node (group or token) at a dot-path. */
104104export const getEntry = ( doc : GroupNode , path : PathLike ) : EntryNode | undefined => {
105105 const segs = toDotPath ( path ) ;
106106 let cur : EntryNode = doc ;
107107 for ( const seg of segs ) {
108108 if ( cur . t !== T . Group ) return undefined ;
109- const next : EntryNode | undefined = ( cur as GroupNode ) . entries [ seg ] ;
109+ const next : EntryNode | undefined = cur . entries [ seg ] ;
110110 if ( ! next ) return undefined ;
111111 cur = next ;
112112 }
@@ -127,8 +127,7 @@ export const getToken = (doc: GroupNode, path: PathLike): TokenNode | undefined
127127
128128/** Returns any node reachable by a local JSON Pointer (string or segment array). */
129129export const getNodeByPointer = ( doc : GroupNode , pointer : string | readonly string [ ] ) : EntryNode | ValueNode | undefined => {
130- const segs = typeof pointer === "string" ? parsePointer ( pointer ) : [ ...pointer ] ;
131- return getByPointer ( doc , segs ) ;
130+ return getByPointer ( doc , typeof pointer === "string" ? parsePointer ( pointer ) : pointer ) ;
132131} ;
133132
134133/** Returns a value node by pointer; if the pointer lands on a token, returns its `$value` node. */
@@ -197,8 +196,8 @@ export class OMLoaderHost {
197196 if ( set == null ) continue ;
198197
199198 for ( const source of set . sources ) {
200- if ( isPlainObject ( source ) && typeof ( source as any ) . $ref === "string" ) {
201- const url = new URL ( ( source as any ) . $ref as string , resolverBase ) ;
199+ if ( hasStringRef ( source ) ) {
200+ const url = new URL ( source . $ref , resolverBase ) ;
202201 sources . push ( url ) ;
203202 formats . push ( this . readJSON < Format > ( url ) ) ;
204203 } else {
@@ -223,6 +222,7 @@ type RawObject = Record<string, unknown>;
223222
224223const isPlainObject = ( v : unknown ) : v is RawObject => v !== null && typeof v === "object" && ! Array . isArray ( v ) ;
225224const isAlias = ( v : unknown ) : v is string => typeof v === "string" && v . startsWith ( "{" ) && v . endsWith ( "}" ) ;
225+ const hasStringRef = ( v : unknown ) : v is { $ref : string } => isPlainObject ( v ) && typeof v . $ref === "string" ;
226226const isPointerRefObject = ( v : unknown ) : v is { $ref : string } => isPlainObject ( v ) && typeof v . $ref === "string" && Object . keys ( v ) . length === 1 ;
227227
228228/** Collects sub-objects for `key` across formats, resetting on non-object override. */
@@ -246,7 +246,7 @@ const getFormatsAtPath = (formats: readonly RawObject[], path: readonly string[]
246246 if ( current . length === 0 ) return [ ] ;
247247 }
248248 // Stop: a token cannot be extended as a group.
249- return current . some ( ( f ) => "$value" in f || typeof ( f as any ) . $ref === "string" ) ? [ ] : current ;
249+ return current . some ( ( f ) => "$value" in f || typeof f . $ref === "string" ) ? [ ] : current ;
250250} ;
251251
252252const parseReferencePath = ( ref : string ) : string [ ] | undefined => {
@@ -268,7 +268,7 @@ const expandFormats = (formats: readonly RawObject[], rootFormats: readonly RawO
268268 try {
269269 const out : RawObject [ ] = [ ] ;
270270 for ( const format of formats ) {
271- const ref = typeof format . $extends === "string" ? ( format . $extends as string ) : undefined ;
271+ const ref = typeof format . $extends === "string" ? format . $extends : undefined ;
272272 if ( ref ) {
273273 const targetPath = parseReferencePath ( ref ) ;
274274 if ( targetPath ) {
@@ -295,11 +295,11 @@ const buildEntry = (formats: RawObject[], rootFormats: RawObject[], inheritedTyp
295295
296296 let nodeType : string | undefined ;
297297 for ( const fmt of effectiveFormats ) {
298- if ( typeof ( fmt as any ) . $type === "string" ) nodeType = ( fmt as any ) . $type as string ;
298+ if ( typeof fmt . $type === "string" ) nodeType = fmt . $type ;
299299 }
300300 nodeType ??= inheritedType ;
301301
302- const isToken = effectiveFormats . some ( ( f ) => "$value" in f || typeof ( f as any ) . $ref === "string" ) ;
302+ const isToken = effectiveFormats . some ( ( f ) => "$value" in f || typeof f . $ref === "string" ) ;
303303 if ( isToken ) {
304304 const rawValue = getEffectiveTokenValue ( effectiveFormats ) ;
305305 const token : TokenNode = { t : T . Token , value : parseTokenValue ( nodeType , rawValue ) } ;
@@ -331,16 +331,17 @@ const getEffectiveTokenValue = (effectiveFormats: readonly RawObject[]): unknown
331331 let hasLeaf = false ;
332332
333333 for ( const fmt of effectiveFormats ) {
334- const ref = ( fmt as any ) . $ref ;
335- if ( typeof ref === "string" ) {
334+ if ( typeof fmt . $ref === "string" ) {
336335 valueObjs . length = 0 ;
337- leaf = { $ref : ref } ;
336+ leaf = { $ref : fmt . $ ref } ;
338337 hasLeaf = true ;
339338 }
340339
341340 if ( "$value" in fmt ) {
342- const v = ( fmt as any ) . $value as unknown ;
341+ const v = fmt . $value ;
343342 if ( isPlainObject ( v ) ) {
343+ hasLeaf = false ;
344+ leaf = undefined ;
344345 valueObjs . push ( v ) ;
345346 } else {
346347 valueObjs . length = 0 ;
@@ -417,24 +418,19 @@ export const parseValue = (raw: unknown): ValueNode => {
417418// ─── Evaluation ───────────────────────────────────────────────────────────────
418419
419420export const compute = ( v : ValueNode , ctx : EvalContext ) : ValueNode => {
420- const key = v as unknown as object ;
421- const cached = ctx . memo . get ( key ) ;
421+ const cached = ctx . memo . get ( v ) ;
422422 if ( cached ) return cached ;
423423
424- if ( ctx . resolving . has ( key ) ) return { t : T . Error , message : "circular reference" } ;
425- ctx . resolving . add ( key ) ;
424+ if ( ctx . resolving . has ( v ) ) return { t : T . Error , message : "circular reference" } ;
425+ ctx . resolving . add ( v ) ;
426426
427427 try {
428428 let out : ValueNode = v ;
429429
430430 if ( v . t === T . AliasRef ) {
431431 const segs = aliasSegments ( v , ctx ) ;
432- if ( isError ( segs ) ) {
433- out = segs ;
434- } else {
435- const tok = getTokenByPath ( ctx . doc , segs ) ;
436- out = tok ? compute ( tok . value , ctx ) : { t : T . Error , message : "alias target is not a token" } ;
437- }
432+ const tok = getToken ( ctx . doc , segs ) ;
433+ out = tok ? compute ( tok . value , ctx ) : { t : T . Error , message : "alias target is not a token" } ;
438434 } else if ( v . t === T . PointerRef ) {
439435 const segs = pointerSegments ( v , ctx ) ;
440436 if ( isError ( segs ) ) {
@@ -450,10 +446,10 @@ export const compute = (v: ValueNode, ctx: EvalContext): ValueNode => {
450446 }
451447 }
452448
453- ctx . memo . set ( key , out ) ;
449+ ctx . memo . set ( v , out ) ;
454450 return out ;
455451 } finally {
456- ctx . resolving . delete ( key ) ;
452+ ctx . resolving . delete ( v ) ;
457453 }
458454} ;
459455
@@ -502,7 +498,7 @@ export const toJSONComputed = (v: ValueNode, ctx: EvalContext): unknown => {
502498 case T . Bool :
503499 case T . Num :
504500 case T . Str :
505- return ( c as any ) . v ;
501+ return c . v ;
506502 case T . Arr :
507503 return c . items . map ( ( it ) => toJSONComputed ( it , ctx ) ) ;
508504 case T . Obj : {
@@ -525,46 +521,33 @@ export const toJSONComputed = (v: ValueNode, ctx: EvalContext): unknown => {
525521
526522// ─── Reference lookup ─────────────────────────────────────────────────────────
527523
528- const isError = ( v : unknown ) : v is ErrorNode => typeof v === "object" && v !== null && ( v as any ) . t === T . Error ;
524+ const isError = ( v : unknown ) : v is ErrorNode => typeof v === "object" && v !== null && ( v as ErrorNode ) . t === T . Error ;
529525const isValueNode = ( n : EntryNode | ValueNode ) : n is ValueNode => n . t !== T . Group && n . t !== T . Token ;
530526
531- const aliasSegments = ( n : AliasRefNode , ctx : EvalContext ) : string [ ] | ErrorNode => {
532- const cached = ctx . aliasMemo . get ( n as unknown as object ) ;
527+ /** AliasRefNode is only created for valid aliases, so parsing always succeeds. */
528+ const aliasSegments = ( n : AliasRefNode , ctx : EvalContext ) : string [ ] => {
529+ const cached = ctx . aliasMemo . get ( n ) ;
533530 if ( cached ) return cached ;
534-
535- const raw = n . raw ;
536- const segs = isAlias ( raw ) ? raw . slice ( 1 , - 1 ) . split ( "." ) : undefined ;
537- const out : string [ ] | ErrorNode = segs ?? ( { t : T . Error , message : "invalid alias" } as ErrorNode ) ;
538- ctx . aliasMemo . set ( n as unknown as object , out ) ;
539- return out ;
531+ const segs = n . raw . slice ( 1 , - 1 ) . split ( "." ) ;
532+ ctx . aliasMemo . set ( n , segs ) ;
533+ return segs ;
540534} ;
541535
542536const pointerSegments = ( n : PointerRefNode , ctx : EvalContext ) : string [ ] | ErrorNode => {
543- const cached = ctx . pointerMemo . get ( n as unknown as object ) ;
537+ const cached = ctx . pointerMemo . get ( n ) ;
544538 if ( cached ) return cached ;
545539
546540 try {
547541 const segs = parsePointer ( n . $ref ) ;
548- ctx . pointerMemo . set ( n as unknown as object , segs ) ;
542+ ctx . pointerMemo . set ( n , segs ) ;
549543 return segs ;
550544 } catch {
551545 const err : ErrorNode = { t : T . Error , message : "invalid JSON Pointer" } ;
552- ctx . pointerMemo . set ( n as unknown as object , err ) ;
546+ ctx . pointerMemo . set ( n , err ) ;
553547 return err ;
554548 }
555549} ;
556550
557- const getTokenByPath = ( doc : GroupNode , path : readonly string [ ] ) : TokenNode | undefined => {
558- let cur : EntryNode = doc ;
559- for ( const seg of path ) {
560- if ( cur . t !== T . Group ) return undefined ;
561- const next : EntryNode | undefined = cur . entries [ seg ] ;
562- if ( ! next ) return undefined ;
563- cur = next ;
564- }
565- return cur . t === T . Token ? cur : undefined ;
566- } ;
567-
568551const getByPointer = ( doc : GroupNode , ptr : readonly string [ ] ) : EntryNode | ValueNode | undefined => {
569552 let cur : EntryNode | ValueNode = doc ;
570553
@@ -588,15 +571,15 @@ const getByPointer = (doc: GroupNode, ptr: readonly string[]): EntryNode | Value
588571 return undefined ;
589572 }
590573
591- // value node traversal
592- cur = getValueChild ( cur , seg ) ?? ( undefined as any ) ;
593- if ( ! cur ) return undefined ;
574+ const next = getValueChild ( cur , seg ) ;
575+ if ( ! next ) return undefined ;
576+ cur = next ;
594577 }
595578
596579 return cur ;
597580} ;
598581
599- const getValueChild = ( v : ValueNode , seg : string ) : EntryNode | ValueNode | undefined => {
582+ const getValueChild = ( v : ValueNode , seg : string ) : ValueNode | undefined => {
600583 switch ( v . t ) {
601584 case T . Obj :
602585 return v . props [ seg ] ;
@@ -625,7 +608,7 @@ const resolveSet = (item: Resolver["resolutionOrder"][number], resolver: Resolve
625608 return ( item as SetLike ) . type === "set" ? ( item as any ) : null ;
626609} ;
627610
628- const isSet = ( v : unknown ) : v is { sources : unknown [ ] } => isPlainObject ( v ) && Array . isArray ( ( v as any ) . sources ) ;
611+ const isSet = ( v : unknown ) : v is { sources : unknown [ ] } => isPlainObject ( v ) && Array . isArray ( v . sources ) ;
629612
630613const toBaseURL = ( base : URL | string | undefined , fallback : URL ) : URL => {
631614 if ( base == null ) return fallback ;
0 commit comments