Skip to content

Commit

Permalink
feat(typescript): Add support explicit null values (#5612)
Browse files Browse the repository at this point in the history
  • Loading branch information
amckinney authored Jan 15, 2025
1 parent 80a4949 commit 61f3405
Show file tree
Hide file tree
Showing 1,749 changed files with 16,551 additions and 14,453 deletions.
3 changes: 3 additions & 0 deletions fern/pages/changelogs/cli/2025-01-15.mdx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 0.50.0
**`(internal):`** The CLI is capable of migrating the latest TypeScript generator to IRv55.

## 0.49.1
**`(fix):`** The OpenAPI v2 parser now supports `x-fern-global-headers` and fixes an issue with generating webhook content.

Expand Down
3 changes: 3 additions & 0 deletions fern/pages/changelogs/ts-express/2025-01-15.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.17.7
**`(internal):`** Upgrade to IRv55.

2 changes: 1 addition & 1 deletion generators/typescript/express/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
},
"devDependencies": {
"@fern-fern/generator-exec-sdk": "^0.0.898",
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/abstract-generator-cli": "workspace:*",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
"dependencies": {
"@fern-api/core-utils": "workspace:*",
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/abstract-schema-generator": "workspace:*",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ export class GeneratedExpressEndpointTypeSchemasImpl implements GeneratedExpress
if (this.endpoint.response.body?.type === "text") {
throw new Error("Text response is not supported");
}
if (this.endpoint.response.body?.type === "bytes") {
throw new Error("Bytes response is not supported");
}

if (!this.includeSerdeLayer) {
return referenceToParsedResponse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"depcheck": "depcheck"
},
"dependencies": {
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/abstract-error-class-generator": "workspace:*",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
"dependencies": {
"@fern-api/core-utils": "workspace:*",
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/abstract-schema-generator": "workspace:*",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"depcheck": "depcheck"
},
"dependencies": {
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"depcheck": "depcheck"
},
"dependencies": {
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/abstract-schema-generator": "workspace:*",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"depcheck": "depcheck"
},
"dependencies": {
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
"@fern-typescript/resolvers": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"depcheck": "depcheck"
},
"dependencies": {
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
"@fern-typescript/resolvers": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
HttpRequestBody,
HttpResponseBody,
HttpService,
JsonResponse,
Package,
PathParameter
} from "@fern-fern/ir-sdk/api";
Expand Down Expand Up @@ -928,6 +929,9 @@ export class GeneratedExpressServiceImpl implements GeneratedExpressService {
private getResponseBodyType(response: HttpResponseBody, context: ExpressContext): ts.TypeNode {
return HttpResponseBody._visit<ts.TypeNode>(response, {
json: (jsonResponse) => context.type.getReferenceToType(jsonResponse.responseBodyType).typeNode,
bytes: () => {
throw new Error("bytes is not supported");
},
streaming: () => {
throw new Error("Streaming is not supported");
},
Expand Down
2 changes: 1 addition & 1 deletion generators/typescript/express/generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"@fern-api/core-utils": "workspace:*",
"@fern-api/fs-utils": "workspace:*",
"@fern-api/logger": "workspace:*",
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
"@fern-typescript/express-endpoint-type-schemas-generator": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,55 @@ export class TypeContextImpl implements TypeContext {
public getGeneratedExample(example: ExampleTypeReference): GeneratedTypeReferenceExample {
return this.typeReferenceExampleGenerator.generateExample(example);
}

public isOptional(typeReference: TypeReference): boolean {
switch (typeReference.type) {
case "named": {
const typeDeclaration = this.typeResolver.getTypeDeclarationFromId(typeReference.typeId);
switch (typeDeclaration.shape.type) {
case "alias":
return this.isOptional(typeDeclaration.shape.aliasOf);
default:
return false;
}
}
case "container":
switch (typeReference.container.type) {
case "nullable":
return this.isOptional(typeReference.container.nullable);
case "optional":
return true;
default:
return false;
}
default:
return false;
}
}

public isNullable(typeReference: TypeReference): boolean {
switch (typeReference.type) {
case "named": {
const typeDeclaration = this.typeResolver.getTypeDeclarationFromId(typeReference.typeId);
switch (typeDeclaration.shape.type) {
case "alias":
return this.isNullable(typeDeclaration.shape.aliasOf);
default:
return false;
}
}
case "container": {
switch (typeReference.container.type) {
case "nullable":
return true;
case "optional":
return this.isNullable(typeReference.container.optional);
default:
return false;
}
}
default:
return false;
}
}
}
6 changes: 6 additions & 0 deletions generators/typescript/express/versions.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
- changelogEntry:
- summary: Upgrade to IRv55.
type: internal
irVersion: 55
version: 0.17.7

- changelogEntry:
- summary: Re-release the `0.17.5` version.
type: feat
Expand Down
2 changes: 1 addition & 1 deletion generators/typescript/model/type-generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"depcheck": "depcheck"
},
"dependencies": {
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
"@fern-typescript/union-generator": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"depcheck": "depcheck"
},
"dependencies": {
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-api/core-utils": "workspace:*",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export abstract class AbstractTypeReferenceConverter<T> {
list: (type) => this.list(type, setGenericIn(params, genericIn.List)),
set: (type) => this.set(type, setGenericIn(params, genericIn.Set)),
optional: (type) => this.optional(type, params),
nullable: (type) => this.nullable(type, params),
literal: (type) => this.literal(type, params),
_other: () => {
throw new Error("Unexpected container type: " + container.type);
Expand All @@ -149,6 +150,7 @@ export abstract class AbstractTypeReferenceConverter<T> {
protected abstract list(itemType: TypeReference, params: ConvertTypeReferenceParams): T;
protected abstract set(itemType: TypeReference, params: ConvertTypeReferenceParams): T;
protected abstract optional(itemType: TypeReference, params: ConvertTypeReferenceParams): T;
protected abstract nullable(itemType: TypeReference, params: ConvertTypeReferenceParams): T;
protected abstract literal(literal: Literal, params: ConvertTypeReferenceParams): T;
protected abstract unknown(): T;
protected abstract any(): T;
Expand Down Expand Up @@ -197,6 +199,14 @@ export abstract class AbstractTypeReferenceConverter<T> {
return false;
}

public isTypeReferenceOptional(typeReference: TypeReference): boolean {
return this.context.type.isOptional(typeReference);
}

public isTypeReferenceNullable(typeReference: TypeReference): boolean {
return this.context.type.isNullable(typeReference);
}

protected generateNonOptionalTypeReferenceNode(typeNode: ts.TypeNode): TypeReferenceNode {
return {
isOptional: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,12 @@ export abstract class AbstractTypeReferenceToTypeNodeConverter extends AbstractT
return this.generateNonOptionalTypeReferenceNode(ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword));
}

protected override nullable(itemType: TypeReference, params: ConvertTypeReferenceParams): TypeReferenceNode {
return this.generateNonOptionalTypeReferenceNode(
this.addNullToTypeNode(this.convert({ ...params, typeReference: itemType }).typeNode)
);
}

protected override optional(itemType: TypeReference, params: ConvertTypeReferenceParams): TypeReferenceNode {
const referencedToValueType = this.convert({ ...params, typeReference: itemType }).typeNode;
return {
Expand All @@ -170,6 +176,10 @@ export abstract class AbstractTypeReferenceToTypeNodeConverter extends AbstractT
};
}

private addNullToTypeNode(typeNode: ts.TypeNode): ts.TypeNode {
return ts.factory.createUnionTypeNode([typeNode, ts.factory.createLiteralTypeNode(ts.factory.createNull())]);
}

private addUndefinedToTypeNode(typeNode: ts.TypeNode): ts.TypeNode {
return ts.factory.createUnionTypeNode([
typeNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ export class TypeReferenceToRawTypeNodeConverter extends AbstractTypeReferenceTo
};
}

protected override nullable(itemType: TypeReference, params: ConvertTypeReferenceParams): TypeReferenceNode {
return this.generateNonOptionalTypeReferenceNode(
ts.factory.createUnionTypeNode([
this.convert({ ...params, typeReference: itemType }).typeNode,
ts.factory.createLiteralTypeNode(ts.factory.createNull())
])
);
}

protected override dateTime(): TypeReferenceNode {
return this.string();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export class TypeReferenceToSchemaConverter extends AbstractTypeReferenceConvert
return this.zurg.date();
}

protected override nullable(itemType: TypeReference, params: ConvertTypeReferenceParams): Zurg.Schema {
return this.convert({ ...params, typeReference: itemType }).nullable();
}

protected override optional(itemType: TypeReference, params: ConvertTypeReferenceParams): Zurg.Schema {
return this.convert({ ...params, typeReference: itemType }).optional();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,25 @@ export class TypeReferenceToStringExpressionConverter extends AbstractTypeRefere
public convertWithNullCheckIfOptional(
params: ConvertTypeReferenceParams
): (reference: ts.Expression) => ts.Expression {
const type = params.typeReference;
const isNullable = TypeReference._visit(type, {
named: (typeName) => {
const resolvedType = this.context.type.resolveTypeName(typeName);
return resolvedType.type === "container" && resolvedType.container.type === "optional";
},
container: (container) => container.type === "optional",
primitive: () => false,
unknown: () => true,
_other: () => {
throw new Error("Unknown TypeReference: " + type.type);
}
});

if (!isNullable) {
const isNullable = this.isTypeReferenceNullable(params.typeReference);
const isOptional = this.isTypeReferenceOptional(params.typeReference);
if (!isNullable && !isOptional) {
return this.convert(params);
}

if (isNullable) {
return (reference) =>
ts.factory.createConditionalExpression(
ts.factory.createBinaryExpression(
reference,
ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken),
ts.factory.createIdentifier("undefined")
),
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
this.convert(params)(reference),
ts.factory.createToken(ts.SyntaxKind.ColonToken),
ts.factory.createIdentifier("undefined")
);
}
return (reference) =>
ts.factory.createConditionalExpression(
ts.factory.createBinaryExpression(
Expand Down Expand Up @@ -79,6 +80,7 @@ export class TypeReferenceToStringExpressionConverter extends AbstractTypeRefere
ContainerType._visit(containerType, {
list: () => this.jsonStringify(mapExpression),
optional: (optional) => getStringify(this.context.type.resolveTypeReference(optional)),
nullable: (nullable) => getStringify(this.context.type.resolveTypeReference(nullable)),
set: () => this.jsonStringify(mapExpression),
map: () => this.jsonStringify(mapExpression),
literal: (literal) => {
Expand Down Expand Up @@ -114,6 +116,7 @@ export class TypeReferenceToStringExpressionConverter extends AbstractTypeRefere
container: (containerType) =>
ContainerType._visit(containerType, {
list: this.list.bind(this),
nullable: (nullableType) => this.nullable(nullableType, params),
optional: (optionalType) => this.optional(optionalType, params),
set: this.set.bind(this),
map: (mapType) => this.map(mapType, params),
Expand Down Expand Up @@ -197,6 +200,13 @@ export class TypeReferenceToStringExpressionConverter extends AbstractTypeRefere
return (reference) => reference;
}

protected override nullable(
itemType: TypeReference,
params: ConvertTypeReferenceParams
): (reference: ts.Expression) => ts.Expression {
return (reference) => this.convert({ ...params, typeReference: itemType })(reference);
}

protected override optional(
itemType: TypeReference,
params: ConvertTypeReferenceParams
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
"dependencies": {
"@fern-api/core-utils": "workspace:*",
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
"ts-morph": "^15.1.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ export class GeneratedTypeReferenceExampleImpl implements GeneratedTypeReference
),
true
),
nullable: (exampleItem) =>
exampleItem.nullable != null
? this.buildExample({ example: exampleItem.nullable, context, opts })
: ts.factory.createIdentifier("null"),
optional: (exampleItem) =>
exampleItem.optional != null
? this.buildExample({ example: exampleItem.optional, context, opts })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"depcheck": "depcheck"
},
"dependencies": {
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/abstract-schema-generator": "workspace:*",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion generators/typescript/model/union-generator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
"dependencies": {
"@fern-api/core-utils": "workspace:*",
"@fern-fern/ir-sdk": "53.23.0",
"@fern-fern/ir-sdk": "55.0.0",
"@fern-typescript/commons": "workspace:*",
"@fern-typescript/contexts": "workspace:*",
"ts-morph": "^15.1.0"
Expand Down
Loading

0 comments on commit 61f3405

Please sign in to comment.