Service Middleware
The maniflex/middleware/service package supplies business-logic helpers for the
Service step — field transforms, derived values, and owner-scoping.
Side-effect helpers (events, webhooks, email) live in the separate
maniflex/events package, not here — see
Side effects below.
Field transforms
HashField
Replaces a plaintext field with its hash before the DB step, using a Hasher
you supply. The bcrypt hasher lives in the satellite package
maniflex/middleware/service/bcrypt (kept separate so the core has no bcrypt
dependency):
import (
"github.com/xaleel/maniflex/middleware/service"
svcbcrypt "github.com/xaleel/maniflex/middleware/service/bcrypt"
)
server.Pipeline.Service.Register(
service.HashField("password", svcbcrypt.Hasher()),
maniflex.ForModel("User"),
)
svcbcrypt.Hasher() takes an optional cost (svcbcrypt.Hasher(12)); the default
is suitable for production. A Hasher is just func(plaintext string) (string, error), so you can supply argon2 or any other implementation.
SlugifyField
Derives a slug field from a source field on create:
server.Pipeline.Service.Register(
service.SlugifyField("title", "slug"),
maniflex.ForModel("Post"), maniflex.ForOperation(maniflex.OpCreate),
)
Punctuation is stripped, spaces become hyphens, and the result is lowercased.
SetField
Sets a field on every create or update based on context — typically pulling
identity from ctx.Auth:
server.Pipeline.Service.Register(
service.SetField("user_id", func(ctx *maniflex.ServerContext) any {
return ctx.Auth.UserID
}),
maniflex.ForOperation(maniflex.OpCreate),
)
StripField
Removes a field from the request body (and the typed record) before the DB step. Useful for input-only confirmation fields that should never reach the database:
server.Pipeline.Service.Register(service.StripField("password_confirm"))
TimestampWhen
Sets a timestamp column when another field transitions to a specific value —
for example, recording published_at the moment status becomes
"published":
server.Pipeline.Service.Register(
service.TimestampWhen("published_at", "status", "published"),
maniflex.ForModel("Post"),
)
Timestamp
Unconditionally sets a timestamp column to the current time on every write it
runs for — use ForOperation to scope it (e.g. a last_seen_at touched on
update):
server.Pipeline.Service.Register(
service.Timestamp("last_seen_at"),
maniflex.ForModel("User"), maniflex.ForOperation(maniflex.OpUpdate),
)
CopyField
Copies one field’s value into another before the DB step — for denormalising a value the client shouldn’t set directly:
server.Pipeline.Service.Register(
service.CopyField("email", "billing_email"),
maniflex.ForModel("Account"), maniflex.ForOperation(maniflex.OpCreate),
)
Authorisation
OwnerScope
Forces a user-id field to the authenticated caller on create. It is exactly
SetField("user_id", ctx.Auth.UserID) — an unconditional overwrite, so any value
the client sent for that field is replaced (not rejected):
server.Pipeline.Service.Register(
service.OwnerScope("user_id"),
maniflex.ForOperation(maniflex.OpCreate),
)
Side effects
Side effects — events, webhooks, email — are not in this package. They live
in maniflex/events, and only events.Emit
is pipeline middleware; events.Webhook and events.SendEmail are event-bus
subscribers, not middleware, so they are wired with bus.Subscribe, not
Pipeline.Register.
events.Emit publishes a domain event on the DB step at maniflex.After,
after the write succeeds:
import "github.com/xaleel/maniflex/events"
server.Pipeline.DB.Register(
events.Emit(bus),
maniflex.ForOperation(maniflex.OpCreate, maniflex.OpUpdate, maniflex.OpDelete),
maniflex.AtPosition(maniflex.After),
)
Webhooks and email then react to those events as subscribers:
bus.Subscribe(ctx, events.Subscription{
Patterns: []string{"order.*"},
Handler: events.Webhook(events.WebhookConfig{URL: "https://hooks.example.com/orders", Secret: "whsec_…"}),
})
bus is any events.Bus — events/inproc for a single binary, or the Kafka,
NATS, RabbitMQ, and Redis adapters. See
Events & Background Jobs for the full API,
including the transactional outbox that makes events.Emit commit atomically
with the write (without it, a rolled-back transaction leaves an already-sent
side effect — outbound emails do not unsend themselves).