@@ -38,15 +38,57 @@ interface StoredFieldMeta extends SchemaFieldOptions {
3838 designType ?: unknown ;
3939}
4040
41+ /**
42+ * Type guard for Stage 3 (TypeScript 5.0+) decorator context.
43+ * New decorators pass (value, context) where context has a `kind` property.
44+ */
45+ function isNewDecoratorContext (
46+ arg : unknown
47+ ) : arg is { kind : string ; name : string | symbol } {
48+ return (
49+ typeof arg === "object" &&
50+ arg !== null &&
51+ "kind" in arg &&
52+ typeof ( arg as { kind : string } ) . kind === "string"
53+ ) ;
54+ }
55+
56+ /**
57+ * Track (class, propertyKey) pairs already stored to avoid duplicates when
58+ * the initializer runs on each instance (Stage 3 decorator API).
59+ */
60+ const schemaFieldProcessed = new WeakMap < object , Set < string > > ( ) ;
61+
62+ function storeSchemaFieldMetadata (
63+ cls : object ,
64+ propertyKey : string ,
65+ options : SchemaFieldOptions ,
66+ designType ?: unknown
67+ ) : void {
68+ const existing : StoredFieldMeta [ ] =
69+ ( Reflect . getOwnMetadata ( SCHEMA_METADATA_KEY , cls ) as StoredFieldMeta [ ] | undefined ) ?? [ ] ;
70+
71+ existing . push ( {
72+ ...options ,
73+ propertyKey,
74+ designType,
75+ } ) ;
76+
77+ Reflect . defineMetadata ( SCHEMA_METADATA_KEY , existing , cls ) ;
78+ }
79+
4180/**
4281 * Property decorator to define JSON Schema metadata on a class.
4382 *
4483 * When used with `generateSchemaFromClass()`, produces a JSON Schema draft-07
4584 * object from the decorated properties.
4685 *
47- * If `emitDecoratorMetadata` is enabled in tsconfig.json, the TypeScript type
48- * is automatically inferred for `string`, `number`, `boolean` — no need to
49- * specify `type` explicitly for those.
86+ * Supports both TypeScript 5.0+ (Stage 3) and legacy (experimentalDecorators)
87+ * decorator APIs.
88+ *
89+ * If `emitDecoratorMetadata` is enabled in tsconfig.json (legacy mode), the
90+ * TypeScript type is automatically inferred for `string`, `number`, `boolean` —
91+ * no need to specify `type` explicitly for those.
5092 *
5193 * @example
5294 * ```typescript
@@ -65,26 +107,46 @@ interface StoredFieldMeta extends SchemaFieldOptions {
65107 * ```
66108 */
67109export function schemaField ( options : SchemaFieldOptions = { } ) {
68- return function ( target : object , propertyKey : string ) {
69- // Read existing field metadata for this class
70- const existing : StoredFieldMeta [ ] =
71- ( Reflect . getOwnMetadata ( SCHEMA_METADATA_KEY , target . constructor ) as StoredFieldMeta [ ] | undefined ) ?? [ ] ;
110+ return function (
111+ targetOrValue : object | undefined ,
112+ propertyKeyOrContext ?: string | { kind : string ; name : string | symbol }
113+ ) : ( ( initialValue : unknown ) => unknown ) | undefined {
114+ if ( isNewDecoratorContext ( propertyKeyOrContext ) ) {
115+ // Stage 3 (TypeScript 5.0+) API: (value, context)
116+ // Return initializer that runs when instance is created; `this` = instance
117+ const propertyKey = String ( propertyKeyOrContext . name ) ;
118+ return function ( this : unknown , initialValue : unknown ) {
119+ const cls = ( this as object ) . constructor as object ;
120+ const processed = schemaFieldProcessed . get ( cls ) ?? new Set < string > ( ) ;
121+ if ( ! processed . has ( propertyKey ) ) {
122+ processed . add ( propertyKey ) ;
123+ schemaFieldProcessed . set ( cls , processed ) ;
124+ let designType : unknown ;
125+ try {
126+ designType = Reflect . getMetadata (
127+ "design:type" ,
128+ this as object ,
129+ propertyKey
130+ ) ;
131+ } catch {
132+ // reflect-metadata may not emit design:type for Stage 3 decorators
133+ }
134+ storeSchemaFieldMetadata ( cls , propertyKey , options , designType ) ;
135+ }
136+ return initialValue ;
137+ } ;
138+ }
72139
73- // Try to infer type from TypeScript metadata
140+ // Legacy (experimentalDecorators) API: (target, propertyKey)
141+ const target = targetOrValue as object ;
142+ const propertyKey = propertyKeyOrContext as string ;
74143 let designType : unknown ;
75144 try {
76145 designType = Reflect . getMetadata ( "design:type" , target , propertyKey ) ;
77146 } catch {
78147 // reflect-metadata not available — user must provide type explicitly
79148 }
80-
81- existing . push ( {
82- ...options ,
83- propertyKey,
84- designType,
85- } ) ;
86-
87- Reflect . defineMetadata ( SCHEMA_METADATA_KEY , existing , target . constructor ) ;
149+ storeSchemaFieldMetadata ( target . constructor as object , propertyKey , options , designType ) ;
88150 } ;
89151}
90152
0 commit comments