TypeScript SDK
@dalea/sdk by example — data, documents, inventory, and storage.
@dalea/sdk is the official public TypeScript SDK — a self-contained,
runtime-agnostic client (browser, Node, and edge) over the Dalea public API,
covering the data, documents, inventory, and storage domains. This page is
the by-example tour; the shared concepts (coverage, auth model, workspace
context, pagination, retries) live in the Dalea SDK overview,
and every operation in the API reference shows a ready-to-copy
@dalea/sdk snippet.
Install
npm install @dalea/sdk
Ships ESM + CJS builds and its own type declarations. No runtime dependencies. Node >= 20.
Construct a client
import { DaleaClient } from '@dalea/sdk';
const dalea = new DaleaClient({
baseUrl: 'https://dalea.app', // required outside the browser
apiKey: process.env.DALEA_API_KEY, // or omit — read from DALEA_API_KEY
});
The API key already pins the workspace, so that's all you need.
Every method is typed against the public contract, so an incorrect field or
path is a compile error — the SDK cannot silently drift from the API. If your
deployment serves domains from separate origins (e.g. a dev stack with one
port per service), add per-domain overrides with serviceUrls:
const dalea = new DaleaClient({
baseUrl: 'https://dalea.app',
serviceUrls: { data: 'http://localhost:3005', storage: 'http://localhost:3006' },
});
Credentials
Auth resolves in this precedence: explicit credential → apiKey →
sessionCookie → the DALEA_API_KEY environment variable. See the
overview for when to use each,
and Authentication for obtaining them.
import { DaleaClient, OAuthPkceCredential, OAuthClientCredentialsCredential } from '@dalea/sdk';
// API key — server scripts and backends. With DALEA_API_KEY set, this is all you need:
const dalea = new DaleaClient({ baseUrl: 'https://dalea.app' });
// Session cookie — first-party browser apps, same-origin only:
const browser = new DaleaClient({ sessionCookie: true });
// OAuth 2.1 + PKCE — acting on behalf of a user (auto-refresh on 401):
const pkce = await OAuthPkceCredential.exchangeCode({
tokenEndpoint: 'https://dalea.app/api/v1/auth/oauth2/token',
clientId: 'my-app',
code, // from the ?code=… redirect
codeVerifier, // from OAuthPkceCredential.generatePkcePair()
redirectUri: 'https://myapp.example/callback',
onTokens: (tokens) => persist(tokens), // called on every rotation
});
const onBehalfOfUser = new DaleaClient({ credential: pkce });
// OAuth 2.1 client-credentials — machine-to-machine:
const m2m = new DaleaClient({
credential: new OAuthClientCredentialsCredential({
tokenEndpoint: 'https://dalea.app/api/v1/auth/oauth2/token',
clientId: 'my-service',
clientSecret: process.env.OAUTH_CLIENT_SECRET!,
}),
});
An API key is a secret — use it from server code, never a browser bundle. In a
first-party browser app, construct with { sessionCookie: true }
instead.
Workspace context
API keys and OAuth credentials are bound to a workspace when created, so with
those you never set one. Session-cookie callers (which can reach many
workspaces) scope calls via the X-Workspace header: set a default on the
client (workspace), call dalea.setWorkspace(id), or pass a per-call
override — the per-call value wins:
const dalea = new DaleaClient({ sessionCookie: true, workspace: 'ws_123' });
await dalea.data.tables.get('t1', { workspace: 'other-workspace-id' });
Data
Walk your workspace's data: list environments, drill into the first one's first table, and report how many objects it holds.
const { environments } = await dalea.data.environments.list();
const env = environments[0];
const { tables } = await dalea.data.environments.listTables(env.id);
const table = tables[0];
const page = await dalea.data.tables.listObjects(table.id, { limit: 50 });
console.log(`${env.name} → ${table.name}: ${page.total} objects`);
Documents
Create a document, write its body as markdown, and read the whole document back as markdown:
const doc = await dalea.documents.create({
workspaceId: 'ws_123',
title: 'Assay run notes',
});
await dalea.documents.markdown.append(doc.id, {
markdown: '## Results\n\nAll 96 wells within expected range.',
});
const { markdown, blockCount } = await dalea.documents.markdown.outline(doc.id);
console.log(`${blockCount} blocks:\n${markdown}`);
The markdown sub-resource also supports insert, updateBlock,
replaceSection, and parse — see the API reference for each.
Inventory
Read the item-type catalogue, find a container by barcode, and log consumption from an item:
const { itemTypes } = await dalea.inventory.itemTypes.list();
const container = await dalea.inventory.containers.getByBarcode('FRZ-A-001');
await dalea.inventory.consumption.consume('item-uuid', {
quantity: 2.5,
reason: 'Assay run 42',
});
Placements handle physical movement — checkIn, batchCheckOut, move, and
placeObject under dalea.inventory.placements.
Search
Unified search across documents, files, data objects, and result schemas:
const { results, total } = await dalea.search.search({
query: 'aspirin',
types: 'document,data_object',
limit: 25,
});
// Scope data-object results to one or more tables (e.g. the same table
// name across several environments) — `tableIds` joins into the wire param:
const scoped = await dalea.search.search({ query: 'batch-42', tableIds: [tableA.id, tableB.id] });
// Auto-paged: walk every result across pages.
for await (const hit of dalea.search.searchAll({ query: 'aspirin' })) {
console.log(hit.type, hit.name);
}
Storage
Upload a file and hand out a short-lived presigned download URL:
// Multipart from raw bytes (browser File/Blob, or a Node Blob):
const { file } = await dalea.storage.upload.file({ file: myBlob, filename: 'plate-map.csv' });
// Or JSON with base64 content:
await dalea.storage.upload.base64({
filename: 'report.pdf',
mimeType: 'application/pdf',
data: base64Payload,
});
const { url } = await dalea.storage.files.getUrl(file.id);
Presigned URLs are short-lived — fetch a fresh one per download rather than storing it.
Pagination
List methods return one page; iterate* methods return an async iterable that
transparently fetches successive pages:
for await (const obj of dalea.data.tables.iterateObjects(tableId, { limit: 200 })) {
// every object, across all pages
}
for await (const doc of dalea.documents.iterate()) {
// every document — cursor paging handled for you
}
Errors
Non-2xx responses become typed DaleaError subclasses carrying status,
code, message, and data. Throttling (429) and transient 503s are retried
automatically with exponential backoff + jitter, honoring Retry-After — writes
included (a throttle is rejected before the handler runs, so replaying is safe).
A DaleaRateLimitError surfaces only once retries are exhausted. Tune with
maxRetries (default 2), retryWrites (default true — opt out for strict callers),
retryBaseDelayMs, and maxRetryDelayMs.
import { DaleaNotFoundError, DaleaRateLimitError, DaleaValidationError } from '@dalea/sdk';
try {
await dalea.data.tables.createObject(tableId, body);
} catch (err) {
if (err instanceof DaleaValidationError) console.error(err.data); // 400/422
else if (err instanceof DaleaNotFoundError) { /* 404 */ }
else if (err instanceof DaleaRateLimitError) console.log(err.retryAfterSeconds);
else throw err;
}
The public SDK never deletes. Lifecycle is reversible — archive /
restore across the data domain (environments, tables, columns,
objects, result batches, import mappings), and documents/inventory/storage
follow the same no-hard-delete posture.