apiplant-js 0.3.1

TypeScript/JavaScript functions for apiplant: build-time transpile, V8 isolates at runtime
Documentation

TypeScript and JavaScript functions.

A function written in Rust, C, Zig or Go arrives as a shared library. A function written in TypeScript cannot: there is nothing to link. So this crate provides the other half of what a .so gives the host — a manifest to read at boot and something to call per request — backed by V8 isolates instead of dlopen.

functions/greet.ts   ← what you write
functions/greet.js   ← `apiplant build` strips the types (swc, at build time)
                       and the server loads *this*, like it loads libgreet.so

Two stages, on purpose

Types are stripped at build time, so the server never parses TypeScript and a syntax error is a build failure rather than a boot failure. What runs at request time is plain JavaScript in a V8 isolate — the same split Deno and Bun make internally, just with the first half hoisted into apiplant build.

No type checking happens: swc strips annotations without consulting them, exactly like deno run --no-check or bun. apiplant build writes an apiplant.d.ts beside your sources so your editor (and tsc --noEmit, if you want it in CI) does the checking with real types.

What a module looks like

import { defineFunctions, db, s } from "apiplant";

export default defineFunctions({
  greet: {
    permission: "public",
    input: s.object({ name: s.string() }),
    handler(input) {
      const notes = db.value("SELECT count(*)::int AS n FROM apiplant_note");
      return { message: `Hello, ${input.name}!`, notes };
    },
  },
});

One module may declare any number of functions, like one .so may export any number. apiplant is the only module a function can import; it is compiled into this crate from typescript/ at the repository root and served to the isolate by [module], so nothing is installed and nothing can be out of step with the host. A module that would rather import nothing declares export const manifest = [...] and one export per entry instead; both forms arrive here the same way.

Concurrency

An isolate is single-threaded, so a module is loaded into a small pool of them ([workers], APIPLANT_JS_WORKERS) that share one job queue. Requests run concurrently up to the pool size and queue beyond it. Isolates share nothing: module-level state in a function is per-worker and must not be treated as shared state — use the database or the cache for that.

An invocation that runs longer than APIPLANT_JS_TIMEOUT_MS (30s by default) has its isolate terminated and fails that one request; the worker recovers.