import "github.com/CaliLuke/go-typeql/v3/gotype" -- pkg.go.dev
Models are Go structs that map to TypeDB entities and relations. They use struct tags to define attribute names, annotations, and role players.
Embed gotype.BaseEntity and tag each exported field with typedb:"...":
type Person struct {
gotype.BaseEntity
Name string `typedb:"name,key"`
Email string `typedb:"email,unique"`
Age *int `typedb:"age"`
}BaseEntityprovides theEntitymarker interface plusGetIID()/SetIID()methods.- The TypeDB type name is derived from the Go struct name in kebab-case (
UserAccountbecomesuser-account). - Pointer fields are optional attributes. Non-pointer fields are required.
Embed gotype.BaseRelation. Role player fields use role:name tags; attribute fields use the same syntax as entities:
type Employment struct {
gotype.BaseRelation
Employee *Person `typedb:"role:employee"`
Employer *Company `typedb:"role:employer"`
StartDate *string `typedb:"start-date"`
}BaseRelationprovides theRelationmarker interface plusGetIID()/SetIID().- Role player fields must be pointers to registered entity types.
Tags follow the format typedb:"name[,option1][,option2]...":
| Tag | Example | Description |
|---|---|---|
| attribute name | typedb:"name" |
Maps field to a TypeDB attribute |
key |
typedb:"name,key" |
@key annotation (unique identifier) |
unique |
typedb:"email,unique" |
@unique annotation |
card=M..N |
typedb:"items,card=0..5" |
Cardinality constraint |
role:name |
typedb:"role:employee" |
Role player in a relation |
abstract |
typedb:"abstract" |
Marks the type as abstract |
type:name |
typedb:"type:custom_name" |
Overrides the TypeDB type name |
sub:name |
typedb:"sub:artifact" |
Declares an explicit supertype |
value:decimal |
typedb:"price,value:decimal" |
Stores the attribute as TypeDB decimal |
- |
typedb:"-" |
Skip this field |
Cardinality formats: 0..1, 1..5, 2.. (unbounded max), 0+ (shorthand for 0..).
Negative or inverted ranges (card=5..2) are rejected at registration.
value:decimal overrides the value type derived from the Go field (a float64
field would otherwise map to double). It is allowed on float64/float32
fields (idiomatic, but lossy for fractions a binary float cannot represent) and
on string fields (exact: digits round-trip verbatim, e.g. "0.1"), including
pointers and slices of those; any other field kind is rejected at registration.
Write paths emit dec-suffixed literals (12.5dec; integral values keep a
fraction: 3.0dec), and values read back arrive as decimal strings and are
parsed into the field type automatically.
Type-level options (abstract, type:, sub:) may be placed on the embedded base
field (e.g. gotype.BaseEntity `typedb:"sub:artifact"` ) or on a blank field
(_ byte). The declared supertype is emitted as entity child sub parent in the
generated schema and populates ModelInfo.Supertype / SubtypesOf.
Registration validates tags strictly and returns an error for: field-level options
without an attribute name (typedb:"key" alone), unsupported Go field types
(map/chan/complex/non-time structs), attribute names duplicated within a model or
registered elsewhere with a different value type, reserved-word or malformed role
names, conflicting type:/sub: declarations, and invalid cardinality ranges.
TypeDB 3.12 @doc annotations can be emitted from Go models.
Use typedb_doc for field ownership documentation:
type Person struct {
gotype.BaseEntity
Name string `typedb:"name,key" typedb_doc:"Primary display name."`
}Use SchemaDoc() string for type-level documentation:
func (Person) SchemaDoc() string {
return "A person record."
}Use SchemaMeta() map[string]string for type-level metadata:
func (Person) SchemaMeta() map[string]string {
return map[string]string{
"owner": "identity",
"ui": "person",
}
}Schema generation emits these as TypeQL @doc("...") and @meta("key", "value") annotations. Metadata keys are sorted for deterministic schema output.
There is no general typedb_meta field tag because repeatable key/value
metadata does not fit Go struct tags cleanly.
| Go Type | TypeDB Value Type |
|---|---|
string |
string |
bool |
boolean |
int, int8..int64 |
long |
uint, uint8..uint64 |
long |
float32, float64 |
double |
time.Time |
datetime |
All model types must be registered before use. Registration extracts metadata via reflection and stores it in a global registry. Registration rejects the 42 reserved TypeQL keywords, such as define, match, and entity. The check matches case.
// Register returns an error if the type is invalid
err := gotype.Register[Person]()
// MustRegister panics on error (convenient for init())
gotype.MustRegister[Person]()The registry is global and shared. In tests, call ClearRegistry() and re-register per test since other tests may clear it.
Lookup functions let you find registered types by TypeDB name, Go type, or Go struct name. SubtypesOf and ResolveType support polymorphic type hierarchies.
Hydration populates struct fields from map[string]any data returned by TypeDB queries:
// Populate an existing struct
err := gotype.Hydrate(&person, data)
// Create and populate in one step
person, err := gotype.HydrateNew[Person](data)
// Polymorphic: uses the "_type" field in data to pick the concrete type
instance, err := gotype.HydrateAny(data)Hydration handles nested role player structs recursively, with a depth limit of 10 (MaxHydrationDepth) to prevent infinite loops when the database graph contains cycles.
Convert between struct instances and map[string]any:
alice := &Person{Name: "Alice", Email: "alice@example.com"}
// Struct -> map
dict, err := gotype.ToDict(alice)
// {"name": "Alice", "email": "alice@example.com"}
// Map -> struct
person, err := gotype.FromDict[Person](dict)Generate TypeQL strings directly from struct instances:
alice := &Person{Name: "Alice", Email: "alice@example.com"}
insertQL, err := gotype.ToInsertQuery(alice)
// insert $e isa person, has name "Alice", has email "alice@example.com";
matchQL, err := gotype.ToMatchQuery(alice)
// match $e isa person, has name "Alice";ToMatchQuery uses key fields only. Both require the type to be registered.
For type hierarchies (using sub), the registry supports polymorphic operations:
// Get all registered subtypes of "artifact"
subtypes := gotype.SubtypesOf("artifact")
// Resolve a type label returned by TypeDB
info, ok := gotype.ResolveType("task")See GetByIIDPolymorphic and GetByIIDPolymorphicAny in the CRUD docs for fetching instances polymorphically.
- ModelInfo holds all extracted metadata for a registered type: Go type, kind (entity/relation), TypeDB name, fields, roles, key fields. You can look up fields by Go name or TypeDB attribute name.
- ModelStrategy is the internal strategy pattern (
entityStrategy/relationStrategy) that builds TypeQL strings for different type kinds. You don't interact with it directly. - Reserved words: Registration checks names against the 42 reserved TypeQL keywords (
TypeQLReservedWords). If a type, attribute, or role name is one of them,Registerreturns aReservedWordError. The check matches case, like the server:matchis reserved, andMatchis not. Other TypeQL words, such aslabelandcount, are valid names. - Identifier validation: Type names, attribute names, and role names are validated during registration. Valid identifiers start with a letter or underscore and contain only letters, digits, hyphens, or underscores. Invalid identifiers produce an
InvalidIdentifierError. UseValidateIdentifier(name, context)to check programmatically.