Environments
Where the Bastion SDK runs — Node, Bun, Deno, edge runtimes, and browsers — with the caveats for each.
The SDK is built against the Fetch standard. Anywhere fetch, Response, Request, and ReadableStream are globals, the SDK should work.
| Runtime | Status | Notes |
|---|---|---|
| Node.js 20+ | ✅ First-class | fetch is global since Node 18, stable in 20. |
| Node.js 18 | ⚠ Works | Same global fetch, but technically experimental. Upgrade to 20 if you can. |
| Bun | ✅ First-class | Drop-in replacement for Node for SDK purposes. |
| Deno | ✅ Works | Use npm: specifier. Pass apiKey explicitly via Deno.env.get; the SDK's process.env.BASTION_API_KEY fallback only fires under Deno's Node-compat mode. |
| Cloudflare Workers | ✅ Works | No process — pass apiKey explicitly via the binding. |
| Vercel Edge | ✅ Works | Same as Workers — pass apiKey explicitly. |
| Browsers (modern) | ⚠ Works but don't | Your API key would leak. Proxy through your server. |
| React Native | ⚠ Use with care | Requires a fetch polyfill that supports streaming bodies if you stream. |
Node.js
Nothing to configure:
import { Bastion } from "@qubittron/bastion-sdk";
const client = new Bastion(); // reads BASTION_API_KEYModule formats: both ESM (.mjs) and CJS (.cjs) are shipped — TS types included for both.
Bun
Identical to Node:
const client = new Bastion({ apiKey: Bun.env.BASTION_API_KEY });Cloudflare Workers / Vercel Edge
process.env does not exist on these runtimes. Pass the key explicitly from the binding/env:
export default {
async fetch(req: Request, env: { BASTION_API_KEY: string }) {
const client = new Bastion({ apiKey: env.BASTION_API_KEY });
// ...
},
};The SDK uses no Node-only APIs (fs, crypto, buffer) on the hot path. Streaming via ReadableStream works.
Deno
import { Bastion } from "npm:@qubittron/bastion-sdk";
const client = new Bastion({ apiKey: Deno.env.get("BASTION_API_KEY") });Always pass apiKey explicitly under Deno. The SDK's automatic process.env.BASTION_API_KEY fallback assumes a Node process object exists, which only happens under Deno's Node-compatibility mode — relying on it is fragile across Deno versions.
Browsers — don't ship your key
The SDK runs in browsers, but never put a real API key in browser-bundled code:
- Static-site bundlers (Vite, Next.js client components, Astro) will embed any value you import from
process.env.SOMETHINGinto the JS sent to the user. - Even a "short-lived" key visible in DevTools is a key in the wild.
The right pattern: proxy through your server.
// server (Hono, Express, Next.js route handler, etc.)
const client = new Bastion();
app.post("/api/chat", async (c) => {
const body = await c.req.json();
const res = await client.chat.completions.create(body);
return c.json(res);
});
// browser
const res = await fetch("/api/chat", {
method: "POST",
body: JSON.stringify({ model: "gpt-oss-120b", messages }),
});If you must call directly from the browser (e.g. an internal admin tool, an authenticated app where the key is per-user and short-lived), set up a key-per-user system on your backend — never share one global key across users.
React Native
The SDK uses streaming bodies for SSE. Older RN versions ship a fetch polyfill that does not stream — response.body may be null. Verify on your target before relying on stream: true. Non-streaming calls work everywhere.
Polyfill notes
If your runtime is missing fetch, you can inject one:
import { Bastion } from "@qubittron/bastion-sdk";
import { fetch } from "undici";
const client = new Bastion({
apiKey: process.env.BASTION_API_KEY,
fetch: fetch as typeof globalThis.fetch,
});See Custom fetch for more uses of this hook.