Skip to content

feat: Mutator example#2

Draft
Chriztiaan wants to merge 2 commits into
feat/initfrom
feat/mutator
Draft

feat: Mutator example#2
Chriztiaan wants to merge 2 commits into
feat/initfrom
feat/mutator

Conversation

@Chriztiaan
Copy link
Copy Markdown
Collaborator

@Chriztiaan Chriztiaan commented May 7, 2026

This PoC explores the idea of a mutator pattern for use in the write path. A strong example to reference is Zero's implementation.

The client defines a named mutator with an args schema and a run function that writes to the local SQLite database, including metadata about the mutator call (the mutator name and its arguments), and is invoked directly from the UI: mutate.listCreate({ id, name }).
This write is queued via the ps_crud upload queue, where a matching server-side mutator with the same name receives the original args and applies them directly to the source database, with no CRUD translation needed.

// 1. Call in UI
const createNewList = async (name: string) => {
    await mutate.listCreate({ id: uuid(), name });
};

// 2. Client mutator definition
// the run callback writes against the powersync db and applies the needed metadata
import { z } from 'zod';

const listCreateArgs = z.object({
  id: z.string().uuid(),
  name: z.string().min(1)
});

const listCreate: ClientMutator<typeof listCreateArgs> = {
  args: listCreateArgs,
  run: async (args, tx, ctx) => {
    await tx.execute(
      `INSERT INTO ${LISTS_TABLE} (id, created_at, name, owner_id, _metadata)
       VALUES (?, datetime(), ?, ?, ?)`,
      [args.id, args.name, ctx.userId, ctx.metadata]
    );
  }
};

// 3. Backend mutator definition
// The run function can take the received args and apply it to the source db directly
const listCreate: ServerMutator<typeof listCreateArgs> = {
  args: listCreateArgs,
  run: async (args, ctx) => {
    await ctx.pg.query(
      `INSERT INTO lists (id, name, owner_id) VALUES ($1, $2, $3)`,
      [args.id, args.name, ctx.userId]
    );
  }
};

Pros:

  • This means no CRUD-to-SQL translation on the backend. The server works with typed, named arguments rather than raw operations
  • Business logic lives in a well-defined, co-located, testable place on both client and server
  • Potentially a better fit for complex or enterprise projects where fine-grained control over each write operation matters
  • Opens the door to richer validation (via Zod schemas shared between client and server)

What's still open: Delete workaround, metadata threading made easier

AI disclosure

This PR was created with the help of Claude Code. Help constitutes assistance in research, planning, and rough outline of implementation. Beyond having a hand in the implementation, I have also manually tested this work.

@Chriztiaan Chriztiaan changed the title feat: Mutator pattern feat: Mutator example May 11, 2026
@Chriztiaan Chriztiaan mentioned this pull request May 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant