11import type {
22 Declaration ,
3+ SchemaEnum ,
34 SchemaClass ,
45 SchemaField ,
56 SchemaFieldType ,
@@ -25,6 +26,27 @@ export function parseKV3Defaults(value: string): Record<string, unknown> | null
2526 return null ;
2627}
2728
29+ function deepEqual ( a : unknown , b : unknown ) : boolean {
30+ if ( a === b ) return true ;
31+ if ( a === null || b === null || typeof a !== typeof b ) return false ;
32+ if ( typeof a !== "object" ) return false ;
33+ if ( Array . isArray ( a ) ) {
34+ if ( ! Array . isArray ( b ) || a . length !== b . length ) return false ;
35+ for ( let i = 0 ; i < a . length ; i ++ ) {
36+ if ( ! deepEqual ( a [ i ] , b [ i ] ) ) return false ;
37+ }
38+ return true ;
39+ }
40+ const aObj = a as Record < string , unknown > ;
41+ const bObj = b as Record < string , unknown > ;
42+ const aKeys = Object . keys ( aObj ) ;
43+ if ( aKeys . length !== Object . keys ( bObj ) . length ) return false ;
44+ for ( const key of aKeys ) {
45+ if ( ! deepEqual ( aObj [ key ] , bObj [ key ] ) ) return false ;
46+ }
47+ return true ;
48+ }
49+
2850/** @internal Exported for testing */
2951export function diffObject (
3052 embedded : Record < string , unknown > ,
@@ -34,7 +56,7 @@ export function diffObject(
3456 let hasDiff = false ;
3557 for ( const [ k , v ] of Object . entries ( embedded ) ) {
3658 if ( k === "_class" || v === HIDDEN_SENTINEL ) continue ;
37- if ( JSON . stringify ( v ) !== JSON . stringify ( ownDefaults [ k ] ) ) {
59+ if ( ! deepEqual ( v , ownDefaults [ k ] ) ) {
3860 diff [ k ] = v ;
3961 hasDiff = true ;
4062 }
@@ -62,25 +84,30 @@ function assignDefaults(classes: SchemaClass[]) {
6284 const classMap = new Map < string , SchemaClass > ( ) ;
6385 for ( const cls of classes ) classMap . set ( `${ cls . module } /${ cls . name } ` , cls ) ;
6486
65- // Parse and cache defaults per class key
66- const defaultsCache = new Map < string , Record < string , unknown > | null > ( ) ;
87+ // Pre-parse all KV3 defaults before the main loop mutates metadata
88+ const parsedDefaults = new Map < string , Record < string , unknown > | null > ( ) ;
89+ for ( const cls of classes ) {
90+ const meta = cls . metadata . find ( ( m ) => m . name === "MGetKV3ClassDefaults" && m . value ) ;
91+ parsedDefaults . set ( `${ cls . module } /${ cls . name } ` , meta ? parseKV3Defaults ( meta . value ! ) : null ) ;
92+ }
6793 function getDefaults ( module : string , name : string ) : Record < string , unknown > | null {
68- const key = `${ module } /${ name } ` ;
69- if ( defaultsCache . has ( key ) ) return defaultsCache . get ( key ) ! ;
70- const cls = classMap . get ( key ) ;
71- const meta = cls ?. metadata . find ( ( m ) => m . name === "MGetKV3ClassDefaults" && m . value ) ;
72- const parsed = meta ? parseKV3Defaults ( meta . value ! ) : null ;
73- defaultsCache . set ( key , parsed ) ;
74- return parsed ;
94+ return parsedDefaults . get ( `${ module } /${ name } ` ) ?? null ;
7595 }
7696
77- // Collect all fields including from parent chain
78- function collectFields ( cls : SchemaClass , out : Map < string , SchemaField > ) {
97+ // Cache all field names (including inherited) per class
98+ const allFieldsCache = new Map < string , Set < string > > ( ) ;
99+ function getAllFieldNames ( cls : SchemaClass ) : Set < string > {
100+ const key = `${ cls . module } /${ cls . name } ` ;
101+ const cached = allFieldsCache . get ( key ) ;
102+ if ( cached !== undefined ) return cached ;
103+ const names = new Set < string > ( ) ;
79104 for ( const p of cls . parents ) {
80105 const parent = classMap . get ( `${ p . module } /${ p . name } ` ) ;
81- if ( parent ) collectFields ( parent , out ) ;
106+ if ( parent ) for ( const n of getAllFieldNames ( parent ) ) names . add ( n ) ;
82107 }
83- for ( const f of cls . fields ) out . set ( f . name , f ) ;
108+ for ( const f of cls . fields ) names . add ( f . name ) ;
109+ allFieldsCache . set ( key , names ) ;
110+ return names ;
84111 }
85112
86113 for ( const cls of classes ) {
@@ -91,16 +118,13 @@ function assignDefaults(classes: SchemaClass[]) {
91118 const ownFields = new Map < string , SchemaField > ( ) ;
92119 for ( const f of cls . fields ) ownFields . set ( f . name , f ) ;
93120
94- // Track parent field names so we know which keys are inherited vs truly unknown
95- const allFields = new Map < string , SchemaField > ( ) ;
96- collectFields ( cls , allFields ) ;
97-
121+ const allFieldNames = getAllFieldNames ( cls ) ;
98122 const unconsumed : Record < string , unknown > = { } ;
99123
100124 for ( const [ key , value ] of Object . entries ( defaults ) ) {
101125 if ( value === HIDDEN_SENTINEL ) continue ;
102126
103- if ( ! allFields . has ( key ) ) {
127+ if ( ! allFieldNames . has ( key ) ) {
104128 unconsumed [ key ] = value ;
105129 continue ;
106130 }
@@ -116,10 +140,7 @@ function assignDefaults(classes: SchemaClass[]) {
116140 break ;
117141 }
118142 }
119- if (
120- parentDefault !== undefined &&
121- JSON . stringify ( value ) !== JSON . stringify ( parentDefault )
122- ) {
143+ if ( parentDefault !== undefined && ! deepEqual ( value , parentDefault ) ) {
123144 unconsumed [ key ] = value ;
124145 }
125146 continue ;
@@ -200,30 +221,25 @@ export interface SchemaMetadata {
200221export type ParsedSchemas = ReturnType < typeof parseSchemas > ;
201222
202223export function parseSchemas ( data : SchemasJson ) {
203- const classes : Declaration [ ] = data . classes . map ( ( c ) => ( {
204- kind : "class" as const ,
205- name : c . name ,
206- module : c . module ,
207- parents : c . parents ?? [ ] ,
208- fields : ( c . fields ?? [ ] ) . map ( ( f ) => ( {
209- ...f ,
210- metadata : f . metadata ?? [ ] ,
211- } ) ) ,
212- metadata : c . metadata ?? [ ] ,
213- } ) ) ;
214- const enums : Declaration [ ] = data . enums . map ( ( e ) => ( {
215- kind : "enum" as const ,
216- name : e . name ,
217- module : e . module ,
218- alignment : e . alignment ,
219- members : ( e . members ?? [ ] ) . map ( ( m ) => ( {
220- ...m ,
221- metadata : m . metadata ?? [ ] ,
222- } ) ) ,
223- metadata : e . metadata ?? [ ] ,
224- } ) ) ;
224+ const classes = data . classes as SchemaClass [ ] ;
225+ for ( const c of classes ) {
226+ c . kind = "class" ;
227+ c . parents ??= [ ] ;
228+ c . metadata ??= [ ] ;
229+ for ( const f of ( c . fields ??= [ ] ) ) {
230+ f . metadata ??= [ ] ;
231+ }
232+ }
233+ const enums = data . enums as SchemaEnum [ ] ;
234+ for ( const e of enums ) {
235+ e . kind = "enum" ;
236+ e . metadata ??= [ ] ;
237+ for ( const m of ( e . members ??= [ ] ) ) {
238+ m . metadata ??= [ ] ;
239+ }
240+ }
225241
226- assignDefaults ( classes as SchemaClass [ ] ) ;
242+ assignDefaults ( classes ) ;
227243
228244 // Sort all declarations by module then name, build map in one pass
229245 const all : Declaration [ ] = [ ...classes , ...enums , ...intrinsicDeclarations . values ( ) ] ;
0 commit comments