#ts-codegen
Minimal TypeScript Telepact example that shows both the generated client and the generated server bindings wired into the runtime library.
Browse the files:
api/greet.telepact.yaml- Telepact schemagen/genTypes.ts- committed generated bindingsserver.ts- generated server handler wired into Telepacttest_example.ts- generated client wired into TelepactMakefile- local run target, including codegen
Run it:
make runThe committed generated bindings also type-check in strict modern ESM setups without special compiler concessions:
npm run build
npm run build:bundler#Source Files
#Makefile
#|
#| Copyright The Telepact Authors
#|
#| Licensed under the Apache License, Version 2.0 (the "License");
#| you may not use this file except in compliance with the License.
#| You may obtain a copy of the License at
#|
#| https://www.apache.org/licenses/LICENSE-2.0
#|
#| Unless required by applicable law or agreed to in writing, software
#| distributed under the License is distributed on an "AS IS" BASIS,
#| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#| See the License for the specific language governing permissions and
#| limitations under the License.
#|
SHELL := /bin/bash
.PHONY: run dep codegen clean
run: codegen
@set -euo pipefail; \
$(MAKE) -C ../../lib/ts; \
rm -rf node_modules dist telepact.tgz; \
cp ../../lib/ts/dist-tgz/*.tgz telepact.tgz; \
npm install --ignore-scripts --no-package-lock; \
npm run build; \
npm test
dep:
@$(MAKE) -C ../../lib/ts
codegen: dep
@set -euo pipefail; \
rm -rf ../../sdk/cli/telepact_cli/telepact; \
cp -r ../../lib/py/telepact ../../sdk/cli/telepact_cli/telepact; \
rm -rf gen; \
mkdir -p gen; \
uv run --project ../../sdk/cli telepact codegen --schema-dir api --lang ts --out gen; \
: > gen/.license-header-ignore
clean:
@rm -rf dist node_modules telepact.tgz gen#api/
#api/greet.telepact.yaml
#|
#| Copyright The Telepact Authors
#|
#| Licensed under the Apache License, Version 2.0 (the "License");
#| you may not use this file except in compliance with the License.
#| You may obtain a copy of the License at
#|
#| https://www.apache.org/licenses/LICENSE-2.0
#|
#| Unless required by applicable law or agreed to in writing, software
#| distributed under the License is distributed on an "AS IS" BASIS,
#| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#| See the License for the specific language governing permissions and
#| limitations under the License.
#|
- fn.greet:
subject: "string"
->:
- Ok_:
message: "string"#gen/
#gen/genTypes.ts
// Code generated by the Telepact CLI. DO NOT EDIT BY HAND.
import { Message, Client } from 'telepact';
import type { TypedMessage } from 'telepact';
export class TaggedValue_<T, U> {
tag: T;
value: U;
constructor(tag: T, value: U) {
this.tag = tag;
this.value = value;
}
}
export class UntypedTaggedValue_ {
tag: string;
value: Record<string, any>;
constructor(tag: string, value: Record<string, any>) {
this.tag = tag;
this.value = value;
}
}
export class GreetInput {
pseudoJson: Record<string, any>;
constructor(pseudoJson: Record<string, any>) {
this.pseudoJson = pseudoJson;
}
static from({
subject,
}: {
subject: string,
}): GreetInput {
const input: Record<string, any> = {};
input["subject"] = subject;
return new GreetInput({"fn.greet": input});
}
subject(): string {
return this.pseudoJson["fn.greet"]["subject"];
}
}
export class GreetOutput {
pseudoJson: Record<string, any>;
constructor(pseudoJson: Record<string, any>) {
this.pseudoJson = pseudoJson;
}
static Ok_: typeof GreetOutputOk;
static from_Ok_(payload: {
message: string,
}): GreetOutput {
return new GreetOutput({
"Ok_": GreetOutputOk.from(payload).pseudoJson
});
}
getTaggedValue():
TaggedValue_<"Ok_", GreetOutputOk> | TaggedValue_<"NoMatch_", UntypedTaggedValue_> {
const tag = Object.keys(this.pseudoJson)[0]!;
if (tag === "Ok_") {
return new TaggedValue_("Ok_", new GreetOutputOk(this.pseudoJson["Ok_"]));
}
return new TaggedValue_("NoMatch_", new UntypedTaggedValue_(tag, this.pseudoJson[tag]));
}
}
export class GreetOutputOk {
pseudoJson: Record<string, any>;
constructor(pseudoJson: Record<string, any>) {
this.pseudoJson = pseudoJson;
}
static from({
message,
}: {
message: string,
}): GreetOutputOk {
const input: Record<string, any> = {};
input["message"] = message;
return new GreetOutputOk(input);
}
message(): string {
return this.pseudoJson["message"];
}
}
GreetOutput.Ok_ = GreetOutputOk;
export class GreetSelect_ {
pseudoJson: Record<string, any> = {};
okmessage(): GreetSelect_ {
const resultUnion = this.pseudoJson["->"] ?? {};
const theseFields = resultUnion["Ok_"] ?? [];
if (!theseFields.includes('message')) {
theseFields.push('message');
}
resultUnion["Ok_"] = theseFields;
this.pseudoJson["->"] = resultUnion;
return this;
}
}
export const greet = {
Input: GreetInput,
Output: GreetOutput,
Select_: GreetSelect_,
} as const;
export class Select_ {
pseudoJson: Record<string, any> = {};
constructor(pseudoJson: Record<string, any>) {
this.pseudoJson = pseudoJson;
}
static for_greet(select: GreetSelect_): Select_ {
return new Select_(select.pseudoJson);
}
}
export class TypedClient {
client: Client;
constructor(client: Client) {
this.client = client;
}
async greet(headers: Record<string, any>, input: GreetInput): Promise<TypedMessage<GreetOutput>> {
const message = await this.client.request(new Message(headers, input.pseudoJson));
return { headers: message.headers, body: new GreetOutput(message.body)};
}
}
export class TypedServerHandler {
async greet(_headers: Record<string, any>, _input: GreetInput): Promise<TypedMessage<GreetOutput>> {
throw new Error('Not implemented');
}
functionRoutes(): Record<string, (functionName: string, requestMessage: Message) => Promise<Message>> {
return {
"fn.greet": async (_functionName: string, requestMessage: Message): Promise<Message> => {
const argument = requestMessage.body["fn.greet"];
const { headers: responseHeaders, body } = await this.greet(
requestMessage.headers,
new GreetInput({ "fn.greet": argument }),
);
return new Message(responseHeaders, body.pseudoJson);
},
};
}
async handler(message: Message): Promise<Message> {
const functionName = message.getBodyTarget();
const functionRoute = this.functionRoutes()[functionName];
if (functionRoute === undefined) {
throw new Error("Unknown function: " + functionName);
}
return await functionRoute(functionName, message);
}
}#package.json
{
"name": "telepact-example-ts-codegen",
"private": true,
"scripts": {
"build": "tsc",
"build:bundler": "tsc --noEmit --module ESNext --moduleResolution bundler",
"test": "node --test dist/test_example.js"
},
"dependencies": {
"telepact": "file:telepact.tgz"
},
"devDependencies": {
"@types/node": "^25.0.3",
"typescript": "^5.9.2"
},
"type": "module"
}#server.ts
//|
//| Copyright The Telepact Authors
//|
//| Licensed under the Apache License, Version 2.0 (the "License");
//| you may not use this file except in compliance with the License.
//| You may obtain a copy of the License at
//|
//| https://www.apache.org/licenses/LICENSE-2.0
//|
//| Unless required by applicable law or agreed to in writing, software
//| distributed under the License is distributed on an "AS IS" BASIS,
//| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//| See the License for the specific language governing permissions and
//| limitations under the License.
//|
import { createServer, IncomingMessage, Server as HttpServer, ServerResponse } from 'node:http';
import * as fs from 'node:fs';
import * as path from 'node:path';
import { FunctionRouter, Server, ServerOptions, TelepactSchema, TelepactSchemaFiles } from 'telepact';
import type { Response, TypedMessage } from 'telepact';
import { GreetInput, GreetOutput, TypedServerHandler, greet } from './gen/genTypes.js';
const files = new TelepactSchemaFiles('api', fs, path);
const schema = TelepactSchema.fromFileJsonMap(files.filenamesToJson);
const options = new ServerOptions();
options.authRequired = false;
class GreetingHandler extends TypedServerHandler {
async greet(_headers: Record<string, any>, input: GreetInput): Promise<TypedMessage<GreetOutput>> {
return {
headers: {},
body: greet.Output.from_Ok_({
message: `Hello ${input.subject()} from generated code!`,
}),
};
}
}
const functionRouter = new FunctionRouter(new GreetingHandler().functionRoutes());
const telepactServer = new Server(schema, functionRouter, options);
function readRequestBytes(request: IncomingMessage): Promise<Uint8Array> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
request.on('data', (chunk) => {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
});
request.on('end', () => resolve(new Uint8Array(Buffer.concat(chunks))));
request.on('error', reject);
});
}
function writeTelepactResponse(responseWriter: ServerResponse, response: Response): void {
responseWriter.statusCode = 200;
responseWriter.setHeader('Content-Type', '@bin_' in response.headers ? 'application/octet-stream' : 'application/json');
responseWriter.end(Buffer.from(response.bytes));
}
export function createHttpServer(): HttpServer {
return createServer((request, responseWriter) => {
void (async () => {
if (request.method !== 'POST' || request.url !== '/api/telepact') {
responseWriter.statusCode = 404;
responseWriter.end();
return;
}
const requestBytes = await readRequestBytes(request);
const response = await telepactServer.process(requestBytes);
writeTelepactResponse(responseWriter, response);
})().catch((error: unknown) => {
responseWriter.statusCode = 500;
responseWriter.end(String(error));
});
});
}#test_example.ts
//|
//| Copyright The Telepact Authors
//|
//| Licensed under the Apache License, Version 2.0 (the "License");
//| you may not use this file except in compliance with the License.
//| You may obtain a copy of the License at
//|
//| https://www.apache.org/licenses/LICENSE-2.0
//|
//| Unless required by applicable law or agreed to in writing, software
//| distributed under the License is distributed on an "AS IS" BASIS,
//| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//| See the License for the specific language governing permissions and
//| limitations under the License.
//|
import assert from 'node:assert/strict';
import test from 'node:test';
import type { AddressInfo } from 'node:net';
import { Client, ClientOptions, Message, Serializer } from 'telepact';
import { TypedClient, greet } from './gen/genTypes.js';
import { createHttpServer } from './server.js';
async function runServer(server: ReturnType<typeof createHttpServer>): Promise<void> {
await new Promise<void>((resolve) => {
server.listen(0, '127.0.0.1', () => resolve());
});
}
async function stopServer(server: ReturnType<typeof createHttpServer>): Promise<void> {
await new Promise<void>((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
}
async function postBytes(url: string, requestBytes: Uint8Array): Promise<Uint8Array> {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: Buffer.from(requestBytes),
});
return new Uint8Array(await response.arrayBuffer());
}
test('codegen example runs end to end', async () => {
const server = createHttpServer();
await runServer(server);
try {
const address = server.address() as AddressInfo;
const url = `http://127.0.0.1:${address.port}/api/telepact`;
const adapter = async (message: Message, serializer: Serializer): Promise<Message> => {
const requestBytes = serializer.serialize(message);
const responseBytes = await postBytes(url, requestBytes);
return serializer.deserialize(responseBytes);
};
const client = new TypedClient(new Client(adapter, new ClientOptions()));
const response = await client.greet({}, greet.Input.from({ subject: 'Telepact' }));
const ok = response.body.getTaggedValue();
assert.equal(ok.tag, 'Ok_');
assert.equal(ok.value.message(), 'Hello Telepact from generated code!');
assert.deepEqual(response.headers, {});
} finally {
await stopServer(server);
}
});#tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": [
"ES2022",
"DOM"
],
"rootDir": ".",
"outDir": "dist",
"strict": true,
"verbatimModuleSyntax": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": [
"*.ts",
"gen/**/*.ts"
]
}