Mutations
Create, update, and delete entities from the server client (createPyloServer / createPyloNode)
or from Client Components with the hooks.
Server
Upsert
Create or update with the __search_value pattern. Pylo updates the record that matches the search,
or creates one when nothing matches.
1
2
3
4
5
6
7
8
9
10
11
const result = await pylo.invoice.upsert({
__search_value: {
field: 'invoice_number',
value: 'INV-001',
not_found_behavior: 'create',
},
invoice_number: 'INV-001',
amount_net: 1000,
});
// result: { id: string }With relations
Use _set to connect related entities during upsert:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
await pylo.invoice.upsert({
__search_value: {
field: 'invoice_number',
value: 'INV-001',
not_found_behavior: 'create',
},
invoice_number: 'INV-001',
amount_net: 1500,
customer_set: {
__search_value: {
field: 'customer_number',
value: 'C-001',
not_found_behavior: 'create',
},
name: 'Acme Corp',
},
});Search values
__search_value targets the record by a field instead of an id. It works at the top level and
inside nested relation upserts (as in the example above).
| Option | Default | Description |
|---|---|---|
field | required | Field to search by |
value | Value to match | |
not_found_behavior | 'create' | 'create' inserts a new record, 'error' fails, 'ignore' skips (nested relation upserts only) |
search_in_all_field_variants | false | Search across all variants of the field instead of only the requested ones |
multiple_results_allowed | false | Allow more than one match; without it, multiple matches fail the mutation |
multiple_results_use_latest | true | With multiple matches allowed, use the newest record (false uses the oldest) |
Bulk upsert
Upserts many rows in one all-or-nothing transaction. Rows without an id or __search_value are
created. The ids come back in input order.
1
2
3
4
5
6
const results = await pylo.invoice.bulkUpsert([
{ invoice_number: 'INV-002', amount_net: 500 },
{ id: 'invoice-uuid', amount_net: 750 },
]);
// results: { id: string }[]Delete
1
2
const result = await pylo.invoice.delete(['invoice-uuid-1', 'invoice-uuid-2']);
// result: { success: boolean }Client hooks
usePyloUpsert
Invalidates the entity's list queries on success.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use client';
import { pylo } from "@/lib/pylo-hooks";
const { mutateAsync } = pylo.usePyloUpsert('invoice');
await mutateAsync({
__search_value: {
field: 'invoice_number',
value: 'INV-001',
not_found_behavior: 'create',
},
invoice_number: 'INV-001',
amount_net: 1000,
});usePyloBulkUpsert works the same way with an array input.
usePyloDelete
1
2
3
const { mutateAsync } = pylo.usePyloDelete('invoice');
await mutateAsync(['invoice-uuid-1']);Query invalidation
Mutation hooks invalidate all queries for the same entity, so list and detail views refetch. Query key structure:
- Lists:
['pylo', entityName, 'list', options] - Infinite lists:
['pylo', entityName, 'infiniteList', options] - By ID:
['pylo', entityName, 'byId', id, options]
Mutations invalidate the prefix ['pylo', entityName], which covers all of the above.