@@ -496,6 +496,50 @@ function collectDefs(schema) {
496496 return defs
497497}
498498
499+ // Walk a JSON-pointer fragment ("/foo/bar/0") into a schema object.
500+ // Returns the target node, or null if any segment is missing.
501+ function walkJsonPointer ( root , fragment ) {
502+ if ( ! fragment || fragment === '/' || fragment === '#' ) return root
503+ const path = fragment . startsWith ( '#' ) ? fragment . slice ( 1 ) : fragment
504+ if ( ! path . startsWith ( '/' ) ) return null
505+ const parts = path . split ( '/' ) . slice ( 1 ) . map ( s => s . replace ( / ~ 1 / g, '/' ) . replace ( / ~ 0 / g, '~' ) )
506+ let target = root
507+ for ( const p of parts ) {
508+ if ( target == null || typeof target !== 'object' ) return null
509+ target = target [ p ]
510+ }
511+ return target == null ? null : target
512+ }
513+
514+ // Resolve a cross-schema $ref of the form "<id>#/<json-pointer>" (or just "<id>").
515+ // Returns { schema, fullId } where fullId is the resolved $id of the host schema.
516+ function resolveCrossSchemaRef ( ref , schemaMap ) {
517+ if ( ! schemaMap ) return null
518+ const hashIdx = ref . indexOf ( '#' )
519+ const baseId = hashIdx >= 0 ? ref . slice ( 0 , hashIdx ) : ref
520+ const fragment = hashIdx >= 0 ? ref . slice ( hashIdx ) : ''
521+ if ( ! baseId ) return null
522+
523+ let baseSchema = null
524+ let fullId = null
525+ if ( schemaMap . has ( baseId ) ) {
526+ baseSchema = schemaMap . get ( baseId )
527+ fullId = baseId
528+ } else if ( ! ref . includes ( '://' ) ) {
529+ for ( const [ id ] of schemaMap ) {
530+ if ( id . endsWith ( '/' + baseId ) ) {
531+ baseSchema = schemaMap . get ( id )
532+ fullId = id
533+ break
534+ }
535+ }
536+ }
537+ if ( ! baseSchema ) return null
538+ const target = fragment ? walkJsonPointer ( baseSchema , fragment ) : baseSchema
539+ if ( target == null ) return null
540+ return { schema : target , fullId }
541+ }
542+
499543function resolveRef ( ref , defs , schemaMap ) {
500544 // Self-reference: "#" — treat as permissive to avoid infinite recursion
501545 if ( ref === '#' ) return ( ) => true
@@ -520,7 +564,15 @@ function resolveRef(ref, defs, schemaMap) {
520564 const fn = compileToJS ( resolved , null , schemaMap )
521565 return fn || ( ( ) => true )
522566 }
523- // 3. Cross-schema ref (relative URI resolution)
567+ // 3. Cross-schema ref with JSON pointer fragment ("<id>#/<path>")
568+ if ( schemaMap && ref . includes ( '#' ) ) {
569+ const r = resolveCrossSchemaRef ( ref , schemaMap )
570+ if ( r ) {
571+ const fn = compileToJS ( r . schema , null , schemaMap )
572+ return fn || ( ( ) => true )
573+ }
574+ }
575+ // 4. Cross-schema ref (relative URI resolution, no fragment)
524576 if ( schemaMap && ! ref . includes ( '://' ) && ! ref . startsWith ( '#' ) ) {
525577 for ( const [ id ] of schemaMap ) {
526578 if ( id . endsWith ( '/' + ref ) ) {
@@ -652,6 +704,11 @@ function codegenSafe(schema, schemaMap) {
652704 if ( id . endsWith ( '/' + schema . $ref ) ) { isResolvable = true ; resolvedTarget = schemaMap . get ( id ) ; break }
653705 }
654706 }
707+ // Cross-schema ref with JSON pointer fragment: "<id>#/<path>"
708+ if ( ! isLocal && ! isResolvable && schemaMap && schema . $ref . includes ( '#' ) && ! schema . $ref . startsWith ( '#' ) ) {
709+ const r = resolveCrossSchemaRef ( schema . $ref , schemaMap )
710+ if ( r ) { isResolvable = true ; resolvedTarget = r . schema }
711+ }
655712 // Anchor-style ref: #name (not #/path, not bare #) — resolvable at compile time via anchors map
656713 const isAnchorRef = ! isLocal && ! isResolvable && schema . $ref . length > 1 && schema . $ref . startsWith ( '#' ) && ! schema . $ref . startsWith ( '#/' )
657714 if ( ! isLocal && ! isResolvable && ! isAnchorRef ) return false
@@ -1093,13 +1150,17 @@ function genCode(schema, v, lines, ctx, knownType) {
10931150 }
10941151 }
10951152 } else if ( schema . $ref !== '#' && ctx . schemaMap ) {
1096- // 2. Cross-schema ref (exact match or relative URI)
1153+ // 2. Cross-schema ref (exact match, relative URI, or JSON pointer fragment )
10971154 let resolved = ctx . schemaMap . get ( schema . $ref )
10981155 if ( ! resolved && ! schema . $ref . includes ( '://' ) && ! schema . $ref . startsWith ( '#' ) ) {
10991156 for ( const [ id , s ] of ctx . schemaMap ) {
11001157 if ( id . endsWith ( '/' + schema . $ref ) ) { resolved = s ; break }
11011158 }
11021159 }
1160+ if ( ! resolved && schema . $ref . includes ( '#' ) && ! schema . $ref . startsWith ( '#' ) ) {
1161+ const r = resolveCrossSchemaRef ( schema . $ref , ctx . schemaMap )
1162+ if ( r ) resolved = r . schema
1163+ }
11031164 if ( resolved ) {
11041165 if ( ctx . refStack . has ( schema . $ref ) ) { if ( ! hasSiblings ) return }
11051166 else {
@@ -2516,6 +2577,17 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
25162577 ctx . refStack . delete ( schema . $ref )
25172578 return
25182579 }
2580+ // Cross-schema ref with JSON pointer fragment ("<id>#/<path>")
2581+ if ( ctx . schemaMap && schema . $ref . includes ( '#' ) && ! schema . $ref . startsWith ( '#' ) ) {
2582+ const r = resolveCrossSchemaRef ( schema . $ref , ctx . schemaMap )
2583+ if ( r ) {
2584+ if ( ctx . refStack . has ( schema . $ref ) ) return
2585+ ctx . refStack . add ( schema . $ref )
2586+ genCodeE ( r . schema , v , pathExpr , lines , ctx , schemaPrefix )
2587+ ctx . refStack . delete ( schema . $ref )
2588+ return
2589+ }
2590+ }
25192591 }
25202592
25212593 // $dynamicRef — resolve via anchors map
@@ -3022,6 +3094,17 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
30223094 ctx . refStack . delete ( schema . $ref )
30233095 return
30243096 }
3097+ // Cross-schema ref with JSON pointer fragment ("<id>#/<path>")
3098+ if ( ctx . schemaMap && schema . $ref . includes ( '#' ) && ! schema . $ref . startsWith ( '#' ) ) {
3099+ const r = resolveCrossSchemaRef ( schema . $ref , ctx . schemaMap )
3100+ if ( r ) {
3101+ if ( ctx . refStack . has ( schema . $ref ) ) return
3102+ ctx . refStack . add ( schema . $ref )
3103+ genCodeC ( r . schema , v , pathExpr , lines , ctx , schemaPrefix )
3104+ ctx . refStack . delete ( schema . $ref )
3105+ return
3106+ }
3107+ }
30253108 }
30263109
30273110 // $dynamicRef — resolve via anchors map
0 commit comments