MutationAdapter
Interface for handling client-to-server mutation requests (create, update, delete).
Import
import type { MutationAdapter } from '@gluonic/client'Interface Definition
Exact definition from implementation:
export type MutationAdapter = {
mutate: (
t: string,
id: string,
patch: Record<string, any>,
token: string,
onUnauthenticated: () => void | Promise<void>,
clientTxId?: string
) => Promise<void>
create?: (
t: string,
id: string,
patch: Record<string, any>,
token: string,
onUnauthenticated: () => void | Promise<void>,
clientTxId?: string
) => Promise<void>
delete?: (
t: string,
id: string,
token: string,
onUnauthenticated: () => void | Promise<void>,
clientTxId?: string
) => Promise<void>
}Total: 3 methods (1 required, 2 optional). Default implementation sends HTTP requests to server sync endpoints.
Methods
mutate()
mutate(
t: string,
id: string,
patch: Record<string, any>,
token: string,
onUnauthenticated: () => void | Promise<void>,
clientTxId?: string
): Promise<void>Send mutation (update) to server.
Parameters:
| Parameter | Type | Description |
|---|---|---|
t | string | Model type name |
id | string | Model ID |
patch | Record<string, any> | Fields to update |
token | string | JWT auth token |
onUnauthenticated | () => void | Promise<void> | Callback if 401/403 response |
clientTxId | string | Optional client transaction ID |
Returns: Promise<void> - Resolves when server confirms
Example:
await mutationAdapter.mutate(
'task',
'abc-123',
{ title: 'Updated', done: true },
token,
() => router.push('/login'),
'tx-001'
)create() (Optional)
create?(
t: string,
id: string,
patch: Record<string, any>,
token: string,
onUnauthenticated: () => void | Promise<void>,
clientTxId?: string
): Promise<void>Send create mutation to server.
Optional: If not provided, falls back to mutate().
Parameters: Same as mutate()
Example:
await mutationAdapter.create(
'task',
'abc-123',
{ title: 'New Task', done: false },
token,
() => router.push('/login'),
'tx-002'
)delete() (Optional)
delete?(
t: string,
id: string,
token: string,
onUnauthenticated: () => void | Promise<void>,
clientTxId?: string
): Promise<void>Send delete mutation to server.
Optional: If not provided, falls back to mutate() with empty patch.
Parameters:
| Parameter | Type | Description |
|---|---|---|
t | string | Model type name |
id | string | Model ID |
token | string | JWT auth token |
onUnauthenticated | () => void | Promise<void> | Callback if 401/403 response |
clientTxId | string | Optional client transaction ID |
Example:
await mutationAdapter.delete(
'task',
'abc-123',
token,
() => router.push('/login'),
'tx-003'
)Default Implementation
The default HTTP-based implementation:
const defaultMutationAdapter: MutationAdapter = {
async mutate(t, id, patch, token, onUnauthenticated, clientTxId) {
const response = await fetch('/sync/v1/tx', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
ops: [{
t,
id,
op: 'u',
patch,
client_tx_id: clientTxId
}]
})
})
if (response.status === 401 || response.status === 403) {
await onUnauthenticated()
throw new Error('Unauthenticated')
}
if (!response.ok) {
throw new Error(`Mutation failed: ${response.statusText}`)
}
}
}Custom Implementation
Implement custom mutation adapter for special use cases:
import type { MutationAdapter } from '@gluonic/client'
class GraphQLMutationAdapter implements MutationAdapter {
async mutate(t, id, patch, token, onUnauthenticated, clientTxId) {
const mutation = `
mutation UpdateModel($type: String!, $id: String!, $patch: JSON!) {
updateModel(type: $type, id: $id, patch: $patch) {
success
}
}
`
const response = await fetch('/graphql', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: mutation,
variables: { type: t, id, patch }
})
})
if (response.status === 401) {
await onUnauthenticated()
throw new Error('Unauthenticated')
}
const result = await response.json()
if (!result.data?.updateModel?.success) {
throw new Error('Mutation failed')
}
}
}
// Use in SyncClient
const client = SyncClient({
server: 'https://api.example.com',
storage,
models,
mutations: new GraphQLMutationAdapter()
})Batching Behavior
Mutations are automatically batched within the same tick:
// Multiple mutations
await store.save('task', '1', { done: true })
await store.save('task', '2', { done: true })
await store.save('task', '3', { done: true })
// Single request sent:
mutate(/* batched ops array */)Error Handling
Authentication Errors
const mutationAdapter: MutationAdapter = {
async mutate(t, id, patch, token, onUnauthenticated) {
const response = await fetch('/sync/v1/tx', {
headers: { 'Authorization': `Bearer ${token}` },
// ...
})
// Handle 401/403
if (response.status === 401 || response.status === 403) {
await onUnauthenticated() // Trigger logout/re-auth
throw new Error('Unauthenticated')
}
}
}Network Errors
// Mutations automatically retry on network failure
// Failed mutations stay in transaction queue
// Retried when network returnsUse Cases
Custom Protocol
Implement for non-HTTP backends:
class WebSocketMutationAdapter implements MutationAdapter {
async mutate(t, id, patch, token, onUnauthenticated, clientTxId) {
// Send over WebSocket instead of HTTP
ws.send(JSON.stringify({
type: 'mutation',
t, id, patch, clientTxId
}))
// Wait for server confirmation
await waitForAck(clientTxId)
}
}Custom Authentication
class CustomAuthMutationAdapter implements MutationAdapter {
async mutate(t, id, patch, token, onUnauthenticated) {
const response = await fetch('/sync/v1/tx', {
headers: {
'X-Custom-Auth': token, // Custom header
'X-API-Key': process.env.API_KEY
},
// ...
})
}
}See Also
- Store - Uses MutationAdapter for mutations
- SyncClient - Configure custom mutation adapter
- SyncFrame - Frame format sent to server