Documentation
¶
Index ¶
- Constants
- func Allowed(auth Authorizer, userID string, r Resource, a Action) bool
- func AnyGrant(grants []Grant, r Resource, a Action) bool
- func IsNil(v any) bool
- func IsZeroPtr(ptr any, ft FieldType) bool
- func ReadStringPtr(ptr any) (string, bool)
- func ReadValues(schema []Field, ptrs []any) []any
- func ValidateFields(action byte, f Fielder) error
- type Access
- type Action
- type ArrayReader
- type ArrayWriter
- type Authorizer
- type Decodable
- type Definition
- type Encodable
- type Field
- type FieldDB
- type FieldExt
- type FieldReader
- type FieldType
- type FieldWriter
- type Fielder
- type FielderSlice
- type Fields
- type Grant
- type IDGenerator
- type Kind
- type Model
- type ModelSlice
- type ModuleNaming
- type Permitted
- type RawJSON
- type RefKind
- type Resource
- type RoleCode
- type SafeFields
- type Validator
Constants ¶
const ( ActionCreate byte = 'c' ActionRead byte = 'r' ActionUpdate byte = 'u' ActionDelete byte = 'd' )
CRUD action bytes — the single source of truth for the ecosystem-wide action convention used by Validator, ValidateFields, and downstream libraries (crudp HTTP mapping, mcp Tool.Action / RBAC, form).
Deliberately plain byte constants (not a named type) so every existing `action byte` signature accepts them unchanged.
Not to be confused with orm.Action (an int enum describing query execution plans) — that is a different, orm-internal concept.
const AllActions = Create | Read | Update | Delete
AllActions is every verb. This is what "full access" means for the action dimension: a value, not a magic "*" that each implementation parses its own way.
Variables ¶
This section is empty.
Functions ¶
func Allowed ¶ added in v0.0.9
func Allowed(auth Authorizer, userID string, r Resource, a Action) bool
Allowed is the safe way to consult an Authorizer: a missing one denies, instead of panicking or — far worse — letting the call through.
func AnyGrant ¶ added in v0.0.9
AnyGrant reports whether any grant covers the pair. An empty policy denies.
func IsNil ¶ added in v0.0.2
IsNil comprueba de forma segura y portable si un valor es nil o si es un puntero nil dentro de una interfaz Encodable o Decodable.
func ReadStringPtr ¶ added in v0.0.2
ReadStringPtr reads a string from a typed pointer. Returns the string value and true if the pointer is *string, or ("", false) otherwise.
func ReadValues ¶ added in v0.0.2
ReadValues reads field values through Pointers by dereferencing based on Storage type. Used by consumers that need []any (e.g., orm for SQL args). Hot-path consumers (json, form) should read through Pointers directly to avoid boxing.
func ValidateFields ¶ added in v0.0.2
ValidateFields validates all fields of a Fielder based on the action. For each FieldText field, reads the string value and calls Field.Validate. For non-text fields with NotNull, checks against zero value.
Common actions are ActionCreate, ActionUpdate, and ActionDelete.
This is the single validation entry point — ormc-generated Validate() methods call this function first.
Types ¶
type Access ¶ added in v0.0.12
type Access uint8
Access declares who may reach something — a route, a tool, anything an enforcer guards.
It lives here, not in router or mcp, because BOTH already express these same three states and would otherwise each invent their own vocabulary for one idea. router encoded them implicitly, as combinations of (Public bool, Resource ""), which made an illegal state writable: a route could be marked Public AND carry a Requires, and the enforcer silently dropped the permission check. One declared value cannot contradict itself.
const ( // AccessGuarded (THE ZERO VALUE) requires an identity AND a permission on its Resource. // // The default is the strictest state on purpose: something that declares nothing must be // unreachable, not quietly open to anyone who happens to be logged in. An enforcer must // REJECT a guarded thing with no Resource at registration — loudly, at startup — because // authorizing against an empty resource denies every call while looking protected. AccessGuarded Access = iota // AccessAuthenticated requires an identity but checks no Resource. It is for operations on // the CALLER themselves ("who am I?"), where authentication already IS the check. // // Without this state, a `me` tool had to invent a resource ("profile") the app never // declared — a hole in the contract turning into policy inside a library. AccessAuthenticated // AccessPublic is reachable with no identity at all: static assets, a login page. // The rarest case, and the one that must be written on purpose. AccessPublic )
func (Access) String ¶ added in v0.0.13
String renders the access level as a word.
It exists because the NUMBER is actively misleading wherever a human or an agent reads it: the zero value is AccessGuarded, so the most protected route serializes as `0` — which any reader takes for "nothing declared", i.e. the opposite of the truth. A routes endpoint or a log line that reports the posture of a server must not invert it.
type Action ¶ added in v0.0.9
type Action uint8
Action is what may be done to a Resource. It is a CLOSED set — the four persistence verbs — and a SET of them: the type is a bit mask, so one Grant can carry several.
Closed on purpose. Resources are open because the app's language lives there ("service_catalog", "invoices"); actions are not, because persistence has exactly these four verbs and every tool in the ecosystem already declares one of them. Leaving the verb open bought nothing and cost a whole class of bugs: with a free-form string, a typo ("raed") compiles and shows up as a denial nobody can explain.
A domain verb like "approve" or "export" is NOT a fifth action: it is another resource ("orders:approve"), acted upon with these same four. Keep the app's vocabulary in the dimension that is open.
The zero value is no action at all, so it grants nothing — closed by default.
func ParseAction ¶ added in v0.0.11
ParseAction reads back what String wrote: "ru" -> Read|Update. Order does not matter.
An unknown letter is an error, never a silent zero: a permission row that says "raed" must fail loudly, not quietly grant nothing (or, worse, be re-saved as no permission at all). An empty string parses to the empty set, which grants nothing — that is not an error, it is the closed default.
func (Action) Has ¶ added in v0.0.10
Has reports whether the set contains every action in want. An empty want is not a licence: a zero Action grants nothing.
func (Action) String ¶ added in v0.0.11
String renders the set as the CRUD letters: Read -> "r", Read|Update -> "ru", AllActions -> "crud". The empty set is "".
The type is a bit mask because only a numeric type CLOSES the verb set: with a string type, `Requires("orders", "write")` still compiles — an invented verb nothing enforces, which is the bug this vocabulary exists to kill. But a raw 5 in a database column or a log line is unreadable, so the STORED and LOGGED form is the letters, not the bits.
Representation and storage are two different questions. This is the one place they meet.
type ArrayReader ¶ added in v0.0.2
type ArrayReader interface {
Len() int
String(i int) string
Int(i int) int64
Float(i int) float64
Bool(i int) bool
Bytes(i int) []byte
Object(i int, into Decodable) bool
}
ArrayReader recorre un array tipado.
type ArrayWriter ¶ added in v0.0.2
type ArrayWriter interface {
String(val string)
Int(val int64)
Float(val float64)
Bool(val bool)
Bytes(val []byte)
Object(val Encodable)
// Close finaliza el array.
Close()
}
ArrayWriter empuja elementos tipados de un array (sin []any).
type Authorizer ¶ added in v0.0.9
Authorizer answers whether an identity may perform an action on a resource.
It is the single seam between the layer that ENFORCES access (a router, an MCP server) and the one that KNOWS it (an auth module). Enforcers depend on this function type, never on a concrete implementation.
A nil Authorizer denies: the absence of an answer is not permission.
type Decodable ¶ added in v0.0.2
type Decodable interface {
DecodeFields(r FieldReader)
IsNil() bool
}
Decodable: un valor que sabe leer SUS campos (lo genera ormc).
type Definition ¶ added in v0.0.3
type Definition struct {
Name string // model identity: table name, ModelName(), route key
Fields Fields // ordered schema; Kinds come from model (e.g. model.Text())
}
Definition is the hand-written, fully-typed source of truth for a model. From a Definition, the ormc generator produces the concrete Go struct and its zero-reflection plumbing (Schema/Pointers/EncodeFields/DecodeFields/List).
This inverts the previous flow (struct + string tags → generated schema): the schema literal is now authored by hand and everything else is derived.
type Encodable ¶ added in v0.0.2
type Encodable interface {
EncodeFields(w FieldWriter)
IsNil() bool
}
Encodable: un valor que sabe escribir SUS campos (lo genera ormc).
type Field ¶ added in v0.0.2
type Field struct {
Name string
Type Kind
NotNull bool
OmitEmpty bool // omit from JSON when zero value
DB *FieldDB // nil for formonly/transport structs
// Ref is the Definition of the table this column references (scalar foreign key).
// e.g. a "staff_id int64" field pointing to StaffModel. It drives DDL FK constraint
// generation (FieldExt/SchemaExt) — see FieldDB.RefColumn/OnDelete.
// It does NOT change the field's Go type, which stays the plain scalar mapping from Type.
//
// Composition (FieldStruct/FieldStructSlice) no longer uses this slot; the nested
// Definition is now a mandatory parameter of the Kind constructor (model.Struct(ref)).
// Setting Field.Ref on a composition field is a contradiction and results in a
// generation error in ormc.
//
// A *Definition with a bidirectional Ref to another package-level Definition in the same
// package cannot be expressed as a single literal (Go rejects it: "initialization cycle") —
// this is a Go language limitation, not a model design gap. Two-phase assignment via init()
// is a workaround but is out of scope for ormc to auto-detect; report it if ever needed.
Ref *Definition
Exclude bool // field exists on the generated struct but is NOT part of
// Pointers()/EncodeFields()/DecodeFields() — no persistence, no wire codec.
// Use for hand-managed data the struct must carry but ormc must not touch
// (e.g. a password hash set via a side channel, never scanned/serialized).
Permitted // embedded: validation rules (characters, min/max)
}
Field describes a single field in a struct's schema. It provides type metadata, constraint flags, and validation rules used by database (orm), transport (json), UI (form), and validation layers.
Validation rules are provided by its Type (Kind) and embedded via Permitted. Field.Validate(value) checks both. A nil Type (Kind) is an error — the ecosystem is fail-closed by design.
Deterministic Field.Type.Storage() → Go type mapping:
| Storage | Go Type | |---|---| | FieldText, FieldRaw | string | | FieldInt | int64 | | FieldFloat | float64 | | FieldBool | bool | | FieldBlob | []byte | | FieldIntSlice | []int | | FieldStruct | type of the kind's ref — Struct(ref) | | FieldStructSlice | [] of the kind's ref — StructSlice(ref) |
func (Field) Validate ¶ added in v0.0.2
Validate checks a string value against this field's constraints. Checks NotNull first, then Kind, then Permitted (length and chars).
Security note: standard Kinds (Text, Int, Float, Bool) provide an input-boundary floor (A03 Injection/XSS) by using whitelists that exclude HTML-dangerous characters. ValidateFields provides implicit fail-closed protection for form-submitted data. Data from external sources (DB reads, API responses) bypasses this check and must be encoded at the output layer.
The base Text kind's charset is a DEFAULT floor, not a mandate: a field that declares its own positive whitelist (Letters/Numbers/Extra/...) REPLACES it — the author's explicit charset governs (e.g. a CSS-selector field permitting '#'). The author then owns the XSS exposure of any dangerous character it whitelists (encode at the output layer). Semantic kinds (email, ...) and non-text kinds (Int, Float, Bool) always validate.
type FieldDB ¶ added in v0.0.2
type FieldDB struct {
PK bool
Unique bool
AutoInc bool
// RefColumn/OnDelete apply only when the owning Field is a scalar foreign key
// (Field.Ref set — composition kinds carry their ref in the constructor and
// never set Field.Ref; see Field.Ref doc).
// Both are optional: RefColumn empty = auto-detect the PK of Ref's table;
// OnDelete empty = generator default (e.g. CASCADE).
RefColumn string
OnDelete string
}
FieldDB contains database-specific metadata. Extracted from Field to keep transport/UI structs lean.
type FieldExt ¶ added in v0.0.16
type FieldExt struct {
Field
Ref string // FK: target table name. Empty = no FK.
RefColumn string // FK: target column. Empty = auto-detect PK of Ref table.
OnDelete string // Override ON DELETE action. Empty = CASCADE (default for all FKs).
}
FieldExt extends Field with foreign-key metadata for adapters that emit FK constraints (SQL compilers, DDL exporters). Moved here from tinywasm/ddlc: it describes data the model declares (via SchemaExt()), not a generation
type FieldReader ¶ added in v0.0.2
type FieldReader interface {
String(name string) (string, bool)
Int(name string) (int64, bool)
Float(name string) (float64, bool)
Bool(name string) (bool, bool)
Bytes(name string) ([]byte, bool)
Object(name string, into Decodable) bool
Array(name string) (ArrayReader, bool)
// Raw devuelve el valor crudo asociado al nombre.
Raw(name string) (string, bool)
}
FieldReader entrega los campos por nombre, TIPADOS. El bool indica presencia. Lee por nombre directo (NO construye un map).
type FieldType ¶ added in v0.0.2
type FieldType int
FieldType represents the abstract storage type of a struct field.
const ( FieldText FieldType = iota // Any string FieldInt // Any integer FieldFloat // Any float FieldBool // Boolean FieldBlob // Binary data ([]byte) FieldStruct // Nested struct (implements Fielder) FieldIntSlice // []int FieldStructSlice // []Fielder FieldRaw // Pre-serialized JSON — emitted inline, no quoting )
type FieldWriter ¶ added in v0.0.2
type FieldWriter interface {
String(name, val string)
Int(name string, val int64)
Float(name string, val float64)
Bool(name string, val bool)
Bytes(name string, val []byte)
Null(name string)
// Raw emite el valor directamente sin escapar.
Raw(name, val string)
// Anidado: objeto hijo que también es Encodable.
Object(name string, val Encodable)
// Arrays tipados sin []any: retorna ArrayWriter directo para evitar closures.
Array(name string, n int) ArrayWriter
}
FieldWriter recibe los campos de un valor por llamadas TIPADAS. Implementaciones: jsvalue (escribe js.Value), json (escribe bytes), etc. Reglas: cero `any`, cero `map`, cero asignación en el heap Go (el writer reusa su buffer).
type Fielder ¶ added in v0.0.2
Fielder describes any type that can expose its schema and writable pointers. It is the shared contract between orm (database), form (UI), and json layers, replacing runtime reflection.
Implementations are generated by code generators — never written by hand.
Contract:
- Schema() and Pointers() MUST return slices of the same length.
- The i-th element in each slice corresponds to the same struct field.
- Pointers() returns pointers to fields for reading (dereference) and writing (scan/sync).
type FielderSlice ¶ added in v0.0.2
FielderSlice is implemented by generated code to allow iteration over a slice of structs without reflection.
type Fields ¶ added in v0.0.3
type Fields = []Field
Fields is the ordered list of field definitions of a model. Alias (not a named type) so a []Field literal is assignable without conversion.
type Grant ¶ added in v0.0.9
Grant is one line of a policy: what a role may do to a resource. Actions is a SET, so "may read and update the catalog" is one Grant, not two:
Grant{Resource: ResourceCatalog, Actions: Read | Update}
The zero value grants nothing — closed by default, like everything here.
type IDGenerator ¶ added in v0.0.15
type IDGenerator interface {
NewID() string
}
IDGenerator mints a new primary-key value for a record about to be created. It is THE contract to name at any boundary that needs identity generation — a domain module creating a record, a composition root wiring one in. Consumers MUST NOT construct a concrete generator (e.g. unixid.NewUnixID) inside a reusable module: that hardcodes an implementation the module should not know about. Accept IDGenerator instead and let the composition root inject it.
Implementations: github.com/tinywasm/unixid (time-sortable, collision-safe under concurrent callers). A test double can be a func literal satisfying this single-method interface.
type Kind ¶ added in v0.0.7
type Kind interface {
Storage() FieldType // deterministic Go/DDL mapping
Name() string // semantic name: "text", "int", "email", ...
Validate(value string) error // ALWAYS present — fail-closed
}
Kind replaces the Field.Type enum slot + Field.Widget pair. Implementations are stateless templates, safe for concurrent reuse.
func Blob ¶ added in v0.0.7
func Blob() Kind
Blob returns the base Blob kind. No content validation (binary).
func Bool ¶ added in v0.0.7
func Bool() Kind
Bool returns the base Bool kind. Accepts "true", "false", "1", "0", "".
func Float ¶ added in v0.0.7
func Float() Kind
Float returns the base Float kind. Accepts digits, at most one '.', and an optional leading minus sign.
func Int ¶ added in v0.0.7
func Int() Kind
Int returns the base Int kind. Accepts digits and an optional leading minus sign.
func IntSlice ¶ added in v0.0.7
func IntSlice() Kind
IntSlice returns the base IntSlice kind. No string validation.
func Raw ¶ added in v0.0.7
func Raw() Kind
Raw returns the base Raw kind. No content validation (pre-serialized JSON).
func Struct ¶ added in v0.0.7
func Struct(ref *Definition) Kind
Struct returns the composition kind nesting the given Definition. The ref is REQUIRED: ormc derives the generated Go field type from it. No string validation (the nested Fielder validates itself, fail-closed).
func StructSlice ¶ added in v0.0.7
func StructSlice(ref *Definition) Kind
StructSlice returns the composition kind nesting a slice of the given Definition. Same contract as Struct.
type Model ¶
type Model interface {
Fielder // Schema() + Pointers() → form, orm
ModuleNaming // ModelName() → routing, RBAC resource
Encodable // EncodeFields() + IsNil() → to the wire
Decodable // DecodeFields() + IsNil() → from the wire
}
Model is the full contract that ormc generates for every domain record: it knows its schema and writable pointers (Fielder), its stable name (ModuleNaming), and how to travel over the wire in both directions (Encodable, Decodable).
It is THE type to name at any boundary that handles a domain record — a form that renders it, a Caller that ships it, an ORM that scans it. Consumers MUST NOT declare local intersections of the atoms below; if a boundary needs a combination that this package does not name, that is a defect HERE, not in the consumer.
Implementations are generated by ormc — never written by hand.
type ModelSlice ¶ added in v0.1.0
type ModelSlice interface {
FielderSlice
Decodable
}
ModelSlice is the wire-decodable counterpart of FielderSlice: a list result a Caller can decode a Call response into, row by row. It is THE type to name at any boundary that lists domain records over the wire (a Presenter's list operation, a Caller consumer) — do not declare a local intersection of FielderSlice+Decodable.
type ModuleNaming ¶ added in v0.0.2
type ModuleNaming interface {
ModelName() string
}
ModuleNaming is the minimal identity contract shared across the ecosystem: a stable model name used as route key, nav hash and DOM id. Consumers that need only identity (routing, layout shells) embed this; data models embed the richer Model.
type Permitted ¶ added in v0.0.2
type Permitted struct {
Letters bool // a-z, A-Z, ñ, Ñ
Tilde bool // á, é, í, ó, ú (and uppercase) — uses aL/aU from mapping.go
Numbers bool // 0-9
Spaces bool // ' '
BreakLine bool // '\n'
Tab bool // '\t'
Extra []rune // additional allowed characters (e.g., '@', '.', '-')
NotAllowed []string // forbidden substrings
Minimum int // min length (0 = no limit)
Maximum int // max length (0 = no limit)
StartWith *Permitted // rules for first character (nil = same as main rules)
}
Permitted validates strings character-by-character against a configurable whitelist.
Zero value = nothing permitted (strictest). Enable flags to allow character classes. Moved from form/input to fmt for cross-layer reuse.
Rule: if only Minimum/Maximum are configured (no character flags), Validate only checks length — it never rejects characters. To restrict characters, enable at least one flag (Letters, Numbers, etc.) or add entries to Extra/NotAllowed.
Security contract: the whitelist is positive — only explicitly enabled characters pass. HTML-dangerous characters (<, >, &, ", ') are not included in any standard widget's whitelist. Data validated through standard widgets is therefore safe for HTML output without additional escaping.
If a custom widget adds dangerous chars to Extra, it must document the XSS risk and the caller is responsible for output encoding (e.g., fmt.Convert(v).EscapeHTML()).
func (Permitted) NoHTML ¶ added in v0.0.2
NoHTML adds HTML-dangerous characters to NotAllowed as an explicit safety layer. Returns a modified copy. Use when Extra contains characters that could appear in HTML injection attempts and the widget cannot be restricted further.
Example:
t.Permitted = t.Permitted.NoHTML()
type RawJSON ¶ added in v0.0.2
type RawJSON = string
RawJSON is a type alias for string that signals pre-serialized JSON content. Like encoding/json.RawMessage, it tells the encoder to emit the value inline without quoting or re-serializing, avoiding linter warnings about the `json:",raw"` tag.
Usage:
type Result struct {
Content RawJSON // no tag needed; type itself conveys intent
Error RawJSON `json:",omitempty"`
}
The encoder detects RawJSON at code-generation time (ormc), marking the field as FieldRaw in the Schema(), and tinywasm/json handles it without re-serializing.
type RefKind ¶ added in v0.0.7
type RefKind interface {
Kind
Ref() *Definition
}
RefKind is implemented by composition kinds (Struct, StructSlice). Consumers (ormc, orm relations) read the nested Definition from here.
type Resource ¶ added in v0.0.9
type Resource string
Resource is the thing being protected: "service_catalog", "invoices". The consumer declares its own — a library that enforces access must never invent one.
const Wildcard Resource = "*"
Wildcard matches every Resource.
The wildcard is MECHANISM (how a permission is matched), never POLICY: a library may honour it when checking, but must never grant it on its own. Handing out full access is a decision only the consumer can make, and it must cost an explicit, greppable line.
There is no wildcard for actions: that is simply AllActions. A closed set does not need an escape hatch — which is exactly why it is closed.
func ResourceOf ¶ added in v0.0.9
func ResourceOf(m ModuleNaming) Resource
ResourceOf is the resource that protects a module: its own identity.
This is the whole point of putting the vocabulary next to ModuleNaming. The convention "a module's ID is its RBAC resource" used to be a line in a document that nothing enforced; here it is a function, so the two can no longer disagree.
type RoleCode ¶ added in v0.0.9
type RoleCode string
RoleCode is how a consumer names a role: "admin", "reception", "practitioner". The vocabulary belongs to the app. No library may hardcode one.
type SafeFields ¶ added in v0.0.2
SafeFields combines schema access with validation. Handlers that receive user input should accept SafeFields to enforce compile-time validation guarantees.