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, search, 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:3004' },
});
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, deleteBlocks, 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. Item types are read-only here: they are data tables, designed in the environment designer.
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. Seven surfaces have an auto-paging companion that
returns an async iterable and fetches successive pages for you:
documents.iterate, storage.files.iterate, data.savedQueries.iterate,
data.tables.iterateObjects, data.tables.iterateArchivedObjects,
data.tables.searchObjectsAll, and search.searchAll.
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
}
Every other list method returns a single page; page it yourself with
limit/offset or the nextCursor it returns.
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.
A 429 is rejected before the handler runs, so replaying is safe: any method is
retried, writes included, unless you set retryWrites: false. A 503 may
come from inside a handler that already committed a write, so it is retried for
GETs only and never replays a write, whatever retryWrites says. 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 an entity. 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. The one
exception is documents.markdown.deleteBlocks, which permanently
removes blocks from a document body and requires an audit reason.