[](https://codecov.io/gh/monotykamary/flame-js)
npm install @monotykamary/flame
FLAME-style remote execution for TypeScript with Bun, Effect.ts internals, and a same-image / different-entrypoint model.
This project is inspired by the original Elixir implementation of FLAME in the Phoenix ecosystem:
https://github.com/phoenixframework/flame
In Elixir, FLAME treats your whole app as a lambda, ships closures over the BEAM distribution,
and runs them on ephemeral nodes managed by a pool/backend. This TypeScript library adapts the
same high-level idea to a different runtime: we do not ship closures, and we invoke explicit
service/method IDs over HTTP between parent and runner processes. The architecture is different,
but the goal is similar: run targeted work on short-lived infrastructure with a same-image,
different-entrypoint deployment model.
FLAME lets you define services and functions once, then run them locally, on a parent control plane, or inside remote runners that execute the same codebase. It favors stability and safety:
- Explicit service and method IDs (no dynamic imports or code shipping).
- Superjson for arguments/results (structured data only).
- Effect.ts for pooling, retries, and orchestration primitives.
- Same image, different entrypoint deployments.
FLAME shines when you want simple remote execution with stable IDs and a tight deployment story:
- Burstable compute: spawn workers for spikes, shut down when idle.
- CPU/GPU workloads: route specific methods to specialized pools.
- Multi-tenant workloads: isolate heavier work into runners without changing code.
- Deterministic API boundaries: explicit service/method IDs across refactors.
- In-process or remote toggling: switch between local and remote execution per environment.
- Multi-parent coordination or distributed leases (planned, not in v1).
- Long-running job queues with durable state.
- Dynamic code loading or uploading user-defined functions.
- Cross-language remote invocation.
You ship one image and start it in different modes:
- parent: routes calls to runners via HTTP.
- runner: exposes an HTTP endpoint and executes registered handlers.
- local: in-process execution (good for tests/dev).
Mode resolution:
- config.mode if provided.
- FLAME_MODE environment variable.
- FLAME_RUNNER=true fallback.
``bash`
npm install @monotykamary/flameor
pnpm add @monotykamary/flameor
yarn add @monotykamary/flameor
bun add @monotykamary/flame
Note: the package currently exports TypeScript source. Use Bun or a TS-aware bundler/runtime.
`ts
import { flame, defineMethod } from "@monotykamary/flame";
export const Billing = flame.service.billing({
charge: defineMethod.charge(async (_ctx, req: { amount: number }) => {
return { ok: true, charged: req.amount };
})
});
`
`ts
import { flame } from "@monotykamary/flame";
import "./services";
await flame.configure({
mode: "parent",
runnerUrl: process.env.RUNNER_URL,
security: { secret: process.env.FLAME_SECRET ?? "dev-secret" }
});
const result = await Billing.charge({ amount: 50 });
console.log(result);
`
`ts
import { flame } from "@monotykamary/flame";
import "./services";
await flame.configure({
mode: "runner",
security: { secret: process.env.FLAME_SECRET ?? "dev-secret" }
});
flame.createRunnerServer({
port: 8080,
security: { secret: process.env.FLAME_SECRET ?? "dev-secret" }
});
`
`ts
import { flame } from "@monotykamary/flame";
import "./services";
await flame.configure({ mode: "local" });
const result = await Billing.charge({ amount: 50 });
console.log(result);
`
- flame.service. defines a service (or use flame.service("serviceId", ...)).defineMethod.
- assigns method IDs (or use defineMethod("methodId", ...)).InvocationContext
- Handlers receive first:
`ts
import { defineMethod } from "@monotykamary/flame";
const handler = defineMethod.charge(async (ctx, req: { amount: number }) => {
if (ctx.deadline && Date.now() > ctx.deadline) throw new Error("expired");
return { ok: true, charged: req.amount };
});
`
`ts`
const ping = flame.fn.ping(async () => "pong");
const result = await ping();
FlameService.layer provides a scoped instance and ensures shutdown.
`ts
import { Effect } from "effect";
import { FlameService } from "@monotykamary/flame";
const program = Effect.gen(function* () {
const flame = yield* FlameService;
const ping = flame.fn.ping(async () => "pong");
return yield* Effect.tryPromise(() => ping());
});
const layer = FlameService.layer({ mode: "local" });
const result = await Effect.runPromise(program.pipe(Effect.provide(layer)));
`
`text
┌──────────────────────────────┐
│ flame.ts │
│ - service / fn │
│ - serviceEffect / fnEffect │
│ - toEffect │
└───────────────┬──────────────┘
│
│ returns proxies or Effect wrappers
v
┌──────────────────────────────┐
│ proxy.ts │
│ - createServiceProxyEffect │
│ - toEffect (wraps metadata) │
└───────────────┬──────────────┘
│
│ delegates to runtimeRef
v
┌──────────────────────────────┐
│ runtime.ts │
│ - invokeEffect (Effect.fn) │
│ - invoke (Promise boundary) │
└───────────────┬──────────────┘
│
│ uses PoolManager + Pool
v
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ pool/manager.ts │ │ pool/pool.ts │
│ - get(name) -> Effect │ │ - acquire/release/shutdown │
│ - shutdownAll -> Effect │ │ - Queue/Ref/Scope internals │
└───────────────┬──────────────┘ └───────────────┬──────────────┘
│ │
└──────────────┬───────────────────┘
│
│ spawns/terminates runners
v
┌──────────────────────────────┐
│ backend (config-driven) │
│ - spawn/terminate/health │
└──────────────────────────────┘
Effect boundary:
- Promise API: flame.service / flame.fn → runtime.invoke → Effect.runPromise(invokeEffect)
- Effect API: flame.serviceEffect / fnEffect / toEffect → runtime.invokeEffect (returns Effect)
`
Enable experimentalDecorators in tsconfig.json.
`ts
import { flame, flameService } from "@monotykamary/flame";
@flameService("billing", { pool: "default" })
class BillingService {
@flame({ id: "charge", pool: "gpu" })
async charge(amount: number) {
return { ok: true, charged: amount };
}
async refund(amount: number) {
return { ok: true, refunded: amount };
}
}
`
| Field | Type | Description |
| --- | --- | --- |
| mode | "parent" \| "runner" \| "local" | Execution mode. |defaultPool
| | string | Pool used when none is specified. |pools
| | Record | Pool definitions. |backend
| | Backend | Optional spawn/terminate adapter for dynamic runners. |runnerUrl
| | string | Base URL used by the parent to call runners. |invokePath
| | string | Path for runner invocations (default /invoke). |security
| | { secret: string } | HMAC signing secret. |requestTimeoutMs
| | number | Default timeout for remote calls. |maxBodyBytes
| | number | Runner request body limit. |exposeErrors
| | boolean | When true, runner returns error details. |logger
| | Logger | Reserved for future logging hooks. |
| Field | Type | Description |
| --- | --- | --- |
| pool | string | Pool name override. |timeoutMs
| | number | Request timeout for this call. |idempotencyKey
| | string | Idempotency token passed to runners. |retry.maxAttempts
| | number | Retry count (parent side). |retry.baseDelayMs
| | number | Exponential backoff base delay. |
| Field | Type | Description |
| --- | --- | --- |
| min | number | Minimum runners to keep alive. |max
| | number | Max runners in the pool. |maxConcurrency
| | number | Concurrent invocations per runner. |runners
| | { id?: string, url: string }[] | Static runners. |spawnTimeoutMs
| | number | Reserved for future spawn timeout. |
By default, FLAME manages pools in memory. If you want dynamic runners, provide a backend:
`ts
import { Effect } from "effect";
const backend = {
spawn: ({ poolName }: { poolName: string }) =>
Effect.succeed({ id: ${poolName}-${Date.now()}, url: "http://runner" }),
terminate: () => Effect.void
};
await flame.configure({
backend,
pools: {
default: { min: 0, max: 4, maxConcurrency: 2 }
}
});
`
- Requests can be signed with an HMAC secret (x-flame-signature header).exposeErrors
- Args/results are serialized with superjson; closures/functions are not supported.
- controls whether runner returns details to callers.
`bashparent
FLAME_MODE=parent RUNNER_URL=http://runner:8080 bun run src/parent.ts
Testing
`bash
bun run test
bun run test:unit
bun run test:integration
bun run test:smoke
bun run test:e2e
bun run test:coverage
`API surface (quick reference)
-
createFlame(config?), flame
- flame.service, flame.serviceEffect
- flame.fn, flame.fnEffect
- flame.configure, flame.shutdown
- defineMethod
- flame.toEffect
- FlameService.layer
- flame.createRunnerServerLicense
MIT. See
LICENSE`.