@gigadrive/sdk
v0.17.0
Published
The official TypeScript SDK for the [Gigadrive Network](https://gigadrive.de) cloud platform — organizations, applications, deployments, storage with resumable file uploads, and an OpenAI-compatible AI Gateway.
Readme
@gigadrive/sdk
The official TypeScript SDK for the Gigadrive Network cloud platform — organizations, applications, deployments, storage with resumable file uploads, and an OpenAI-compatible AI Gateway.
Works in Node.js 18+, browsers, and edge runtimes (anything with fetch).
Installation
npm install @gigadrive/sdk
# or: pnpm add @gigadrive/sdkQuick start
import { GigadriveClient } from '@gigadrive/sdk';
// Credentials are auto-detected from the environment (see below),
// or pass them explicitly.
const client = new GigadriveClient({
clientId: process.env.GIGADRIVE_CLIENT_ID,
clientSecret: process.env.GIGADRIVE_CLIENT_SECRET,
});
const { items: organizations } = await client.organizations.list();
const { items: applications } = await client.applications.list();Organizations and product entitlements
Organizations are the top-level account containers for Gigadrive products. Beyond listing orgs, the SDK can create organizations, inspect members, and check product entitlements (read-only; the SDK does not activate or change plans):
// Create an organization (user-backed token + platform:organizations:write)
const org = await client.organizations.create({ name: 'Acme Corp' });
// Fetch one organization and its members
const details = await client.organizations.get(org.id);
const { items: members } = await client.organizations.members.list(org.id);
// Inspect product access / entitlements (read-only)
const { items: products } = await client.organizations.products.list(org.id);
const office = await client.organizations.products.get(org.id, 'office');
const check = await client.organizations.products.checkEntitlement(org.id, 'office');
console.log(details.name, members.length, office.hasAccess, check.hasAccess, products.length);Authentication
Authentication is handled for you — tokens are fetched, cached, and refreshed behind the scenes. Provide credentials via the constructor or environment variables (constructor values take precedence):
| Method | Constructor | Environment |
| ---------------------------- | --------------------------------- | ------------------------------------------------- |
| API key (machine-to-machine) | clientId + clientSecret | GIGADRIVE_CLIENT_ID + GIGADRIVE_CLIENT_SECRET |
| Pre-obtained bearer token | bearerToken | GIGADRIVE_BEARER_TOKEN |
| Refresh token | clientId + refreshToken | GIGADRIVE_CLIENT_ID + GIGADRIVE_REFRESH_TOKEN |
| Authorization code + PKCE | clientId + onAuthorizationUrl | — |
Context-bound storage calls also resolve an application UUID from
applicationId in the constructor or GIGADRIVE_APPLICATION_ID. Gigadrive
injects the latter with workload credentials, so deployed server code normally
needs no SDK configuration.
// Custom fetch / base URL (e.g. for tests or non-standard runtimes)
const client = new GigadriveClient({ bearerToken: 'eyJ...', fetch: myFetch });Sticky sessions
Deployed MicroVM functions can mint a routing URL that keeps the same opaque application key on one function instance. Workload credentials are injected by the platform, so no SDK configuration is required inside a deployment:
import { GigadriveClient } from '@gigadrive/sdk';
const gigadrive = new GigadriveClient();
const { url, expiresAt } = await gigadrive.stickySessions.createUrl({
key: gameId,
endpoint: '/socket',
expiresInSeconds: 14_400,
});
const socket = new WebSocket(url);The URL is routing authority, not user authentication. Applications still own authorization and room membership. State remains in one MicroVM's memory, URLs expire, and deploys do not migrate that in-memory state to a new version.
File storage
Bucket name is the canonical REST and IaC identifier. Names are immutable,
lowercase, URL-safe, and unique within an environment. The returned bucket
slug remains the global CDN/S3 identifier and should not be passed to these
REST helpers.
Declare buckets for each deployment environment under services.storage in
gigadrive.yaml. Mapping keys are the canonical bucket names; null or an
empty object uses private visibility. The deployment determines the environment
and generates each global CDN/S3 slug.
version: 4
services:
storage:
buckets:
assets:
visibility: public
uploads: nullInside a deployed workload, application and environment context are inferred:
import { GigadriveClient } from '@gigadrive/sdk';
const client = new GigadriveClient();
const { items } = await client.storage.objects.list('assets');Management callers can configure the application and select an environment by slug or UUID:
const client = new GigadriveClient({ applicationId, clientId, clientSecret });
const bucket = await client.storage.buckets.create({
name: 'assets',
environment: 'production',
visibility: 'public',
});
const { items } = await client.storage.objects.list(bucket.name, {
environment: 'production',
});Existing client.applications.storage calls, explicit applicationId
arguments, and bucket UUIDs remain available as deprecated compatibility
paths.
File uploads
The high-level upload() computes the required SHA-256 checksum, infers the
content type from the key, creates the upload session, and uploads the bytes
with resumable transfer — in one call.
// Node.js — upload straight from a file path (size, checksum, type inferred)
const { url } = await client.storage.upload({
bucket: 'reports',
key: 'reports/q1.pdf',
path: './q1-report.pdf',
});
// Browser — upload a File with progress and cancellation
const controller = new AbortController();
const { url } = await client.storage.upload({
bucket: 'uploads',
key: `uploads/${file.name}`,
data: file,
onProgress: (sent, total) => console.log(`${Math.round((sent / total) * 100)}%`),
signal: controller.signal,
});
// Wait until the object is finalized server-side, then read it back
const { object } = await client.storage.upload({
bucket: 'avatars',
key: 'avatars/user-1.png',
data: bytes,
waitForCompletion: true,
});
console.log(object?.contentLength, 'bytes stored');Accepted inputs: browser File/Blob, Node Buffer/Uint8Array/ArrayBuffer,
a Node filesystem path, or a Node readable stream (with contentLength and
checksumSha256).
Empty files such as .gitkeep or __init__.py work with every input kind. The
API stores a zero-byte object when the upload session is created, so nothing is
transferred, waitForCompletion returns without polling, and the result always
includes object. For an empty stream, pass contentLength: 0; the empty
SHA-256 is filled in for you.
Many files at once
const results = await client.storage.uploadBatch(
files.map((f) => ({ bucket: 'uploads', key: f.name, data: f })),
{ concurrency: 6, onProgress: (done, total) => console.log(`${done}/${total}`) }
);
const failed = results.filter((r) => r.error);Working with objects and trash
// List a "folder" one level deep
const { items, commonPrefixes } = await client.storage.objects.list('assets', {
prefix: 'images/',
limit: 100,
});
// Signed download URL for a private object
const { url } = await client.storage.objects.getAccessUrl('assets', objectId, {
expiresInSeconds: 3600,
});
// Delete moves an object to trash; restore or permanently purge it later
await client.storage.objects.delete('assets', objectId);
await client.storage.trash.restore('assets', objectId);
await client.storage.trash.purge('assets', objectId);
// Permanently purge every trashed object in the bucket
const { purgedCount } = await client.storage.trash.empty('assets');AI Gateway
OpenAI-compatible chat completions, responses, audio, video, and model discovery.
// Chat completion
const res = await client.aiGateway.chatCompletions({
model: 'openai/gpt-4o',
messages: [{ role: 'user', content: 'Hello!' }],
});
console.log(res.choices[0].message.content);
// Streaming
for await (const chunk of client.aiGateway.chatCompletionsStream({
model: 'openai/gpt-4o',
messages: [{ role: 'user', content: 'Write a haiku about the sea.' }],
})) {
process.stdout.write(chunk.choices[0]?.delta.content ?? '');
}
// Models
const { items: models } = await client.aiGateway.listModels();Organization-scoped governance (usage analytics, budgets, policies) lives under
client.organizations.aiGateway.
Custom domains
Attach a hostname you own to an application. The response lists the DNS records to publish; Gigadrive Network then verifies ownership, checks DNS and issues the certificate on its own.
import { DomainNotActiveError } from '@gigadrive/sdk';
const domain = await client.applications.domains.add('app-id', { hostname: 'shop.example.com' });
for (const record of domain.requiredRecords) {
console.log(record.type, record.host, record.value);
}
try {
await client.applications.domains.waitUntilActive('app-id', domain.id, {
timeoutMs: 15 * 60_000,
onState: (current) => console.log(current.state),
});
} catch (error) {
if (!(error instanceof DomainNotActiveError)) throw error;
// `reason` is `timeout`, `failed`, `suspended` or `removing`; `domain.error` explains what to fix.
console.error(error.reason, error.domain.error?.message);
}Redirect a domain, or serve a branch instead of production:
await client.applications.domains.update('app-id', domain.id, {
target: { type: 'redirect', to: 'www.example.com', statusCode: 308 },
});Verifying the registrable domain once lets every application of the organization attach hostnames below it without another TXT record:
const claim = await client.organizations.domains.add('org-id', 'example.com');
console.log(claim.record.host, claim.record.value); // publish this TXT record
await client.organizations.domains.verify('org-id', claim.id);Queues
Queues deliver work to your app in the background, now or later. A queue with a consumer path is a push queue: Gigadrive Network POSTs each message to that path on your deployment. Without one it is a pull queue that you drain with receive().
import { NonRetryableError, queue, RetryLaterError } from '@gigadrive/sdk';
export const emails = queue<{ to: string; template: string }>('emails');
// Send now, after a delay, or at a time (up to a year ahead).
await emails.send({ to: '[email protected]', template: 'welcome' });
await emails.send({ to: '[email protected]', template: 'nudge' }, { delay: '3d', deduplicationKey: 'nudge:jane' });
// app/api/queues/emails/route.ts: the push consumer. Signatures are verified for you.
export const POST = emails.handler(async (email, { attempt }) => {
if (!templates.has(email.template)) throw new NonRetryableError('Unknown template'); // dead-letter now
if (await mailer.isThrottled()) throw new RetryLaterError('1m'); // defer without spending an attempt
await mailer.send(email); // returning acknowledges the message
});Declare the push consumer and any cron schedules in gigadrive.yaml, or create them from code with emails.ensure({ consumerPath: '/api/queues/emails' }) and emails.schedule(...).
Pull consumers lease messages and settle them:
const jobs = queue<{ id: string }>('jobs');
await jobs.consume(async (job) => processJob(job.id), { maxMessages: 10, stopWhenEmpty: true });client.queues exposes the full REST surface (queues, messages, dead letters, schedules), and createWorkflowQueue() runs the Workflow SDK on Gigadrive Network queues.
microVMs
microVMs are isolated Linux machines you create on demand: run commands, move files in and out, and expose ports on public preview URLs. Use them for code execution, agent sandboxes, CI steps and previews.
import { MicroVM } from '@gigadrive/sdk';
await using vm = await MicroVM.create({ image: 'node22', ports: [3000], timeout: '10m' });
await vm.files.write('server.js', 'require("http").createServer((_, r) => r.end("hi")).listen(3000)');
const { exitCode, stdout } = await vm.run('node --version');
const server = await vm.spawn('node', ['server.js']);
console.log(await vm.waitForPort(3000)); // https://3000-<slug>.vm.gigadrive.app
// Leaving the block stops the microVM.MicroVM.create resolves its client like GigadriveClient (pass client to use
your own) and its application from applicationId, the client's
applicationId or GIGADRIVE_APPLICATION_ID. Inside a Gigadrive deployment
nothing needs configuring. Built-in images: node22, node24, python3.13,
python3.12, bun1, debian12, ubuntu24.04.
| Task | Call |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Create, connect, list | MicroVM.create({ image, vcpus, memoryMb, diskMb, timeout, ports, env, metadata, network }), MicroVM.get(id), for await (const vm of MicroVM.list()) |
| Run and wait | vm.run('ls -la'), vm.run('npm', ['test'], { cwd, env, user, sudo, timeout, onStdout, onStderr, stdin, throwOnError, signal }) → { exitCode, signal, timedOut, stdout, stderr, truncated, durationMs } |
| Run in the background | vm.spawn(cmd, args, { stdin, pty, onStdout }) → Command: logs(), output(), stdout(), stderr(), wait(), kill(signal), write(data), end(), resize(cols, rows) |
| Reattach | vm.commands.get(commandId), vm.commands.list() |
| Files | vm.files.write(path, string \| Uint8Array \| Blob \| ReadableStream), writeMany, read, readText, readStream, list(path, { recursive }), stat, exists, mkdir, remove, move |
| Ports | vm.url(port), vm.waitForPort(port) |
| Lifetime | vm.extendTimeout('30m'), vm.stop(), vm.delete(), await using vm = ... |
| Network | vm.updateNetwork('deny'), vm.updateNetwork({ egress: 'allow', allow: ['registry.npmjs.org', '*.github.com'] }): change what it may reach while it runs |
| Metrics | vm.getMetrics({ windowMinutes: 60 }) → { cpu: [{ time, cpuPercent }], current: { memoryUsedBytes, memoryTotalBytes, diskUsedBytes, diskTotalBytes } \| null } |
| Webhooks | client.microvms.webhooks.create({ url, events: ['microvm.stopped'] }) → { id, secret }, .list(), .update(id, { enabled }), .rotateSecret(id), .listDeliveries(id); verify a request with verifyMicroVmWebhook({ body, headers, secret }) |
| Volumes | client.microvms.volumes.create({ name: 'cache' }), attach with MicroVM.create({ volumes: [{ volumeId, mountPath: '/cache' }] }); saved on vm.stop() and on vm.saveVolumes() |
| Images | client.microvms.images.create({ name, from: 'python:3.13' \| { dockerfile } }), .waitUntilReady(id), .list(), .get(id), .delete(id) |
Command output stays in the microVM, so command.logs() can be read from
another process with vm.commands.get(id), and a dropped connection resumes
at the next unread byte. A non-zero exit code is a normal result; pass
throwOnError: true to get a CommandFailedError. Other typed errors:
MicroVmNotRunningError, MicroVmNotFoundError, MicroVmFileNotFoundError,
MicroVmQuotaExceededError, MicroVmBusyError, MicroVmUnreachableError,
MicroVmImageNotReadyError, MicroVmPortNotExposedError,
MicroVmTimeoutError, MicroVmStartError and MicroVmImageBuildError.
Reads and log streams are retried with backoff while a microVM answers
microvm_busy. A microVM lives at most 24 hours (MICROVM_MAX_TIMEOUT_MS),
or less on your plan.
client.microvms is the low-level REST surface, one method per route
(create, list, listAll, get, getMetrics, extendTimeout, updateNetwork, stop, delete,
commands.*, files.*, images.*, volumes.*, webhooks.*). The SDK uses only fetch, so it runs in
Node.js 18+, Bun, Deno, edge runtimes and Gigadrive Functions.
Pagination
List endpoints accept page / perPage / cursor and return { items, total }
(cursor-paginated endpoints also return nextCursor). Iterate everything with
the paginate helper:
import { paginate } from '@gigadrive/sdk';
for await (const object of paginate((cursor) => client.storage.objects.list('assets', { cursor }))) {
console.log(object.key);
}Errors
All errors extend GigadriveError. Notable subclasses: ApiError (with status
and optional code), AuthenticationError, ConfigurationError, UploadError, and
UploadSessionExpiredError.
import { ApiError } from '@gigadrive/sdk';
try {
await client.deployments.get('missing');
} catch (err) {
if (err instanceof ApiError) console.error(err.status, err.message);
}License
Apache-2.0
