-
Notifications
You must be signed in to change notification settings - Fork 5
Getting Started: Typescript
π¨ This documentation is a WIP. Please feel free to suggest improvements or share feedback. We want to make getting started painless.
This tutorial assumes you've already set up a monorepo for your project, for example:
.
βββ client
β βββ src
β β βββ components
β β βββ index.ts
β βββ public
β βββ ...
βββ server
β βββ src
β β βββ services
β β βββ validators
β β βββ index.ts
β βββ ...
βββ shared
βββ src
β βββ types
β βββ utils
β βββ index.ts
βββ ...
Tempo utilizes decorators. As such, you'll need to set experimentalDecorators
and emitDecoratorMetadata
to true
in your tsconfig.json:
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
"esModuleInterop": true,
"strict": true,
"outDir": "dist",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["server", "client", "shared"]
}
Tempo uses Bebop for generating client and service code. To install it, follow the commands below:
yarn add bebop
yarn add bebop-tools --dev
By default, bebopc
produces client and server code in the same output. You'll need to add the following dependencies to your project:
yarn add @tempojs/client @tempojs/server
If your client and server code lives in the same project, you can install Tempo into it.
It is also recommended that you install the VSCode extension for Bebop. You can get it here.
Next, create a 'bebop.json' file in the same directory as your 'package.json' and configure it in accordance with your project:
{
"inputFiles": [
"./shared/greeter.bop"
],
"generators": [
{
"alias": "ts",
"outputFile": "./shared/index.ts"
}
]
}
a schema for 'bebop.json' is in development; you can find it here. Please note it is subject to change.
Now, you can create your first service. Create a 'greeter.bop' file in your shared project with the following contents:
/**
* `HelloRequest` is a struct representing a request to the Greeter service.
*/
struct HelloRequest {
/**
* The name to be used in the greeting, of type string.
*/
string name;
}
/**
* `HelloResponse` is a struct representing the response from the Greeter service.
*/
struct HelloResponse {
/**
* The greeting message generated by the service, of type string.
*/
string serviceMessage;
}
/**
* `Greeter` is a service that provides a method for generating greeting messages.
*/
service Greeter {
/**
* `sayHello` is a method that takes a `HelloRequest` and returns a `HelloResponse`.
*/
sayHello(HelloRequest): HelloResponse;
}
For more information on writing 'bops' you can find documentation here
Now, just run yarn bebopc
and it will generate all the shared code into index.ts
. A watch
feature is in development for bebopc
so as you make changes to the schema code will be regenerated; for now you'll need to regenerate when you're done editing or use another mechanism to trigger bebopc
The Bebop compiler produces concrete client implementations and abstract base service for you to implement. For example:
// registers 'GreeterService' with the 'TempoServiceRegistry'
// we use 'BaseGreeterService.serviceName' as this is how
// the service is referenced by generated code
@TempoServiceRegistry.register(BaseGreeterService.serviceName)
export class GreeterService extends BaseGreeterService {
public sayHello(
record: IHelloRequest,
context: ServerContext
): Promise<IHelloResponse> {
return Promise.resolve({ serviceMessage: `Hello ${record.name}` });
}
}
A service that you wish to expose to clients must be registered using the TempoServiceRegistry.register
decorator as shown above. The parameter passed to TempoServiceRegistry.register
is always the serviceName
present on the base class of the service. Failure to register a service that was defined in a schema will result in a runtime error when initializing the server. At this time it is recommended that you reexport all services under a single export:
//index.ts
import { GreeterService } from './greeeter'
export {
GreeterService
}
Check out the official template here.
If you don't a serverless platform such as Cloudflare Workers to test on, it is still possible to start developing using Node's HTTP server. You can also try this example online here
First, install the Tempo router for Node into your project
yarn add @tempojs/node-http-router
Next, inside your index.ts
(or wherever your server handler resides) do the following steps:
- Import your services
import * as Services from './services';
- Add this line after your import call
typeof Services;
This is a temporary workaround to decorators not being triggered unless there is a reference made to them\
Now you're ready to create the router:
// creates a logger that will be used by Tempo
// can be anything that implements `TempoLogger`, `ConsoleLogger` is just built in.
const logger = new ConsoleLogger('worker', TempoLogLevel.Debug);
// initialize the service registry. this class is generated for you.
const registry = new TempoServiceRegistry(logger);
// create a new router
// `any` refers to our environment objects type; it is recommended to set it an explicit type (MyEnvObj, etc.)
const router = new TempoRouter<any>(
logger,
registry
);
const server = createServer(
// feed incoming messages into the router
async (req: IncomingMessage, res: ServerResponse) => {
await router.process(req, res, {}));
}
);
server.listen(3000);
Assuming you already have @tempojs/client
as a dependency in your project, then all you need to do is import models from your shared codebase to use them:
import { GreeterClient } from '../shared';
// creates a new channel pointing at the Tempo server running locally
const channel = TempoChannel.forAddress('http://localhost:3000');
channel
.getClient(GreeterClient)
.sayHello({ name: 'World' })
.then((response) => {
console.log(response.serviceMessage);
})
.catch((e) => {
if (e instanceof TempoError) {
console.error(e);
}
});
or more simply if your project supports top-level await
const channel = TempoChannel.forAddress("http://localhost:3000");
const client = channel.getClient(GreeterClient);
const response = await client.sayHello({name: "World"});
console.log(response.serviceMessage);