Entity / Operators
A sixty-second path: a rule named once
A catalog lists a product once it has a publication date. That rule is asked in the public list, in a visitor's Read rule and in reports. Name it:
ts
import { Schema } from 'effect'
import { Collation, Entity, Expr, Query } from 'foldkit-entity'
const Product = Entity.define(
'Product',
Schema.Struct({
id: Schema.String,
upc: Schema.String,
label: Schema.String,
cents: Schema.Int,
publishedAt: Schema.NullOr(Schema.String),
}),
).pipe(Entity.annotateMembers({ upc: Collation.of(Collation.binary) }))
const { fields } = Product
const listed = Expr.operator('acme.listed', {
input: [Schema.NullOr(Schema.String)],
output: Schema.Boolean,
definedAs: publishedAt => Expr.isNotNull(publishedAt),
})
const Listed = Query.from(Product).pipe(Query.where(listed(fields.publishedAt)))
Query.show(Listed)
// FROM Product
// WHERE acme.listed(Product.publishedAt)
Query.operators(Listed) // ['acme.listed']What the calls mean
Expr.operatordeclares; it does nothing. No SQL, no evaluator, no registration. The id is namespaced (acme.;foldkit.is reserved) and has aversion, 1 by default, which changes when what it means does.definedAsbuilds an expression from the call's arguments, once per call. Every interpreter runs that expression, soListedcompiles to the same SQL asExpr.isNotNull(fields.publishedAt)written out, and the client judges it the same way. A booleanoutputmakes a call a predicate, usable inand,or,not, a guard or an invariant.- It reads only its arguments. A
definedAsthat closed over a field instead of taking it is refused when called, since nothing asking what a body reads would see the field. - What it adds is a name.
Query.showprints the call, andQuery.operatorslists what a body calls, so a reviewer, a log or a description says "listed" where it would have said "publishedAt is not null".
foldkit-cms ships one: Cms.published(entity), over its published role.