From f49f183f7cdc18c31aedb57123459e0f60e9bdc9 Mon Sep 17 00:00:00 2001 From: pont1s Date: Thu, 4 Jun 2026 12:08:32 +0300 Subject: [PATCH 1/5] fix(typescript): handle nullable types in generated code --- typescript/testdata/catalogue_client.ts | 2 +- typescript/testdata/catalogue_with_classes.ts | 22 +++++++++---------- .../testdata/catalogue_with_types_only.ts | 2 +- typescript/typescript_client.go | 14 ++++++++++-- typescript/typescript_template.go | 4 ++-- 5 files changed, 27 insertions(+), 17 deletions(-) diff --git a/typescript/testdata/catalogue_client.ts b/typescript/testdata/catalogue_client.ts index 1b4614b..6ada606 100644 --- a/typescript/testdata/catalogue_client.ts +++ b/typescript/testdata/catalogue_client.ts @@ -18,7 +18,7 @@ export interface IGroup { title: string, nodes: Array, groups: Array, - child?: IGroup, + child: IGroup | null, sub: ISubGroup } diff --git a/typescript/testdata/catalogue_with_classes.ts b/typescript/testdata/catalogue_with_classes.ts index adf1c56..1c1ceeb 100755 --- a/typescript/testdata/catalogue_with_classes.ts +++ b/typescript/testdata/catalogue_with_classes.ts @@ -18,7 +18,7 @@ export interface IGroup { title: string, nodes: Array, groups: Array, - child?: IGroup, + child: IGroup | null, sub: ISubGroup } @@ -32,38 +32,38 @@ export class Campaign implements ICampaign { static entityName = "campaign"; id: number = 0; - groups: Array = null; + groups: Array = null!; } export class CatalogueFirstParams implements ICatalogueFirstParams { static entityName = "cataloguefirstparams"; - groups: Array = null; + groups: Array = null!; } export class CatalogueSecondParams implements ICatalogueSecondParams { static entityName = "cataloguesecondparams"; - campaigns: Array = null; + campaigns: Array = null!; } export class Group implements IGroup { static entityName = "group"; id: number = 0; - title: string = null; - nodes: Array = null; - groups: Array = null; - child?: IGroup = null; - sub: ISubGroup = null; + title: string = null!; + nodes: Array = null!; + groups: Array = null!; + child: IGroup | null = null; + sub: ISubGroup = null!; } export class SubGroup implements ISubGroup { static entityName = "subgroup"; id: number = 0; - title: string = null; - nodes: Array = null; + title: string = null!; + nodes: Array = null!; } export const factory = (send: any) => ({ diff --git a/typescript/testdata/catalogue_with_types_only.ts b/typescript/testdata/catalogue_with_types_only.ts index 5cdc864..99ca416 100755 --- a/typescript/testdata/catalogue_with_types_only.ts +++ b/typescript/testdata/catalogue_with_types_only.ts @@ -18,7 +18,7 @@ export interface IGroup { title: string, nodes: Array, groups: Array, - child?: IGroup, + child: IGroup | null, sub: ISubGroup } diff --git a/typescript/typescript_client.go b/typescript/typescript_client.go index 09cfb61..5805ff9 100644 --- a/typescript/typescript_client.go +++ b/typescript/typescript_client.go @@ -74,7 +74,7 @@ type Type struct { Name string Comment string Type string - Optional bool + Nullable bool HasDefault bool Default *string } @@ -147,6 +147,10 @@ func (t Type) DefaultTmpl() string { } } + if result == "null" && !t.Nullable { + result = "null!" + } + return result } @@ -298,7 +302,7 @@ func convertTSType(models *tsModels, interfacesCache map[string]interface{}, in Name: in.Name, Comment: comment, Type: convertTSScalar(in.Type), - Optional: in.Optional, + Nullable: in.Optional, } // detect array sub type @@ -339,6 +343,12 @@ func convertTSType(models *tsModels, interfacesCache map[string]interface{}, in }, typeMapper) } + // Append `| null` for nullable fields so the type reflects what the API actually + // sends (Go marshals nil pointers/slices as JSON null, not as an absent key). + if result.Nullable { + result.Type += " | null" + } + // apply hook if typeMapper != nil { result = typeMapper(in, result) diff --git a/typescript/typescript_template.go b/typescript/typescript_template.go index f7765a9..43d7bf3 100644 --- a/typescript/typescript_template.go +++ b/typescript/typescript_template.go @@ -6,7 +6,7 @@ const client = `/* Code generated from jsonrpc schema by rpcgen v{{ .Version }} export interface {{ .Name }} { {{- $len := len .Parameters }} {{- range $i, $e := .Parameters }} - {{ .Name }}{{ if .Optional }}?{{ end }}: {{ .Type }}{{ if ne $i $len }},{{ end }}{{ if ne .Comment "" }} // {{ .Comment }} + {{ .Name }}: {{ .Type }}{{ if ne $i $len }},{{ end }}{{ if ne .Comment "" }} // {{ .Comment }} {{- end }} {{- end }} } @@ -18,7 +18,7 @@ export class {{ .ModelName }} implements {{ .Name }} { static entityName = "{{ .EntityNameTmpl }}"; {{ $len := len .Parameters }} {{- range $i,$e := .Parameters }} - {{ .Name }}{{ if .Optional }}?{{ end }}: {{ .Type }} = {{ .DefaultTmpl }}; + {{ .Name }}: {{ .Type }} = {{ .DefaultTmpl }}; {{- end }} } {{ end }} From c0d4aafb8248895266aded29a9fb1f64d01e5ec5 Mon Sep 17 00:00:00 2001 From: pont1s Date: Thu, 4 Jun 2026 12:23:10 +0300 Subject: [PATCH 2/5] fix(typescript): handle nullable types in generated code #2 --- typescript/typescript_client.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/typescript/typescript_client.go b/typescript/typescript_client.go index 5805ff9..b1ceada 100644 --- a/typescript/typescript_client.go +++ b/typescript/typescript_client.go @@ -343,8 +343,6 @@ func convertTSType(models *tsModels, interfacesCache map[string]interface{}, in }, typeMapper) } - // Append `| null` for nullable fields so the type reflects what the API actually - // sends (Go marshals nil pointers/slices as JSON null, not as an absent key). if result.Nullable { result.Type += " | null" } From 5db0d60cff6091da1012f85733092386f8d23d67 Mon Sep 17 00:00:00 2001 From: pont1s Date: Fri, 5 Jun 2026 13:22:46 +0300 Subject: [PATCH 3/5] fix(typescript): keep nullable fields optional in requests --- typescript/testdata/catalogue_client.ts | 2 +- typescript/testdata/catalogue_with_classes.ts | 4 ++-- typescript/testdata/catalogue_with_types_only.ts | 2 +- typescript/typescript_template.go | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/typescript/testdata/catalogue_client.ts b/typescript/testdata/catalogue_client.ts index 6ada606..c91e189 100644 --- a/typescript/testdata/catalogue_client.ts +++ b/typescript/testdata/catalogue_client.ts @@ -18,7 +18,7 @@ export interface IGroup { title: string, nodes: Array, groups: Array, - child: IGroup | null, + child?: IGroup | null, sub: ISubGroup } diff --git a/typescript/testdata/catalogue_with_classes.ts b/typescript/testdata/catalogue_with_classes.ts index 1c1ceeb..49d7da0 100755 --- a/typescript/testdata/catalogue_with_classes.ts +++ b/typescript/testdata/catalogue_with_classes.ts @@ -18,7 +18,7 @@ export interface IGroup { title: string, nodes: Array, groups: Array, - child: IGroup | null, + child?: IGroup | null, sub: ISubGroup } @@ -54,7 +54,7 @@ export class Group implements IGroup { title: string = null!; nodes: Array = null!; groups: Array = null!; - child: IGroup | null = null; + child?: IGroup | null = null; sub: ISubGroup = null!; } diff --git a/typescript/testdata/catalogue_with_types_only.ts b/typescript/testdata/catalogue_with_types_only.ts index 99ca416..31e6d3e 100755 --- a/typescript/testdata/catalogue_with_types_only.ts +++ b/typescript/testdata/catalogue_with_types_only.ts @@ -18,7 +18,7 @@ export interface IGroup { title: string, nodes: Array, groups: Array, - child: IGroup | null, + child?: IGroup | null, sub: ISubGroup } diff --git a/typescript/typescript_template.go b/typescript/typescript_template.go index 43d7bf3..5374479 100644 --- a/typescript/typescript_template.go +++ b/typescript/typescript_template.go @@ -6,7 +6,7 @@ const client = `/* Code generated from jsonrpc schema by rpcgen v{{ .Version }} export interface {{ .Name }} { {{- $len := len .Parameters }} {{- range $i, $e := .Parameters }} - {{ .Name }}: {{ .Type }}{{ if ne $i $len }},{{ end }}{{ if ne .Comment "" }} // {{ .Comment }} + {{ .Name }}{{ if .Nullable }}?{{ end }}: {{ .Type }}{{ if ne $i $len }},{{ end }}{{ if ne .Comment "" }} // {{ .Comment }} {{- end }} {{- end }} } @@ -18,7 +18,7 @@ export class {{ .ModelName }} implements {{ .Name }} { static entityName = "{{ .EntityNameTmpl }}"; {{ $len := len .Parameters }} {{- range $i,$e := .Parameters }} - {{ .Name }}: {{ .Type }} = {{ .DefaultTmpl }}; + {{ .Name }}{{ if .Nullable }}?{{ end }}: {{ .Type }} = {{ .DefaultTmpl }}; {{- end }} } {{ end }} From 88923a90fef04fb9752aa959c11a8b9deb9c9258 Mon Sep 17 00:00:00 2001 From: pont1s Date: Mon, 15 Jun 2026 18:05:48 +0300 Subject: [PATCH 4/5] fix(typescript): revet optional to nullable rename --- typescript/typescript_client.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/typescript/typescript_client.go b/typescript/typescript_client.go index b1ceada..a16c044 100644 --- a/typescript/typescript_client.go +++ b/typescript/typescript_client.go @@ -74,7 +74,7 @@ type Type struct { Name string Comment string Type string - Nullable bool + Optional bool HasDefault bool Default *string } @@ -147,7 +147,7 @@ func (t Type) DefaultTmpl() string { } } - if result == "null" && !t.Nullable { + if result == "null" && !t.Optional { result = "null!" } @@ -302,7 +302,7 @@ func convertTSType(models *tsModels, interfacesCache map[string]interface{}, in Name: in.Name, Comment: comment, Type: convertTSScalar(in.Type), - Nullable: in.Optional, + Optional: in.Optional, } // detect array sub type @@ -343,7 +343,7 @@ func convertTSType(models *tsModels, interfacesCache map[string]interface{}, in }, typeMapper) } - if result.Nullable { + if result.Optional { result.Type += " | null" } From 2c396e1b21f72d538d59a8b79ab16153eef78730 Mon Sep 17 00:00:00 2001 From: pont1s Date: Mon, 15 Jun 2026 18:07:08 +0300 Subject: [PATCH 5/5] fix(typescript): revet optional to nullable rename #2 --- typescript/typescript_template.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typescript/typescript_template.go b/typescript/typescript_template.go index 5374479..f7765a9 100644 --- a/typescript/typescript_template.go +++ b/typescript/typescript_template.go @@ -6,7 +6,7 @@ const client = `/* Code generated from jsonrpc schema by rpcgen v{{ .Version }} export interface {{ .Name }} { {{- $len := len .Parameters }} {{- range $i, $e := .Parameters }} - {{ .Name }}{{ if .Nullable }}?{{ end }}: {{ .Type }}{{ if ne $i $len }},{{ end }}{{ if ne .Comment "" }} // {{ .Comment }} + {{ .Name }}{{ if .Optional }}?{{ end }}: {{ .Type }}{{ if ne $i $len }},{{ end }}{{ if ne .Comment "" }} // {{ .Comment }} {{- end }} {{- end }} } @@ -18,7 +18,7 @@ export class {{ .ModelName }} implements {{ .Name }} { static entityName = "{{ .EntityNameTmpl }}"; {{ $len := len .Parameters }} {{- range $i,$e := .Parameters }} - {{ .Name }}{{ if .Nullable }}?{{ end }}: {{ .Type }} = {{ .DefaultTmpl }}; + {{ .Name }}{{ if .Optional }}?{{ end }}: {{ .Type }} = {{ .DefaultTmpl }}; {{- end }} } {{ end }}