PermuteDocs
Go to Permute

Guide

Create and update database records

Define a managed table, read filtered rows, and update records with version checks and safe retries.

Workflow

  1. 1Create and wait for a database
  2. 2Create a typed table
  3. 3Insert and filter records
  4. 4Update with __version
  5. 5Handle a conflict

Complete example

typescript
import { PermuteApiError, PermuteClient } from '@permute/sdk';

const orgClient = new PermuteClient({ apiKey: process.env.PERMUTE_API_KEY! });
const client = orgClient.withWorkspace(process.env.PERMUTE_WORKSPACE_ID!);

// Creation needs an eligible Enterprise plan and workspace database admin access.
let database = await client.databases.create({ name: 'CRM', key: 'crm' });
while (database.status === 'provisioning') {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  database = await client.databases.get(database.id);
}
if (database.status !== 'ready') throw new Error('Database is not ready');

await client.databases.createTable(database.id, {
  name: 'customers',
  columns: [
    { name: 'id', type: 'text', nullable: false },
    { name: 'name', type: 'text', nullable: false },
  ],
  primaryKey: ['id'],
});

const insert = {
  requestId: 'customer-acme-insert-1',
  mutations: [{ type: 'insert' as const, table: 'customers', values: { id: 'acme', name: 'Acme' } }],
};
await client.databases.transact(database.id, insert);
// If the response is uncertain, resend the identical requestId and payload.
await client.databases.transact(database.id, insert);

const filtered = await client.query({
  sourceIds: [database.id],
  sql: "SELECT id, name FROM app.customers WHERE id = 'acme'",
});
console.log(filtered.rows);

const rows = await client.databases.records(database.id, 'customers', { limit: 25 });
const customer = rows.items.find((row) => row.id === 'acme');
if (!customer || typeof customer.__version !== 'string') throw new Error('Customer not found');

await client.databases.transact(database.id, {
  requestId: 'customer-acme-update-1',
  mutations: [{
    type: 'update', table: 'customers', key: { id: 'acme' },
    expectedVersion: customer.__version, values: { name: 'Acme Inc.' },
  }],
});

try {
  await client.databases.transact(database.id, {
    requestId: 'customer-acme-stale-update-1',
    mutations: [{
      type: 'update', table: 'customers', key: { id: 'acme' },
      expectedVersion: customer.__version, values: { name: 'Acme LLC' },
    }],
  });
} catch (error) {
  if (!(error instanceof PermuteApiError) || error.status !== 409) throw error;
  const current = await client.databases.records(database.id, 'customers');
  console.log('Record changed; review before retrying:', current.items);
}

Write rules

Run this example once with a fresh database key. Table creation and insertion are one-time operations. Use a new requestId for each new mutation; reuse it only when retrying the same payload after an uncertain response.

Record reads return __version for managed tables. An update or delete needs the complete primary key and the version just read. A 409 means the record changed or the request ID was reused with different data (read current state and decide what to write).

Filtered reads use client.query with read-only SQL against the database. client.databases.records provides bounded current-state pages but does not accept a filter. Pagination is not a stable snapshot.