Remote / Declared writes
A sixty-second path: a precondition
A product is published only from a draft. The declaration says so; the writer checks it in the statement that writes.
ts
import { Schema } from 'effect'
import { Entity, Expr, Write } from 'foldkit-entity'
import { Mutation } from 'foldkit-remote'
const Product = Entity.define(
'Product',
Schema.Struct({
id: Schema.String,
name: Schema.String,
status: Schema.Literals(['draft', 'published', 'archived']),
stock: Schema.Number,
revision: Schema.Number,
}),
)
const Publish = Mutation.write(
'Publish',
Write.update(
Entity.input(Product, Schema.Struct({ id: Schema.String, status: Schema.Literal('published') })),
{ id: 'id', when: () => Expr.eq(Product.fields.status, 'draft') },
),
)On the server, the write is served with no handler:
ts
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
import { bind, writer } from 'foldkit-remote-drizzle'
import { RemoteServer } from 'foldkit-remote-server'
const products = sqliteTable('products', {
id: text('id').primaryKey(),
name: text('name').notNull(),
status: text('status').notNull(),
stock: integer('stock').notNull(),
revision: integer('revision').notNull(),
})
const Db = bind({ Product }, { Product: { table: products } })
const server = RemoteServer.make({
entities: [],
mutations: [RemoteServer.write(Publish, writer(Db.Product))],
})A draft is published. An archived product is refused as Unmet, and nothing is
written. A published one is answered as written and does not change.
What the calls mean
Write.update(input, { id, when })is a declaration. It runs nothing and reads nothing.whenis built once, at definition, into a query body over the Entity; awhenover another Entity is refused there.Schema.Literal('published')is how a write sets a constant. The server decodes the input before anything else, so a client cannot send another value. There is no separate constant-assignment API, because this is one.Mutation.writetypes the refusal. A write with awhenmay be refused asUnmetas well asConflict; one without has noUnmetarm to match. An insert expects no revision, so it is refused asTaken(its id is a row's), never asConflict.writer(Db.Product)is where I/O happens: oneupdate … where id = ? and <when>, then, if no row changed, one read to say why. It checks no rules; the guard, below, does.