action() is the mutation API for eclipsa.
Use it when the client should submit data to the server, run side effects, and reflect the latest submission state back into the UI.
import { action } from 'eclipsa'
const useCreateTodo = action(async () => {
return {
ok: true,
}
})
export default function TodoComposer() {
const createTodo = useCreateTodo()
return (
<button disabled={createTodo.isPending} onClick={() => void createTodo.action()}>
{createTodo.isPending ? 'Saving...' : 'Create todo'}
</button>
)
}
An action handle such as useCreateTodo() exposes:
Form: a form component that posts to the action endpoint
action(): a function that submits input programmatically
isPending: whether a submission is in flight
result: the latest successful result
error: the latest submission error
lastSubmission: the latest submission snapshot including input, result, and error
Use Form when the mutation should be triggered by a native form submit.
import { action, validator, type StandardSchemaV1 } from 'eclipsa'
const formSchema = {
'~standard': {
types: undefined as unknown as {
input: { title: string }
output: { title: string }
},
validate(value: unknown) {
if (
value &&
typeof value === 'object' &&
typeof (value as Record<string, unknown>).title === 'string'
) {
return { value: value as { title: string } }
}
return {
issues: [{ message: 'title is required' }],
}
},
vendor: 'docs',
version: 1 as const,
},
} satisfies StandardSchemaV1<{ title: string }, { title: string }>
const useCreateTodo = action(validator(formSchema), async (c) => {
return { title: c.var.input.title }
})
export default function TodoForm() {
const createTodo = useCreateTodo()
return (
<createTodo.Form class="flex gap-2">
<input name="title" />
<button disabled={createTodo.isPending} type="submit">
Add
</button>
</createTodo.Form>
)
}
When a form submits, eclipsa normalizes the FormData payload and sends it through the same action pipeline as action().
Pass JSON-serializable input directly when you want to submit from event handlers.
import { action } from 'eclipsa'
const useRename = action(async (c) => {
return {
savedName: c.var.input?.name ?? '',
}
})
export default function RenameButton() {
const rename = useRename()
return <button onClick={() => void rename.action({ name: 'eclipsa' })}>Rename</button>
}
Use validator() to validate and transform incoming input before it reaches the action handler.
import { action, validator, type StandardSchemaV1 } from 'eclipsa'
const todoSchema = {
'~standard': {
types: undefined as unknown as {
input: { title: string }
output: { title: string }
},
validate(value: unknown) {
if (
value &&
typeof value === 'object' &&
typeof (value as Record<string, unknown>).title === 'string'
) {
return { value: value as { title: string } }
}
return {
issues: [{ message: 'title is required' }],
}
},
vendor: 'docs',
version: 1 as const,
},
} satisfies StandardSchemaV1<{ title: string }, { title: string }>
const useCreateTodo = action(validator(todoSchema), async (c) => {
return {
title: c.var.input.title,
}
})
After validation, the transformed value is available as c.var.input.
In validation, schema follows Standard Schema, so you can also use libraries like Zod, valibot, ArkType, and more with adapters.
import * as z from 'zod'
validator(z.object({ title: z.string() }))
import * as v from 'valibot'
validator(v.object({ title: v.string() }))
Actions can use middleware the same way loaders do.
import { action, type ActionMiddleware } from 'eclipsa'
const requestMeta: ActionMiddleware<{
Variables: {
traceId: string
}
}> = async (c, next) => {
c.set('traceId', crypto.randomUUID())
await next()
}
const useCheckout = action(requestMeta, async (c) => {
return {
traceId: c.var.traceId,
}
})
Values written with c.set() in middleware are available as c.var inside the handler.
Actions can also return async generators or readable streams.
import { action } from 'eclipsa'
const useCounter = action(async function* () {
yield 0
yield 1
})
In that case, action() returns an async generator and result tracks the latest emitted value.
Action input and output should stay public and serializable, such as:
strings
numbers
booleans
plain objects
arrays
FormData for native form submission
Avoid passing opaque server-only objects unless they are values explicitly supported by eclipsa's action serialization.