# TypeScript quickstart

> Install primitives-sdk and build a group with a database, KV cache, job queue and memory from TypeScript.

Source: https://useprimitives.com/docs/sdk/typescript/quickstart

This guide builds a small agent backend with the TypeScript SDK: a group with a database, a KV cache, a job queue and memory. It runs against a local engine; switching to the cloud is one line.

## 1. Install

```bash
npm i primitives-sdk
```

The SDK ships ESM and CommonJS builds with full types, and needs Node.js 18 or later. It also runs on Bun, Deno and edge runtimes that have `fetch`.

## 2. Start an engine

Run the engine locally with the [CLI](/docs/cli):

```bash
primitives dev
```

Or use Primitives Cloud: create an API key in the dashboard and export it.

```bash
export PRIMITIVES_API_KEY="pk_…"
```

## 3. Create a client and a group

```ts title="agent.mts"
import { Primitives } from 'primitives-sdk';

// No key: http://localhost:7777. With a pk_ key: https://api.useprimitives.com.
const p = new Primitives({ token: process.env.PRIMITIVES_API_KEY });

const group = await p.groups.create({ name: 'agent-42' });
```

A group starts empty. Everything below lives in it.

## 4. Store data in a database

```ts
const db = await group.db.create('main');

await db.exec('create table tasks (id integer primary key, title text, status text)');
await db.sql`insert into tasks (title, status) values (${'Write docs'}, ${'open'})`;

const { rows } = await db.sql`select id, title from tasks where status = ${'open'}`;
console.log(rows); // [ { id: 1, title: 'Write docs' } ]
```

The tagged template binds every interpolation as a parameter, so values never become SQL.

## 5. Cache with KV

```ts
const cache = await group.kv.create('cache');

await cache.set('session:abc', { step: 1 }, { ttl: '1h' });
const entry = await cache.get<{ step: number }>('session:abc');
console.log(entry?.value?.step); // 1; entry.version is for compare-and-swap

await cache.incr('rate:agent-42', 1, { ttl: '1m' });
```

## 6. Run jobs on a queue

```ts
const jobs = await group.queue.create('jobs');
const emails = jobs.queue<{ to: string }>('emails');

await emails.send({ to: 'ada@example.com' }, { dedupeKey: 'welcome-ada' });

for await (const job of emails.consume({ lease: '5m' })) {
  try {
    await sendEmail(job.body);
    await job.ack();
  } catch (err) {
    await job.retry({ in: '1m', error: String(err) });
  }
}
```

`consume` long-polls forever; `break` out of the loop, or pass an `AbortSignal` as `signal`, to stop.

## 7. Give it memory

```ts
const notes = await group.memory.create('notes');

await notes.remember({ kind: 'semantic', text: 'User prefers pnpm over npm', tags: ['preferences'] });

const [hit] = await notes.recall('which package manager?', { limit: 1 });
console.log(hit.memory.text); // User prefers pnpm over npm
```

## 8. Snapshot before risky changes

```ts
await db.snapshot('before-migration');
const trial = await db.fork({ name: 'main-trial' });

await trial.exec('alter table tasks add column priority integer');
// Happy? Apply it to the original. If not, delete the fork:
await trial.delete();
```

## Handle errors

```ts
import { PrimitivesError } from 'primitives-sdk';

try {
  await group.db.create('main');
} catch (err) {
  if (err instanceof PrimitivesError && err.code === 'already_exists') {
    // reuse it instead
  } else {
    throw err;
  }
}
```

## Next steps

- [TypeScript reference](/docs/sdk/typescript): every method and type.
- [Use with a sandbox](/docs/guides/sandboxes): hand a scoped key to an agent.
- [Primitives](/docs/primitives/database): how each type behaves.
