JavaScript/TypeScript SDK for the Flo distributed systems platform. This monorepo contains packages for both Node.js and browser environments.
| Package | Description | Transport |
|---|---|---|
@floruntime/core |
Core wire protocol, types, and transport-agnostic operations | - |
@floruntime/node |
Node.js client with TCP transport | TCP |
@floruntime/web |
Browser client with WebSocket transport | WebSocket |
npm install @floruntime/node
# or
pnpm add @floruntime/nodenpm install @floruntime/web
# or
pnpm add @floruntime/webNote: The
@floruntime/webpackage requires WebSocket server support on the Flo server. See the Flo documentation for WebSocket endpoint configuration.
import { FloClient } from "@floruntime/node";
const client = new FloClient("localhost:9000", {
namespace: "myapp",
timeoutMs: 5000,
});
await client.connect();
// KV operations
const encoder = new TextEncoder();
const decoder = new TextDecoder();
await client.kv.put("greeting", encoder.encode("Hello, Flo!"));
const value = await client.kv.get("greeting");
console.log(decoder.decode(value!)); // "Hello, Flo!"
// Queue operations
const seq = await client.queue.enqueue(
"tasks",
encoder.encode(JSON.stringify({ task: "process" }))
);
const result = await client.queue.dequeue("tasks", 10);
for (const msg of result.messages) {
console.log(decoder.decode(msg.payload));
await client.queue.ack("tasks", [msg.seq]);
}
await client.close();import { FloClient } from "@floruntime/web";
const client = new FloClient("wss://flo.example.com/ws", {
namespace: "myapp",
});
await client.connect();
// Same API as Node.js
await client.kv.put("key", new TextEncoder().encode("value"));
const value = await client.kv.get("key");
await client.close();interface ClientOptions {
namespace?: string; // Default namespace for operations (default: "default")
timeoutMs?: number; // Connection and operation timeout in ms (default: 5000)
debug?: boolean; // Enable debug logging (default: false)
}| Method | Signature | Description |
|---|---|---|
get |
get(key, opts?) → Promise<Uint8Array | null> |
Get value, null if not found |
put |
put(key, value, opts?) → Promise<void> |
Set key-value |
delete |
delete(key, opts?) → Promise<void> |
Delete key |
scan |
scan(prefix, opts?) → Promise<ScanResult> |
Prefix scan with pagination |
history |
history(key, opts?) → Promise<VersionEntry[]> |
Get version history |
interface PutOptions {
namespace?: string; // Override default namespace
ttlSeconds?: bigint; // Time-to-live in seconds
casVersion?: bigint; // CAS version for optimistic locking
ifNotExists?: boolean; // Only put if key doesn't exist
ifExists?: boolean; // Only put if key exists
}interface ScanOptions {
namespace?: string;
cursor?: Uint8Array; // Pagination cursor
limit?: number; // Max entries to return
keysOnly?: boolean; // Return only keys, not values
}| Method | Signature | Description |
|---|---|---|
enqueue |
enqueue(queue, payload, opts?) → Promise<bigint> |
Add message, returns seq |
dequeue |
dequeue(queue, count, opts?) → Promise<DequeueResult> |
Fetch messages |
ack |
ack(queue, seqs, opts?) → Promise<void> |
Acknowledge messages |
nack |
nack(queue, seqs, opts?) → Promise<void> |
Negative ack (retry or DLQ) |
dlqList |
dlqList(queue, opts?) → Promise<DequeueResult> |
List DLQ messages |
dlqRequeue |
dlqRequeue(queue, seqs, opts?) → Promise<void> |
Move DLQ messages back |
interface EnqueueOptions {
namespace?: string;
priority?: number; // Higher = processed first (0-255)
delayMs?: bigint; // Delay before message becomes available
dedupKey?: string; // Deduplication key
}interface DequeueOptions {
namespace?: string;
visibilityTimeoutMs?: number; // Lease duration in ms
blockMs?: number; // Wait for messages if queue empty
}Actions are registered tasks that can be invoked and executed by workers.
| Method | Signature | Description |
|---|---|---|
register |
register(name, actionType?, opts?) → Promise<void> |
Register an action |
invoke |
invoke(name, input, opts?) → Promise<ActionInvokeResult> |
Invoke an action |
status |
status(runId, opts?) → Promise<ActionRunStatus> |
Get run status |
list |
list(opts?) → Promise<ActionListResult> |
List actions |
delete |
delete(name, opts?) → Promise<void> |
Delete an action |
import { FloClient, ActionType } from "@floruntime/node";
const client = new FloClient("localhost:9000");
await client.connect();
// Register an action
await client.action.register("process-image", ActionType.User, {
timeoutMs: 60000,
maxRetries: 3,
});
// Invoke an action
const result = await client.action.invoke(
"process-image",
encoder.encode(JSON.stringify({ imageUrl: "https://..." })),
{ priority: 10, idempotencyKey: "order-123" }
);
console.log(`Run ID: ${result.runId}`);
// Check status
const status = await client.action.status(result.runId);
console.log(`Status: ${status.status}`);Workers execute tasks for registered actions.
| Method | Signature | Description |
|---|---|---|
register |
register(workerId, taskTypes, opts?) → Promise<void> |
Register a worker |
awaitTask |
awaitTask(workerId, taskTypes, opts?) → Promise<WorkerAwaitResult> |
Wait for task |
touch |
touch(workerId, taskId, opts?) → Promise<void> |
Extend task lease |
complete |
complete(workerId, taskId, result, opts?) → Promise<void> |
Complete task |
fail |
fail(workerId, taskId, error, opts?) → Promise<void> |
Fail task |
list |
list(opts?) → Promise<WorkerListResult> |
List workers |
import { FloClient } from "@floruntime/node";
const client = new FloClient("localhost:9000");
await client.connect();
const workerId = "worker-1";
const taskTypes = ["process-image"];
// Register worker
await client.worker.register(workerId, taskTypes);
// Worker loop
while (true) {
const result = await client.worker.awaitTask(workerId, taskTypes, {
blockMs: 30000,
});
if (!result.task) continue;
const task = result.task;
try {
// Process task
const output = await processImage(task.payload);
await client.worker.complete(workerId, task.taskId, output);
} catch (err) {
await client.worker.fail(workerId, task.taskId, String(err), {
retry: true,
});
}
}Streams are append-only logs for event sourcing and real-time data.
| Method | Signature | Description |
|---|---|---|
append |
append(stream, payload, opts?) → Promise<StreamAppendResult> |
Append to stream |
read |
read(stream, opts?) → Promise<StreamReadResult> |
Read from stream |
groupJoin |
groupJoin(stream, group, consumer, opts?) → Promise<void> |
Join consumer group |
groupRead |
groupRead(stream, opts) → Promise<StreamReadResult> |
Read from group |
groupAck |
groupAck(stream, seqs, opts) → Promise<void> |
Ack in group |
// Append to stream
const result = await client.stream.append(
"events",
encoder.encode(JSON.stringify({ event: "click", userId: "123" })),
{ partitionKey: "user-123" }
);
console.log(`Offset: ${result.seq}`);
// Read from stream
const records = await client.stream.read("events", {
startMode: StreamStartMode.Tail,
limit: 10,
blockMs: 5000,
});
for (const record of records.records) {
console.log(decoder.decode(record.payload));
}
// Consumer groups
await client.stream.groupJoin("events", "processors", "consumer-1");
const groupRecords = await client.stream.groupRead("events", {
group: "processors",
consumer: "consumer-1",
limit: 10,
});
await client.stream.groupAck("events", groupRecords.records.map(r => r.seq), {
group: "processors",
});The SDK provides typed errors for different failure modes:
import {
FloError,
NotFoundError,
ConflictError,
BadRequestError,
TimeoutError,
isNotFound,
isConflict,
} from "@floruntime/node";
try {
await client.kv.put("key", value, { casVersion: 1n });
} catch (err) {
if (isConflict(err)) {
// CAS version mismatch - retry with new version
} else if (isNotFound(err)) {
// Key not found
} else if (err instanceof TimeoutError) {
// Operation timed out
}
}| Error | Description |
|---|---|
FloError |
Base error class |
NotConnectedError |
Client not connected |
ConnectionError |
Connection failed |
TimeoutError |
Operation timed out |
NotFoundError |
Resource not found |
ConflictError |
CAS version mismatch |
BadRequestError |
Invalid request |
UnauthorizedError |
Authentication failed |
OverloadedError |
Server overloaded |
InternalError |
Internal server error |
- Node.js >= 18
- pnpm >= 8
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run tests
pnpm testsdks/js/
├── packages/
│ ├── core/ # @floruntime/core
│ │ └── src/
│ │ ├── types.ts
│ │ ├── errors.ts
│ │ ├── wire.ts
│ │ ├── kv.ts
│ │ ├── queue.ts
│ │ └── index.ts
│ ├── node/ # @floruntime/node
│ │ └── src/
│ │ ├── transport.ts
│ │ ├── client.ts
│ │ └── index.ts
│ └── web/ # @floruntime/web
│ └── src/
│ ├── transport.ts
│ ├── client.ts
│ └── index.ts
├── tests/
│ └── wire.test.ts
├── examples/
│ ├── node-basic.ts
│ └── browser-basic.html
├── package.json
├── pnpm-workspace.yaml
└── tsconfig.base.json
MIT