@@ -23,7 +23,7 @@ import { ValidationErrorItem } from '../validator.js';
2323import { registerDefaultHandlers , registerTypeGuards } from './handlers.js' ;
2424import { NamingStrategy } from './naming.js' ;
2525import { HandlerRegistry } from './registry.js' ;
26- import { BuildState , SerializationOptions } from './state.js' ;
26+ import { BuildOptions , BuildState , SerializationOptions } from './state.js' ;
2727import { registerUnionHandler } from './union.js' ;
2828import { registerValidationHook } from './validation.js' ;
2929
@@ -131,8 +131,8 @@ export class Serializer {
131131 arg < T > ( ) ,
132132 arg < SerializationOptions > ( ) ,
133133 ( b : Builder , data : Ref < T > , options : Ref < SerializationOptions > ) => {
134- const optionsRef = b . let ( b . nullish ( options , b . emptyObj < SerializationOptions > ( ) ) ) ;
135-
134+ // Ensure options is always an object (for safe property access in handlers)
135+ const optionsRef = b . let ( b . nullish ( options , b . emptyObj ( ) ) ) ;
136136 const state = new BuildState ( 'serialize' , this , b , optionsRef , this . serializeRegistry ) ;
137137
138138 return state . build ( type , data ) ;
@@ -151,8 +151,8 @@ export class Serializer {
151151 arg < any > ( ) ,
152152 arg < SerializationOptions > ( ) ,
153153 ( b : Builder , data : Ref < any > , options : Ref < SerializationOptions > ) => {
154- const optionsRef = b . let ( b . nullish ( options , b . emptyObj < SerializationOptions > ( ) ) ) ;
155-
154+ // Ensure options is always an object (for safe property access in handlers)
155+ const optionsRef = b . let ( b . nullish ( options , b . emptyObj ( ) ) ) ;
156156 const state = new BuildState ( 'deserialize' , this , b , optionsRef , this . deserializeRegistry ) ;
157157
158158 return state . build ( type , data ) ;
@@ -378,36 +378,106 @@ export class Serializer {
378378 }
379379}
380380
381+ /**
382+ * Compute a cache key suffix from build options.
383+ * Returns empty string for default options (no baked values).
384+ */
385+ export function computeBuildOptionsKey ( buildOptions ?: BuildOptions ) : string {
386+ if ( ! buildOptions ) return '' ;
387+
388+ const parts : string [ ] = [ ] ;
389+
390+ // Loose mode: L=loose, S=strict
391+ if ( buildOptions . looseBaked !== undefined ) {
392+ parts . push ( buildOptions . looseBaked ? 'L' : 'S' ) ;
393+ }
394+
395+ // Groups: G:group1,group2 or GX:excludedGroup1,excludedGroup2
396+ if ( buildOptions . groupsBaked !== undefined ) {
397+ const sorted = [ ...buildOptions . groupsBaked ] . sort ( ) ;
398+ parts . push ( `G:${ sorted . join ( ',' ) } ` ) ;
399+ }
400+ if ( buildOptions . groupsExcludeBaked !== undefined ) {
401+ const sorted = [ ...buildOptions . groupsExcludeBaked ] . sort ( ) ;
402+ parts . push ( `GX:${ sorted . join ( ',' ) } ` ) ;
403+ }
404+
405+ return parts . length > 0 ? '_' + parts . join ( '_' ) : '' ;
406+ }
407+
408+ /**
409+ * Convert SerializationOptions to BuildOptions for baking into specialized functions.
410+ */
411+ export function serializationOptionsToBuildOptions ( options ?: SerializationOptions ) : BuildOptions | undefined {
412+ if ( ! options ) return undefined ;
413+
414+ const buildOptions : BuildOptions = { } ;
415+ let hasBakedOptions = false ;
416+
417+ // Bake loose mode if explicitly set
418+ if ( options . loosely !== undefined ) {
419+ buildOptions . looseBaked = options . loosely ;
420+ hasBakedOptions = true ;
421+ }
422+
423+ // Bake groups if provided
424+ if ( options . groups !== undefined ) {
425+ buildOptions . groupsBaked = options . groups ;
426+ hasBakedOptions = true ;
427+ }
428+ if ( options . groupsExclude !== undefined ) {
429+ buildOptions . groupsExcludeBaked = options . groupsExclude ;
430+ hasBakedOptions = true ;
431+ }
432+
433+ return hasBakedOptions ? buildOptions : undefined ;
434+ }
435+
381436/**
382437 * Get a cached serializer function for a type.
438+ *
439+ * @param type - The type to serialize
440+ * @param registry - The handler registry (serialize or deserialize)
441+ * @param namingStrategy - Property naming strategy
442+ * @param path - Path prefix for error messages
443+ * @param buildOptions - Optional build-time options to bake into the function
383444 */
384445export function getSerializeFunction (
385446 type : Type ,
386447 registry : HandlerRegistry ,
387448 namingStrategy : NamingStrategy = new NamingStrategy ( ) ,
388449 path : string = '' ,
450+ buildOptions ?: BuildOptions ,
389451) : SerializeFunction {
390452 const jitContainer = getTypeJitContainer ( type ) ;
391- const id = `${ registry . id } _${ namingStrategy . id } _${ path } ` ;
453+ const optionsKey = computeBuildOptionsKey ( buildOptions ) ;
454+ const id = `${ registry . id } _${ namingStrategy . id } _${ path } ${ optionsKey } ` ;
392455
393456 if ( jitContainer [ id ] ) {
394457 return jitContainer [ id ] ;
395458 }
396459
397- jitContainer [ id ] = createSerializeFunction ( type , registry , namingStrategy , path ) ;
460+ jitContainer [ id ] = createSerializeFunction ( type , registry , namingStrategy , path , buildOptions ) ;
398461 toFastProperties ( jitContainer ) ;
399462
400463 return jitContainer [ id ] ;
401464}
402465
403466/**
404467 * Create a serializer function for a type (not cached).
468+ *
469+ * @param type - The type to serialize
470+ * @param registry - The handler registry
471+ * @param namingStrategy - Property naming strategy
472+ * @param path - Path prefix for error messages
473+ * @param buildOptions - Optional build-time options to bake into the function
405474 */
406475export function createSerializeFunction (
407476 type : Type ,
408477 registry : HandlerRegistry ,
409478 namingStrategy : NamingStrategy = new NamingStrategy ( ) ,
410479 path : string = '' ,
480+ buildOptions ?: BuildOptions ,
411481) : SerializeFunction {
412482 // Get direction from registry
413483 const direction = registry . direction ;
@@ -416,16 +486,16 @@ export function createSerializeFunction(
416486 arg < any > ( ) ,
417487 arg < SerializationOptions > ( ) ,
418488 ( b : Builder , data : Ref < any > , options : Ref < SerializationOptions > ) => {
419- const optionsRef = b . let ( b . nullish ( options , b . emptyObj < SerializationOptions > ( ) ) ) ;
489+ // Ensure options is always an object (for safe property access in handlers)
490+ const optionsRef = b . let ( b . nullish ( options , b . emptyObj ( ) ) ) ;
420491
421- // We need a serializer reference here - for now use default
422492 const state = new BuildState (
423493 direction ,
424494 serializer , // Use default serializer
425495 b ,
426496 optionsRef ,
427497 registry ,
428- { namingStrategy } ,
498+ { namingStrategy, buildOptions } ,
429499 ) ;
430500
431501 return state . build ( type , data ) ;
0 commit comments