@@ -7,6 +7,7 @@ import type {
77 RecordModelType ,
88 Scalar ,
99 Type ,
10+ Union ,
1011} from "@typespec/compiler" ;
1112import {
1213 type EmitContext ,
@@ -18,7 +19,7 @@ import type { Attribute, CustomAttribute, Schema } from "electrodb";
1819import * as ts from "typescript" ;
1920
2021import { StateKeys } from "./lib.js" ;
21- import { stringifyObject } from "./stringify.js" ;
22+ import { RawCode , stringifyObject } from "./stringify.js" ;
2223
2324function emitIntrinsincScalar ( type : Scalar ) {
2425 switch ( type . name ) {
@@ -109,6 +110,105 @@ function emitEnumModel(type: Enum): Attribute {
109110 } ;
110111}
111112
113+ function emitTypeToTypeScript ( type : Type ) : string {
114+ switch ( type . kind ) {
115+ case "Scalar" : {
116+ let baseType = type ;
117+ while ( baseType . baseScalar ) {
118+ baseType = baseType . baseScalar ;
119+ }
120+ switch ( baseType . name ) {
121+ case "boolean" :
122+ return "boolean" ;
123+ case "numeric" :
124+ case "integer" :
125+ case "float" :
126+ case "int64" :
127+ case "int32" :
128+ case "int16" :
129+ case "int8" :
130+ case "uint64" :
131+ case "uint32" :
132+ case "uint16" :
133+ case "uint8" :
134+ case "safeint" :
135+ case "float32" :
136+ case "float64" :
137+ case "decimal" :
138+ case "decimal128" :
139+ return "number" ;
140+ default :
141+ return "string" ;
142+ }
143+ }
144+ case "Model" : {
145+ if ( type . name === "Array" ) {
146+ const arrayType = type as ArrayModelType ;
147+ return `${ emitTypeToTypeScript ( arrayType . indexer . value ) } []` ;
148+ }
149+ const properties : string [ ] = [ ] ;
150+ for ( const prop of walkPropertiesInherited ( type as RecordModelType ) ) {
151+ const optional = prop . optional ? "?" : "" ;
152+ properties . push (
153+ `${ prop . name } ${ optional } : ${ emitTypeToTypeScript ( prop . type ) } ` ,
154+ ) ;
155+ }
156+ return `{ ${ properties . join ( "; " ) } }` ;
157+ }
158+ case "Enum" : {
159+ const values = Array . from ( type . members )
160+ . map ( ( [ key , member ] ) => `"${ member . value ?? key } "` )
161+ . join ( " | " ) ;
162+ return values ;
163+ }
164+ case "Union" : {
165+ const variants = Array . from ( type . variants . values ( ) )
166+ . map ( ( variant ) => emitTypeToTypeScript ( variant . type ) )
167+ . join ( " | " ) ;
168+ return variants ;
169+ }
170+ default :
171+ return "any" ;
172+ }
173+ }
174+
175+ function isLiteralUnion ( type : Union ) : string [ ] | null {
176+ const literals : string [ ] = [ ] ;
177+
178+ for ( const variant of type . variants . values ( ) ) {
179+ // Check if this variant is a string or number literal
180+ if ( variant . type . kind === "String" ) {
181+ literals . push ( variant . type . value ) ;
182+ } else if ( variant . type . kind === "Number" ) {
183+ literals . push ( String ( variant . type . value ) ) ;
184+ } else {
185+ // Not a literal union, return null
186+ return null ;
187+ }
188+ }
189+
190+ return literals ;
191+ }
192+
193+ function emitUnion ( type : Union ) : Attribute {
194+ // Check if this is a simple literal union (e.g., "home" | "work" | "other")
195+ const literals = isLiteralUnion ( type ) ;
196+ if ( literals ) {
197+ // Emit as enum-like array, similar to how named enums are handled
198+ return {
199+ type : literals ,
200+ } ;
201+ }
202+
203+ // Complex union - use CustomAttributeType
204+ const tsType = emitTypeToTypeScript ( type ) ;
205+ // RawCode is used to emit the CustomAttributeType function call as-is
206+ return {
207+ // @ts -expect-error - RawCode is handled by stringifyObject at code generation time
208+ type : new RawCode ( `CustomAttributeType<${ tsType } >("any")` ) ,
209+ } ;
210+ }
211+
112212function emitType ( type : Type ) : Attribute {
113213 switch ( type . kind ) {
114214 case "Scalar" :
@@ -118,7 +218,7 @@ function emitType(type: Type): Attribute {
118218 case "Enum" :
119219 return emitEnumModel ( type ) ;
120220 case "Union" :
121- return { type : "string" } ;
221+ return emitUnion ( type ) ;
122222 default :
123223 throw new Error ( `Type kind ${ type . kind } is currently not supported!` ) ;
124224 }
@@ -219,13 +319,23 @@ export async function $onEmit(context: EmitContext) {
219319 } ;
220320 }
221321
222- const typescriptSource = Object . entries ( entities )
322+ const entityDefinitions = Object . entries ( entities )
223323 . map (
224324 ( [ name , schema ] ) =>
225325 `export const ${ name } = ${ stringifyObject ( schema as unknown as Record < string , unknown > ) } as const` ,
226326 )
227327 . join ( "\n" ) ;
228328
329+ // Add CustomAttributeType import if any union types are used
330+ const hasCustomAttributeType = entityDefinitions . includes (
331+ "CustomAttributeType" ,
332+ ) ;
333+ const imports = hasCustomAttributeType
334+ ? 'import { CustomAttributeType } from "electrodb";\n\n'
335+ : "" ;
336+
337+ const typescriptSource = imports + entityDefinitions ;
338+
229339 const declarations = await ts . transpileDeclaration ( typescriptSource , { } ) ;
230340 const javascript = await ts . transpileModule ( typescriptSource , { } ) ;
231341
0 commit comments