diff --git a/packages/emitter/src/emitter.ts b/packages/emitter/src/emitter.ts index 54c04e5..78d74bc 100644 --- a/packages/emitter/src/emitter.ts +++ b/packages/emitter/src/emitter.ts @@ -300,6 +300,7 @@ export class TyplexEmitter { ? getDoc(this.program, model) : undefined; const required: string[] = []; + const nullable: string[] = []; const properties: any = {}; for (const [name, prop] of model.properties) { @@ -307,7 +308,28 @@ export class TyplexEmitter { required.push(name); } - const propDef = this.typeToLexiconDefinition(prop.type, prop); + // Check if property type is a union with null + let typeToProcess = prop.type; + if (prop.type.kind === "Union") { + const unionType = prop.type as Union; + const variants = Array.from(unionType.variants.values()); + + // Check if null is one of the variants + const hasNull = variants.some(v => v.type.kind === "Intrinsic" && (v.type as any).name === "null"); + + if (hasNull) { + // Mark this property as nullable + nullable.push(name); + + // Find the non-null variant + const nonNullVariant = variants.find(v => !(v.type.kind === "Intrinsic" && (v.type as any).name === "null")); + if (nonNullVariant) { + typeToProcess = nonNullVariant.type; + } + } + } + + const propDef = this.typeToLexiconDefinition(typeToProcess, prop); if (propDef) { properties[name] = propDef; } @@ -326,6 +348,10 @@ export class TyplexEmitter { obj.required = required; } + if (nullable.length > 0) { + obj.nullable = nullable; + } + obj.properties = properties; return obj; diff --git a/packages/example/.gitignore b/packages/example/.gitignore index 6cd8161..c451bf6 100644 --- a/packages/example/.gitignore +++ b/packages/example/.gitignore @@ -2,7 +2,7 @@ node_modules/ # Generated lexicons -lexicons/ +lexicon/ # TypeSpec output (if using default config) tsp-output/ diff --git a/packages/example/lexicon/app/example/defs.json b/packages/example/lexicon/app/example/defs.json deleted file mode 100644 index 0bf39cb..0000000 --- a/packages/example/lexicon/app/example/defs.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "lexicon": 1, - "id": "app.example.defs", - "defs": { - "postRef": { - "type": "object", - "description": "Reference to a post", - "required": [ - "uri", - "cid" - ], - "properties": { - "uri": { - "type": "string", - "description": "AT URI of the post" - }, - "cid": { - "type": "string", - "description": "CID of the post" - } - } - }, - "replyRef": { - "type": "object", - "description": "Reference to a parent post in a reply chain", - "required": [ - "root", - "parent" - ], - "properties": { - "root": { - "type": "ref", - "ref": "#postRef", - "description": "Root post in the thread" - }, - "parent": { - "type": "ref", - "ref": "#postRef", - "description": "Direct parent post being replied to" - } - } - }, - "entity": { - "type": "object", - "description": "Text entity (mention, link, or tag)", - "required": [ - "start", - "end", - "type", - "value" - ], - "properties": { - "start": { - "type": "integer", - "description": "Start index in text" - }, - "end": { - "type": "integer", - "description": "End index in text" - }, - "type": { - "type": "string", - "description": "Entity type" - }, - "value": { - "type": "string", - "description": "Entity value (handle, URL, or tag)" - } - } - } - } -} diff --git a/packages/example/lexicon/app/example/follow.json b/packages/example/lexicon/app/example/follow.json deleted file mode 100644 index 5390841..0000000 --- a/packages/example/lexicon/app/example/follow.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "lexicon": 1, - "id": "app.example.follow", - "defs": { - "main": { - "type": "object", - "required": [ - "subject", - "createdAt" - ], - "properties": { - "subject": { - "type": "string", - "description": "DID of the account being followed" - }, - "createdAt": { - "type": "string", - "format": "datetime", - "description": "When the follow was created" - } - } - } - }, - "description": "A follow relationship" -} diff --git a/packages/example/lexicon/app/example/like.json b/packages/example/lexicon/app/example/like.json deleted file mode 100644 index 77739ac..0000000 --- a/packages/example/lexicon/app/example/like.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "lexicon": 1, - "id": "app.example.like", - "defs": { - "main": { - "type": "object", - "required": [ - "subject", - "createdAt" - ], - "properties": { - "subject": { - "type": "ref", - "ref": "app.example.defs#postRef", - "description": "Post being liked" - }, - "createdAt": { - "type": "string", - "format": "datetime", - "description": "When the like was created" - } - } - } - }, - "description": "A like on a post" -} diff --git a/packages/example/lexicon/app/example/post.json b/packages/example/lexicon/app/example/post.json deleted file mode 100644 index e96feff..0000000 --- a/packages/example/lexicon/app/example/post.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "lexicon": 1, - "id": "app.example.post", - "defs": { - "main": { - "type": "object", - "required": [ - "text", - "createdAt" - ], - "properties": { - "text": { - "type": "string", - "description": "Post text content" - }, - "createdAt": { - "type": "string", - "format": "datetime", - "description": "Creation timestamp" - }, - "langs": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Languages the post is written in" - }, - "entities": { - "type": "array", - "items": { - "type": "ref", - "ref": "app.example.defs#entity" - }, - "description": "Referenced entities in the post" - }, - "reply": { - "type": "ref", - "ref": "app.example.defs#replyRef", - "description": "Post the user is replying to" - } - } - } - }, - "description": "A post in the feed" -} diff --git a/packages/example/lexicon/app/example/profile.json b/packages/example/lexicon/app/example/profile.json deleted file mode 100644 index bd2d5d9..0000000 --- a/packages/example/lexicon/app/example/profile.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "lexicon": 1, - "id": "app.example.profile", - "defs": { - "main": { - "type": "object", - "properties": { - "displayName": { - "type": "string", - "description": "Display name" - }, - "description": { - "type": "string", - "description": "Profile description" - }, - "avatar": { - "type": "string", - "description": "Profile avatar image" - }, - "banner": { - "type": "string", - "description": "Profile banner image" - } - } - } - }, - "description": "User profile information" -} diff --git a/packages/example/lexicon/app/example/repost.json b/packages/example/lexicon/app/example/repost.json deleted file mode 100644 index 5ca59b1..0000000 --- a/packages/example/lexicon/app/example/repost.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "lexicon": 1, - "id": "app.example.repost", - "defs": { - "main": { - "type": "object", - "required": [ - "subject", - "createdAt" - ], - "properties": { - "subject": { - "type": "ref", - "ref": "app.example.defs#postRef", - "description": "Post being reposted" - }, - "createdAt": { - "type": "string", - "format": "datetime", - "description": "When the repost was created" - } - } - } - }, - "description": "A repost of another post" -}