Skip to content
This page View Markdown Open in ChatGPT Open in Claude

TypeScript and JavaScript

Code in TypeScript is compiled to JavaScript when you save it, and runs on the runtime you choose in Sandbox Settings : Node.js (the default) or Bun, in the version you pick there, with only the npm packages you add.

In short

  • Write async function main(g) and export it : module.exports = main.
  • It runs on Node.js (24) or Bun (1), as Sandbox Settings say.
  • Refuse a request with throw [{ code: 400, message }].
hello.ts
1
2
3
4
5
6
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    return { hello: g.req.body.name || 'world' };
}
module.exports = main;
Node.js Bun
Image node:<version>-slim (24) oven/bun:<version>-slim (1)
Oldest version 20 1.0
Packages npm install bun add
Code coverage of test cases yes no : Bun has no V8 profiler
Downloads while code runs none none (automatic install off)

The code, the global object g, utility classes, types and the examples of g.sys are the same on both : switching the runtime needs no change of code, unless the code uses something only one runtime has (Bun.file).

The request and the answer

Read the request, shape the answer
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const status = g.req.query.status || 'open';
    const signed = !!g.req.headers['x-am-authorization'];
    g.res.statusCode = T.EStatusCode.CREATED;
    g.res.headers = { 'X-Request-Kind': 'orders' };
    return { customer: g.req.params.id, status, signed };
}
module.exports = main;

Bytes as the answer

A file, a picture, a PDF : return its bytes (a Buffer, or any Uint8Array : Buffer.from(await answer.arrayBuffer()) for what fetch downloaded), or put them in g.res.output, and name their kind of content. The answer is those bytes as they are, whatever the kind of content, with the headers of the code. (A text under a kind of bytes, image/gif, is read as base64.)

A picture as the answer
1
2
3
4
5
6
7
8
9
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const picture = Buffer.from('R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOwA=', 'base64');
    g.res.contentType = 'image/gif' as T.EContentType;
    g.res.headers = { 'Content-Disposition': 'inline; filename="pixel.gif"' };
    return picture;
}
module.exports = main;

Calling API Maker : g.sys

Database calls
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const table = { instance: 'mysql_8', database: 'inventory', collection: 'customers' };
    const rows = await g.sys.db.getAll({ ...table, queryParams: { limit: 10, sort: '-customer_id' } });
    const two = await g.sys.db.getById({ ...table, id: 2 });
    const count = await g.sys.db.count({ ...table, find: { country: 'IN' } });
    return { first: rows[0], two, inIndia: count };
}
module.exports = main;
Calls at the same time
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const shop = { instance: 'mongodb', database: 'shop' };
    const [orders, products] = await Promise.all([
        g.sys.db.count({ ...shop, collection: 'orders' }),
        g.sys.db.count({ ...shop, collection: 'products' }),
    ]);
    return { orders, products };
}
module.exports = main;
Rows as they arrive
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    let amount = 0;
    await g.sys.db.getAllByStream({ instance: 'mysql_8', database: 'inventory', collection: 'orders' }, (row: any) => {
        amount += row.amount || 0;
    });
    return { amount };
}
module.exports = main;

Every call of g.sys has its own page of examples : database, generated APIs, system APIs, cache.

Errors

Throw a list of errors to answer with them :

Refuse a request
1
2
3
4
5
6
7
8
9
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    if (!g.req.body.quantity) {
        throw [{ code: T.EStatusCode.BAD_REQUEST, message: `'quantity' is missing.` }];
    }
    return { ok: true };
}
module.exports = main;
Catch the errors of a call
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    try {
        await g.sys.db.removeById({ instance: 'mysql_8', database: 'inventory', collection: 'customers', id: 404 });
        return { removed: true };
    } catch (errors) {
        g.logger.warn('not removed : ', errors[0].message);
        return { removed: false, code: errors[0].code };
    }
}
module.exports = main;

An exception answers 500 with its message and the lines of your TypeScript (the source map of the save is applied).

Logs, printing and shared values

Logs and shared values
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    g.logger.log('order ', 7, ' paid');
    console.log('kept in the console of the account');
    g.shared.calls = (g.shared.calls || 0) + 1;   // lives as long as the runner
    g.res.shared.started = true;                  // travels with this request : pre hook → API → post hook
    return g.shared.calls;
}
module.exports = main;

Hooks

Pre hook : only my rows
1
2
3
4
5
6
7
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    const person = g.req.auth?.authAMDB || {};
    g.req.query.find = { ...(g.req.query.find || {}), owner_id: person.id };
}
module.exports = main;
Post hook : hide a column
1
2
3
4
5
6
import * as T from 'types';

async function main(g: T.IAMGlobal) {
    g.res.output = (g.res.output || []).map(({ password, ...row }: any) => row);
}
module.exports = main;

Packages

Add the package in Sandbox Settings (dayjs 1.11.13), then import it :

An npm package
1
2
3
4
5
6
7
import * as T from 'types';
import * as dayjs from 'dayjs';

async function main(g: T.IAMGlobal) {
    return { tomorrow: dayjs().add(1, 'day').format('YYYY-MM-DD') };
}
module.exports = main;
  • Versions are what npm takes : 4.17.21, ^4.17.0, latest.
  • Nothing is installed unless you add it : a new sandbox has only the packages of its runtime.
  • Types of the packages are read by the code editors from the sandbox.

Utility classes

Code in TypeScript imports the utility classes written in TypeScript on both runtimes (import { Tax } from 'utils/Tax';), and they import each other and your packages. A class written in another language is for the code of that language.

How it runs

  • The code is compiled to JavaScript when you save it; the runner keeps the compiled function by the hash of the code.
  • One runner serves many requests at once on its event loop; the sandbox opens more runners while all are busy.
  • Values keep their kind inside one sandbox (dates are Date, ids are ObjectId); a request which comes from outside reaches the code as JSON.

Debugging

  • Turn debugging on in the debug settings, set a breakpoint in the margin of the editor and send the request : the code stops on the line of your TypeScript, on Node.js and on Bun alike (its source map is applied for you), and the Debug tab of the API testing page shows its variables and its console.
  • Learn more Debugging.