Workflow
- 1Follow the managed-database guide to publish a customers table with id and name columns. Set PERMUTE_DATABASE_ID to that database ID.
- 2Create an action with business action creation admin permission. Editing and publishing require admin permission on the action and each selected database or connector.
- 3Save the handler, input and output contracts, invocation permission, and published schema bindings as one draft.
- 4Publish the exact draft through client.revisions, then invoke it with a stable requestId and input.
Complete example
import { PermuteClient } from '@permute/sdk';
const client = new PermuteClient({ apiKey: process.env.PERMUTE_API_KEY! })
.withWorkspace(process.env.PERMUTE_WORKSPACE_ID!);
const databaseId = process.env.PERMUTE_DATABASE_ID!;
const schema = await client.databases.definition(databaseId);
if (!schema.published) throw new Error('Publish the database schema first');
// Keep the ID after creation. Set PERMUTE_ACTION_ID to edit this action later.
const action = process.env.PERMUTE_ACTION_ID
? await client.actions.get(process.env.PERMUTE_ACTION_ID)
: await client.actions.create({ name: 'Create customer' });
console.log('Action ID:', action.id);
const current = await client.actions.definition(action.id);
const contract = {
type: 'object',
properties: { id: { type: 'string', minLength: 1 }, name: { type: 'string', minLength: 1 } },
required: ['id', 'name'],
additionalProperties: false,
};
const saved = await client.actions.saveDefinition(action.id, {
expectedDraft: current.draft
? { revisionId: current.draft.revisionId, sequence: current.draft.sequence }
: null,
definition: {
source: "exports.handler = ({ input, db }) => { db.insert('customers', input); return input; };",
inputSchema: contract,
outputSchema: contract,
invokePermission: 'write',
sources: [{
sourceId: databaseId,
revisionId: schema.published.revisionId,
readTables: [],
writeTables: ['customers'],
}],
},
});
const history = await client.revisions.list(action.id);
if (history.draft?.revisionId !== saved.revisionId) {
throw new Error('Action draft changed; review before publishing');
}
await client.revisions.publish(action.id, { expectedVersion: history.version });
// Persist this request before sending if retries must survive a process restart.
const request = { requestId: crypto.randomUUID(), input: { id: crypto.randomUUID(), name: 'Acme' } };
const receipt = await client.actions.invoke(action.id, request);
console.log(receipt.revisionId, receipt.output);
// After a lost response, retry with the identical requestId and input.Permissions and publication
The author delegates selected database tables and connector accounts when saving and publishing. Callers need the declared read, write, or admin permission on the action; they do not need general write access to its database. Use the narrowest action contract that fits the operation.
client.actions.list exposes published input and output contracts without source code. client.actions.definition requires admin permission and returns draft and published definitions. History, restore, and publication use client.revisions; restoring does not publish or execute the action.
Hosted apps bind an exact published action revision in apps.saveSite and call it through @permute/sdk/app. The deploy-app guide covers bindings and browser calls. API-key invocations use the current published action, while a retry retains the revision selected by its first attempt.
Writes, retries, and limits
Handlers run sandboxed JavaScript. db.insert, db.update, and db.delete stage local writes until the handler completes and its output validates. An action can write to one managed database, with up to 100 mutations; the mutations and receipt commit atomically. Reads are outside that write transaction.
A new user operation needs a new requestId. Reuse both requestId and input when retrying an uncertain response (changing the payload is not a retry). Do not generate a new ID merely because a network request failed.
Optional connectors maps names to connector IDs. Handlers call connectors.<name>.request with provider-relative HTTP paths, query for supported SQL providers, or putObject for S3. Each external call needs a stable stepId; credentials remain on the server, and the connected account’s grants apply.
External effects happen when awaited and cannot be rolled back with local database writes. Successful steps replay their saved responses; uncertain outcomes block automatic resend and require checking provider state. There is no reconciliation API or long-running workflow runner. Changing a published database write target requires a new action.