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 credentialapiKeysessionCookie → 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!,
  }),
});
Keep keys server-side

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.

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-Afterwrites 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;
}
Archive, not delete

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.

Next