model

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 6, 2026 License: MIT Imports: 1 Imported by: 0

README

tinywasm/model

Core schema, validation, and codec types for tinywasm ecosystem. This package provides the foundational interfaces and types used across ORM, JSON serialization, form handling, and API layers.

Overview

model provides the foundational type definitions and contracts for the tinywasm ecosystem. It focuses on:

  • Schema Metadata - Field, Fielder, and FieldType for reflection-less struct introspection
  • Validation - Permitted and Validator for data integrity
  • Typed Serialization - Encodable, Decodable, FieldWriter, FieldReader for 0-alloc codec patterns

Key Features

  • 🏗️ Schema-first design - Describe struct fields without reflection
  • Validation by contract - Character whitelists, length constraints, custom validators
  • 🔄 Typed codecs - Encodable/Decodable for 0-alloc serialization
  • 🧵 Concurrency safe - Thread-safe schema metadata and codecs
  • 📦 Zero dependencies - No reflection, no maps, no any boxing in codec paths
  • WebAssembly optimized - Small binary footprint for WASM targets

Installation

go get github.com/tinywasm/model

Quick Start

Defining a Schema

Typically generated by ormc, but here's the manual pattern:

import "github.com/tinywasm/model"

type User struct {
    ID   string
    Name string
    Age  int
}

func (u *User) Schema() []model.Field {
    return []model.Field{
        {
            Name: "id",
            Type: model.FieldText,
            DB:   &model.FieldDB{PK: true},
        },
        {
            Name:      "name",
            Type:      model.FieldText,
            NotNull:   true,
            Permitted: model.Permitted{Letters: true, Spaces: true},
        },
        {
            Name: "age",
            Type: model.FieldInt,
        },
    }
}

func (u *User) Pointers() []any {
    return []any{&u.ID, &u.Name, &u.Age}
}

func (u *User) Validate(action byte) error {
    return model.ValidateFields(action, u)
}
Using the Codec

Generated EncodeFields and DecodeFields methods follow the typed codec pattern:

func (u *User) EncodeFields(w model.FieldWriter) {
    w.String("id", u.ID)
    if u.Name != "" {
        w.String("name", u.Name)
    }
    w.Int("age", int64(u.Age))
}

func (u *User) DecodeFields(r model.FieldReader) {
    if id, ok := r.String("id"); ok {
        u.ID = id
    }
    if name, ok := r.String("name"); ok {
        u.Name = name
    }
    if age, ok := r.Int("age"); ok {
        u.Age = int(age)
    }
}

Documentation

Architecture

Separation of Concerns:

Concern Type Used By
DDL & Column Metadata Field.DB orm, sqlt, postgres
Data Validation Field + Permitted orm, form, json
UI Bindings Field.Widget form, ormc codegen
SQL Scanning Pointers() orm/qb (positional via database/sql)
Serialization Encodable/Decodable + codec json, jsvalue, transports

One schema (Field) serves all layers. Serialization uses the typed codec (Encodable/Decodable) for 0-alloc, map-free performance.


Contributing


License

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsNil added in v0.0.2

func IsNil(v any) bool

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 IsZeroPtr added in v0.0.2

func IsZeroPtr(ptr any, ft FieldType) bool

IsZeroPtr checks if a pointer points to a zero value.

func ReadStringPtr added in v0.0.2

func ReadStringPtr(ptr any) (string, bool)

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

func ReadValues(schema []Field, ptrs []any) []any

ReadValues reads field values through Pointers by dereferencing based on FieldType. 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

func ValidateFields(action byte, f Fielder) error

ValidateFields validates all fields of a Fielder based on the action ('c', 'u', 'd'). For each FieldText field, reads the string value and calls Field.Validate. For non-text fields with NotNull, checks against zero value.

This is the single validation entry point — ormc-generated Validate() methods call this function first.

Types

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 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 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      FieldType
	NotNull   bool
	OmitEmpty bool     // omit from JSON when zero value
	Widget    Widget   // ← NEW: set by ormc from `input:` tag. nil = no UI binding.
	DB        *FieldDB // nil for formonly/transport structs
	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 embedded via Permitted. When a field has validation configured, Field.Validate(value) checks the value against all rules. Fields without validation rules pass any value.

func (Field) IsAutoInc added in v0.0.2

func (f Field) IsAutoInc() bool

IsAutoInc returns true if the field is Auto-Increment.

func (Field) IsPK added in v0.0.2

func (f Field) IsPK() bool

IsPK returns true if the field is a Primary Key.

func (Field) IsUnique added in v0.0.2

func (f Field) IsUnique() bool

IsUnique returns true if the field has a Unique constraint.

func (Field) Validate added in v0.0.2

func (f Field) Validate(value string) error

Validate checks a string value against this field's constraints. Checks NotNull first, then length, then delegates to Widget and Permitted.

Security note: fields using standard widgets (Text, Textarea, Email) have whitelists that exclude HTML-dangerous characters. ValidateFields provides implicit XSS protection for form-submitted data. Data from external sources (DB reads, API responses) bypasses this check and must be encoded at the output layer.

type FieldDB added in v0.0.2

type FieldDB struct {
	PK      bool
	Unique  bool
	AutoInc bool
}

FieldDB contains database-specific metadata. Extracted from Field to keep transport/UI structs lean.

type FieldReader added in v0.0.2

type FieldReader interface {
	String(name string) (string, bool)
	Int(name string) (int64, bool)
	Uint(name string) (uint64, 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
)

func (FieldType) String added in v0.0.2

func (ft FieldType) String() string

type FieldWriter added in v0.0.2

type FieldWriter interface {
	String(name, val string)
	Int(name string, val int64)
	Uint(name string, val uint64)
	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

type Fielder interface {
	Schema() []Field
	Pointers() []any
}

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

type FielderSlice interface {
	Fielder
	Len() int
	At(i int) Fielder
	Append() Fielder
}

FielderSlice is implemented by generated code to allow iteration over a slice of structs without reflection.

type Model

type Model interface {
	Fielder
	ModuleNaming
}

Model describes a resource with a schema and a name. Standard interface used by DB, Form, and API layers.

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

func (p Permitted) NoHTML() Permitted

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()

func (Permitted) Validate added in v0.0.2

func (p Permitted) Validate(field, text string) error

Validate checks that text conforms to the permitted rules. Order: length → forbidden substrings → start-with → characters.

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 SafeFields added in v0.0.2

type SafeFields interface {
	Fielder
	Validator
}

SafeFields combines schema access with validation. Handlers that receive user input should accept SafeFields to enforce compile-time validation guarantees.

type Validator added in v0.0.2

type Validator interface {
	Validate(action byte) error
}

Validator is implemented by types that can self-validate. Generated by ormc: func (m *X) Validate(action byte) error { return model.ValidateFields(action, m) } Used by form, json.Decode, and orm pre-insert to enforce data integrity.

type Widget added in v0.0.2

type Widget interface {
	Type() string                       // Semantic input type (e.g., "email", "textarea")
	Validate(value string) error        // Semantic validation for this input type
	Clone(parentID, name string) Widget // Returns a positioned instance; pass ("","") for a bare template
}

Widget is the contract for a semantic input type. It is implemented by tinywasm/form/input types and custom project inputs. Stored in Field.Widget; set by ormc code generation from the `input:` struct tag.

Implementations must be stateless templates — Clone() returns a fresh copy for thread-safe use per request in the form layer.

RenderHTML is intentionally excluded: rendering is a concern of tinywasm/form, not of the base schema package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL