The TypeScript SDK is intended for environments where FUSE mounting is not available — such as serverless functions,
containers without CAP_SYS_ADMIN, browser-adjacent runtimes, or CI/CD pipelines. If you can mount via FUSE
using the Archil CLI, prefer that approach: it provides full POSIX compatibility, works with
any language or tool, and benefits from the client’s local caching layer.
The Archil TypeScript SDK provides two packages:
| Package | Description |
|---|
@archildata/client | Core client with native (NAPI) bindings to the Archil protocol. Provides low-level inode operations and a control plane API. |
@archildata/just-bash | Higher-level filesystem adapter built on @archildata/client. Provides path-based operations (readFile, writeFile, mkdir, etc.) and an interactive shell. |
Most applications should use both packages together: @archildata/client to establish the connection and
@archildata/just-bash for filesystem operations.
The native module ships pre-built binaries for:
- Linux x64 (glibc)
- Linux ARM64 (glibc)
- macOS ARM64 (Apple Silicon)
Node.js 18 or later is required.
Installation
npm install @archildata/client @archildata/just-bash
Connecting to a disk
IAM authentication (AWS)
When running on an EC2 instance or any environment with AWS credentials available, the client authenticates
automatically using IAM:
import { ArchilClient } from "@archildata/client";
const client = await ArchilClient.connect({
region: "aws-us-east-1",
diskName: "myorg/mydisk",
});
Disk token authentication
Outside of AWS, or when using explicit credentials, pass a disk token —
the per-disk credential generated from the disk’s Details page in the Archil console:
const client = await ArchilClient.connect({
region: "aws-us-east-1",
diskName: "myorg/mydisk",
authToken: process.env.ARCHIL_DISK_TOKEN,
});
Store disk tokens in environment variables rather than hardcoding them. The SDK checks ARCHIL_DISK_TOKEN automatically
if no authToken is provided.
A disk token is not an API key. Disk tokens grant mount access to a single disk; API keys authenticate
requests to the Control Plane API for managing disks programmatically.
The CLI reads the same disk token from ARCHIL_MOUNT_TOKEN — the two environment variable names refer to the same kind of credential.
Connection options
interface SimpleConnectionConfig {
region: string; // e.g., "aws-us-east-1", "gcp-us-central1"
diskName: string; // "org/disk-name" or disk ID "dsk-xxx"
authToken?: string; // Disk token (defaults to IAM when omitted)
stsRegion?: string; // Override STS region for IAM auth
logLevel?: string; // "trace" | "debug" | "info" | "warn" | "error"
}
Environment variables
The SDK reads these environment variables as fallbacks:
| Variable | Description |
|---|
ARCHIL_REGION | Region identifier |
ARCHIL_LOG_LEVEL | Log level |
ARCHIL_API_KEY | API key for control plane operations |
Filesystem operations
Create an ArchilFs instance to work with paths instead of raw inode IDs:
import { ArchilClient } from "@archildata/client";
import { ArchilFs } from "@archildata/just-bash";
const client = await ArchilClient.connect({
region: "aws-us-east-1",
diskName: "myorg/mydisk",
});
const fs = await ArchilFs.create(client);
Reading files and directories
// Read a file as a UTF-8 string
const content = await fs.readFile("/config.json");
// Read a file as a buffer
const buffer = await fs.readFileBuffer("/image.png");
// List a directory
const names = await fs.readdir("/data");
// List with file types
const entries = await fs.readdirWithFileTypes("/data");
for (const entry of entries) {
console.log(`${entry.name} (file=${entry.isFile}, dir=${entry.isDirectory})`);
}
// Get file metadata
const info = await fs.stat("/data/report.csv");
console.log(`size=${info.size}, modified=${info.mtime}`);
// Check if a path exists
if (await fs.exists("/data/report.csv")) {
// ...
}
Writing files and directories
Write operations require a delegation on the target. The high-level writeFile and
mkdir methods handle delegation management automatically when possible.
// Write a file (atomic: writes to a temp file, then renames)
await fs.writeFile("/output/result.json", JSON.stringify(data));
// Append to a file
await fs.appendFile("/logs/app.log", `${new Date().toISOString()} event\n`);
// Create a directory
await fs.mkdir("/output/reports");
// Create nested directories
await fs.mkdir("/output/reports/2026/q1", { recursive: true });
// Copy a file
await fs.cp("/data/source.txt", "/data/copy.txt");
// Copy a directory tree
await fs.cp("/data/input", "/data/backup", { recursive: true });
// Move / rename
await fs.mv("/data/old-name.txt", "/data/new-name.txt");
// Create a symlink
await fs.symlink("/data/latest", "/data/reports/2026");
// Delete a file
await fs.rm("/data/temp.txt");
// Delete a directory tree
await fs.rm("/data/old-output", { recursive: true });
Permissions and timestamps
// Change permissions
await fs.chmod("/script.sh", 0o755);
// Update access and modification times
await fs.utimes("/data/file.txt", new Date(), new Date());
Subdirectory scoping
You can scope an ArchilFs instance to a subdirectory, making it the root of all path operations.
See Subdirectory Mounts for details.
const fs = await ArchilFs.create(client, {
subdirectory: "/data/project",
});
// "/" now refers to /data/project on the disk
const files = await fs.readdir("/");
Delegation management
When multiple clients access the same disk, you must manage delegations to
coordinate write access. The low-level client exposes delegation operations directly:
// Acquire exclusive write access to a file or directory
await client.checkout(inodeId);
// Force-revoke another client's delegation
await client.checkout(inodeId, { force: true });
// Release write access
await client.checkin(inodeId);
// Release all delegations
const count = await client.checkinAll();
// List current delegations
const delegations = client.listDelegations();
for (const d of delegations) {
console.log(`inode ${d.inodeId}: ${d.state}`);
}
Using force: true on checkout will immediately revoke another client’s delegation. Any unflushed
writes from that client will be lost.
Control plane API
The Archil class provides access to the control plane for managing disks and API keys programmatically.
It authenticates with an API key (ARCHIL_API_KEY), which is separate from the per-disk tokens described above:
import { Archil } from "@archildata/client/api";
const archil = new Archil({
apiKey: process.env.ARCHIL_API_KEY,
region: "aws-us-east-1",
});
// List disks
const disks = await archil.disks.list();
// Create a disk
const disk = await archil.disks.create({
name: "my-disk",
mounts: [{
type: "s3",
bucketName: "my-bucket",
accessKeyId: "xxx",
secretAccessKey: "xxx",
}],
});
// Manage tokens
const token = await archil.tokens.create({ name: "ci-token" });
const tokens = await archil.tokens.list();
await archil.tokens.delete(token.id);
The control plane API is also available as a REST API for non-TypeScript environments.
Interactive shell
The @archildata/just-bash package includes an interactive shell for exploring and modifying disks
from the terminal:
# Connect to a disk
npx @archildata/just-bash aws-us-east-1 myorg/mydisk
# With explicit token
ARCHIL_DISK_TOKEN=xxx npx @archildata/just-bash aws-us-east-1 myorg/mydisk
# Quick start with an S3 bucket
npx @archildata/just-bash s3://my-bucket --api-key adt_xxx
# Mount a subdirectory
npx @archildata/just-bash aws-us-east-1 myorg/mydisk:/data
The shell supports standard commands (ls, cat, cp, mv, rm, mkdir) plus Archil-specific
commands for delegation management:
archil checkout /path # Acquire write delegation
archil checkin /path # Release write delegation
archil list-delegations # Show held delegations
Closing the connection
Always close the client when finished to flush pending writes and release delegations:
close() syncs all outstanding writes to the server and releases all held delegations before
disconnecting.
Low-level inode operations
For advanced use cases, ArchilClient provides direct inode-level operations:
const user = { uid: 1000, gid: 1000 };
// Look up a child by name
const entry = await client.lookupInode(parentInodeId, "file.txt", { user });
if (entry) {
console.log(`inode=${entry.inodeId}, size=${entry.attributes.size}`);
}
// Get attributes
const attrs = await client.getAttributes(inodeId, { user });
// Create a file
const result = await client.create(parentInodeId, "new.txt", {
inodeType: "File",
uid: 1000,
gid: 1000,
mode: 0o644,
}, { user });
// Write data at an offset (requires checkout first)
await client.checkout(result.inodeId);
await client.writeData(result.inodeId, 0, Buffer.from("hello"));
await client.checkin(result.inodeId);
// Read data at an offset
const buf = await client.readInode(inodeId, 0, 1024, { user });
// Paginated directory listing
const handle = await client.openDirectory(dirInodeId, { user });
let cursor;
do {
const page = await client.readDirectory(dirInodeId, handle, 1000, cursor, { user });
for (const entry of page.entries) {
console.log(entry.name);
}
cursor = page.nextCursor;
} while (cursor);
await client.closeDirectory(dirInodeId, handle);