Operations
Operations are the only public mutation surface. They are owned by an entity or trait, have stable catalog identities, and execute authoritatively inside one database.
New here? Start with Mutate data.
Targeted operation
Section titled “Targeted operation”const Task = Ramose.Entity("task", { title: Ramose.string(), done: Ramose.boolean(),}, { operations: (Operation) => ({ setDone: Operation({ doc: "Set whether this task is complete", input: S.Struct({ done: S.Boolean }), output: S.Struct({}), run(op, { done }) { op.self.set(Task.done, done) return {} }, optimistic({ tx, self, input }) { tx.set(self, Task.done, input.done) }, }), }),})Targeted operations default to self: true. The authoritative writer exposes the compatible owner as op.self and only the explicitly declared related write capabilities.
Targetless operation
Section titled “Targetless operation”Declare creation inside the same owner’s operations callback, using its Operation factory:
const createTask = Operation({ self: false, input: S.Struct({ title: S.String }), output: S.Struct({ task: Ramose.EntityId }), run(op, { title }) { const task = op.create({ title, done: false }) return { task } },})Targetless operations are invoked from db.mutate. Use them for creation and database-wide commands without an existing target.
Input and output
Section titled “Input and output”input and output are Effect Schemas. Validation occurs before the body or optimistic projection runs. Output is plain cloneable data stored with the receipt and returned on an identical retry.
Authoritative writer
Section titled “Authoritative writer”The writer can create compatible entities, set or retract allowed fields, and delete according to declared ownership and cascade rules. The commit applies required fields, defaults, fixed trait bindings, protected types, reference checks, uniqueness, and policy constraints atomically.
Operation-thrown domain rejections become operation_rejected. Unexpected faults remain internal errors and do not expose implementation details.
Typed reads
Section titled “Typed reads”Use op.query with the same portable Query builder used elsewhere. The
operation’s existing target has a resolved op.self.eid:
const task = await op.query(Query.from(Task) .where(Query.byId(op.self.eid)) .select({ title: Task.title }) .oneOrFail())Selected fields and result cardinality determine the return type.
Optimistic projection
Section titled “Optimistic projection”The optional optimistic function is synchronous and pure. It receives validated input, stable client entity references, and a local transaction builder. It cannot query local state, read a clock, inspect policy, run an Effect, or expand its write scope.
It is a preview only. The server body is the authority and its receipt reconciles the local layer.
Invocation and receipt
Section titled “Invocation and receipt”const receipt = task.mutate.setDone({ done: true })Every invocation has a client-generated invocationId. The server scopes the stored receipt to principal, database, operation, target, and input. Repeating the same invocation returns its original outcome. Reusing the id for a different intent returns invocation_conflict.
Receipt states are queued, committed, and rejected. Committed receipts include decoded output; rejected receipts include a public operation error.
Authorization
Section titled “Authorization”A targeted call requires an exact operation rule plus a visible compatible target. A targetless call requires its exact operation rule. An operation rule does not reveal an otherwise hidden target, and read visibility does not grant any operation.
Generated CRUD
Section titled “Generated CRUD”Catalog-generated create, set, and delete capabilities are ordinary owned operations. They are not automatically public; grant each exact operation just like a custom product command.