Generating a client#

Smithy code generators are implemented as smithy-build plugins that run when the Smithy model is built. The plugins generate the equivalent of packages in their respective languages, configured to be used by language idiomatic tooling.

Add the Codegen Plugin#

TypeScript clients are generated by the typescript-codegen plugin. Configure the plugin by adding it to smithy-build.json:

smithy-build.json#
{
    "version": "1.0",
    "sources": ["model"],
    "plugins": {
        "typescript-codegen": {
            "package": "@weather-service/client",
            "packageVersion": "0.0.1"
        }
    }
}

In this case, we've configured the typescript-codegen plugin to generate a package named @weather-service/client with version 0.0.1.

smithy-build.json#
{
    "version": "1.0",
    "sources": ["model"],
    "maven": {
        "dependencies": [
            "software.amazon.smithy.typescript:smithy-aws-typescript-codegen:0.20.1"
        ]
    },
    "...": "..."
}

Next, add a build-time dependency on the code generator:

build.gradle.kts#
dependencies {
    smithyBuild("software.amazon.smithy.typescript:smithy-aws-typescript-codegen:0.20.1")
}
build.gradle#
dependencies {
    smithyBuild 'software.amazon.smithy.typescript:smithy-aws-typescript-codegen:0.20.1'
}

Important

smithy-aws-typescript-codegen is used here because it provides a protocol generator for the @aws.protocols#restJson1 protocol. As mentioned in Updating the Smithy Model, code generators must know how to generate code for the specified protocol.

Now run smithy build to build the model and generate the code. The TypeScript package is written to the typescript-codegen directory:

.
├── build
│   └── smithy
│       └── source
│           ├── build-info/
│           ├── model/
│           ├── sources/
│           └── typescript-codegen
│               ├── LICENSE
│               ├── package.json
│               ├── src/
│               ├── tsconfig.cjs.json
│               ├── tsconfig.es.json
│               ├── tsconfig.json
│               ├── tsconfig.types.json
│               └── typedoc.json
├── model
│   └── weather.smithy
└── smithy-build.json

Now run gradle build to build the model and generate the code. The TypeScript package is written to the typescript-codegen directory:

.
├── build
│   ├── smithyprojections
│   │   └── weather-service
│   │       └── source
│   │           ├── build-info/
│   │           ├── model/
│   │           ├── sources/
│   │           └── typescript-codegen
│   │               ├── LICENSE
│   │               ├── package.json
│   │               ├── src/
│   │               ├── tsconfig.cjs.json
│   │               ├── tsconfig.es.json
│   │               ├── tsconfig.json
│   │               ├── tsconfig.types.json
│   │               └── typedoc.json
│   └── tmp
├── build.gradle.kts
├── model
│   └── weather.smithy
└── smithy-build.json
.
├── build
│   ├── smithyprojections
│   │   └── weather-service
│   │       └── source
│   │           ├── build-info/
│   │           ├── model/
│   │           ├── sources/
│   │           └── typescript-codegen
│   │               ├── LICENSE
│   │               ├── package.json
│   │               ├── src/
│   │               ├── tsconfig.cjs.json
│   │               ├── tsconfig.es.json
│   │               ├── tsconfig.json
│   │               ├── tsconfig.types.json
│   │               └── typedoc.json
│   └── tmp
├── build.gradle
├── model
│   └── weather.smithy
└── smithy-build.json

Using the generated code#

The generated code is just a normal TypeScript package. Each time the model is built and the code generated, the TypeScript code also has to be compiled. The generated package.json contains scripts to do so:

package.json#
"scripts": {
    "build": "concurrently 'yarn:build:cjs' 'yarn:build:es' 'yarn:build:types'",
    "build:cjs": "tsc -p tsconfig.cjs.json",
    "build:docs": "typedoc",
    "build:es": "tsc -p tsconfig.es.json",
    "build:types": "tsc -p tsconfig.types.json",
    "build:types:downlevel": "downlevel-dts dist-types dist-types/ts3.4",
    "clean": "rimraf ./dist-* && rimraf *.tsbuildinfo",
    "prepack": "yarn run clean && yarn run build"
}

This example creates a mono-repo using Yarn Workspaces that integrates building the Smithy model and generating the code into the development workflow. First, move the Smithy project into its own directory named smithy/:

.
└── smithy
    ├── build
    ├── model
    └── smithy-build.json
.
└── smithy
    ├── build
    ├── build.gradle.kts
    ├── model
    └── smithy-build.json
.
└── smithy
    ├── build
    ├── build.gradle
    ├── model
    └── smithy-build.json

Next, create a package.json in the root of the project with the following contents:

package.json#
{
  "name": "weather-service",
  "scripts": {
    "generate": "cd smithy && gradle clean build",
    "build": "yarn workspace @weather-service/client build",
  },
  "dependencies": {
    "@weather-service/client": "0.0.1"
  },
  "private": true,
  "workspaces": [
    "smithy/build/smithy/source/typescript-codegen"
  ]
}
package.json#
{
  "name": "weather-service",
  "scripts": {
    "generate": "cd smithy && gradle clean build",
    "build": "yarn workspace @weather-service/client build",
  },
  "dependencies": {
    "@weather-service/client": "0.0.1"
  },
  "private": true,
  "workspaces": [
    "smithy/build/smithyprojections/smithy/source/typescript-codegen"
  ]
}

A few things to note:

  • The path under workspaces is the path to the root of the generated TypeScript package.
  • A generate script which builds the model, re-generating the code.
  • The build script compiles the generated TypeScript package, referred to by the name specified in the typescript-codegen plugin configuration in smithy-build.json.
  • A dependency has been added on the generated TypeScript package, using the name and version specified in the typescript-codegen plugin configuration in smithy-build.json

After making model updates, use yarn generate && yarn build to run the code generator and build the generated code. You will have to do this before using the client in this example, because the output directory path has changed after moving the Smithy project into the smithy directory.

Finally, create an app.ts file to use the client:

import {
  GetCityCommandInput,
  GetCityCommandOutput,
  Weather
} from '@weather-service/client';

const client: Weather = new Weather({ endpoint: 'some-endpoint' });

const getCityInput: GetCityCommandInput = {
  cityId: 'foo'
};

client.getCity(getCityInput).then((getCityOutput: GetCityCommandOutput) => {
  // TODO Handle response
});

The typescript-codegen plugin has generated a client, Weather, with methods for each of the operations, as well as types for the inputs and outputs of those operations.