Documentation
¶
Overview ¶
Example ¶
package main
import (
"database/sql"
"database/sql/driver"
"encoding"
"fmt"
"strings"
"github.com/go-chi/typeid"
)
// Prefix definitions — in practice these live next to each domain entity.
type userPrefix struct{}
func (userPrefix) Prefix() string { return "user" }
type orgPrefix struct{}
func (orgPrefix) Prefix() string { return "org" }
// Type aliases give readable names.
type (
UserID = typeid.UUID[userPrefix]
OrgID = typeid.Int64[orgPrefix]
)
// Compile-time interface checks.
var (
_ fmt.Stringer = UserID{}
_ fmt.Stringer = OrgID{}
_ encoding.TextMarshaler = UserID{}
_ encoding.TextMarshaler = OrgID{}
_ encoding.TextUnmarshaler = (*UserID)(nil)
_ encoding.TextUnmarshaler = (*OrgID)(nil)
_ driver.Valuer = UserID{}
_ driver.Valuer = OrgID{}
_ sql.Scanner = (*UserID)(nil)
_ sql.Scanner = (*OrgID)(nil)
)
func main() {
orgID, err := typeid.NewInt64[orgPrefix]()
if err != nil {
panic(err)
}
userID, err := typeid.NewUUID[userPrefix]()
if err != nil {
panic(err)
}
fmt.Println(strings.HasPrefix(orgID.String(), "org_"))
fmt.Println(strings.HasPrefix(userID.String(), "user_"))
}
Output: true true
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrOnlyV7 = errors.New("typeid: only UUIDv7 is supported") ErrZeroUUID = errors.New("typeid: zero UUID") ErrNonPositiveInt = errors.New("typeid: non-positive Int64") ErrOverflowBase32 = errors.New("typeid: base32 overflow at pos 0") ErrOverflowInt64 = errors.New("typeid: value overflows int64") )
Functions ¶
This section is empty.
Types ¶
type Int64 ¶
type Int64[P Prefixer] struct { // contains filtered or unexported fields }
Int64 is a type-safe compact identifier. Maps to Postgres BIGINT.
Bit layout ¶
[48-bit unix ms timestamp][15-bit crypto/rand] = 63 bits, always positive
Timestamp range ¶
48-bit millisecond timestamp (same as UUIDv7) covers Unix epoch through year 10889. No action needed in our lifetimes.
Collision resistance ¶
15 random bits = 32,768 values per millisecond. Collision probability follows the birthday problem: ~R²/65,536,000 expected collisions per second for R total IDs/sec across all servers.
10 IDs/sec → ~1 collision per 7,500 days 100 IDs/sec → ~1 collision per 1.8 hours 1,000 IDs/sec → ~1 collision per 65 seconds
Protect with a UNIQUE constraint and retry on conflict. For high-throughput resources use UUID instead.
Ordering (k-sortable) ¶
IDs are k-sortable: the 48-bit timestamp in the high bits dominates sort order, so IDs sort by creation time at millisecond granularity. Two IDs generated in the exact same millisecond are not ordered relative to each other, but they cluster on the same B-tree leaf pages — no impact on Postgres insert locality. Clock skew between servers may produce out-of-order IDs within that skew window.
Example (Json) ¶
type Org struct {
ID OrgID `json:"id"`
Name string `json:"name"`
}
id, _ := typeid.NewInt64[orgPrefix]()
original := Org{ID: id, Name: "Polygon"}
data, _ := json.Marshal(original)
var decoded Org
_ = json.Unmarshal(data, &decoded)
fmt.Println(original.ID == decoded.ID)
fmt.Println(strings.Contains(string(data), `"id":"org_`))
Output: true true
func Int64From ¶
Example ¶
id, _ := typeid.NewInt64[orgPrefix]()
raw := id.Int64()
reconstructed, err := typeid.Int64From[orgPrefix](raw)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(id == reconstructed)
Output: true
Example (RejectsNonPositive) ¶
_, err := typeid.Int64From[orgPrefix](-1) fmt.Println(err) _, err = typeid.Int64From[orgPrefix](0) fmt.Println(err)
Output: typeid: non-positive Int64 typeid: non-positive Int64
func NewInt64 ¶
Example ¶
id, err := typeid.NewInt64[orgPrefix]()
if err != nil {
fmt.Println("error:", err)
return
}
s := id.String()
prefix, suffix, _ := strings.Cut(s, "_")
fmt.Println(prefix)
fmt.Println(len(suffix))
fmt.Println(id.Int64() > 0)
Output: org 13 true
func ParseInt64 ¶
Example ¶
original, _ := typeid.NewInt64[orgPrefix]()
parsed, err := typeid.ParseInt64[orgPrefix](original.String())
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(original == parsed)
Output: true
Example (WrongPrefix) ¶
_, err := typeid.ParseInt64[orgPrefix]("foo_0h455vb4pex5v")
fmt.Println(err)
Output: typeid: prefix mismatch: expected "org", got "foo"
func (Int64[P]) IsZero ¶
Example ¶
var id OrgID fmt.Println(id.IsZero()) id, _ = typeid.NewInt64[orgPrefix]() fmt.Println(id.IsZero())
Output: true false
func (Int64[P]) MarshalText ¶
func (*Int64[P]) Scan ¶
Example ¶
id, _ := typeid.NewInt64[orgPrefix]() raw := id.Int64() var scanned OrgID err := scanned.Scan(raw) fmt.Println(err == nil) fmt.Println(id == scanned)
Output: true true
func (*Int64[P]) UnmarshalText ¶
type Prefixer ¶
type Prefixer interface {
Prefix() string
}
Prefixer is the constraint for type-safe ID prefixes.
type UUID ¶
type UUID[P Prefixer] struct { // contains filtered or unexported fields }
UUID is a type-safe UUIDv7 identifier with a compile-time prefix. Maps to Postgres uuid.
Example (Json) ¶
type User struct {
ID UserID `json:"id"`
Name string `json:"name"`
}
id, _ := typeid.NewUUID[userPrefix]()
original := User{ID: id, Name: "Alice"}
data, _ := json.Marshal(original)
var decoded User
_ = json.Unmarshal(data, &decoded)
fmt.Println(original.ID == decoded.ID)
fmt.Println(strings.Contains(string(data), `"id":"user_`))
Output: true true
func NewUUID ¶
Example ¶
id, err := typeid.NewUUID[userPrefix]()
if err != nil {
fmt.Println("error:", err)
return
}
s := id.String()
prefix, suffix, _ := strings.Cut(s, "_")
fmt.Println(prefix)
fmt.Println(len(suffix))
fmt.Println(int(id.UUID().Version()))
Output: user 26 7
func ParseUUID ¶
Example ¶
original, _ := typeid.NewUUID[userPrefix]()
parsed, err := typeid.ParseUUID[userPrefix](original.String())
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(original == parsed)
Output: true
Example (WrongPrefix) ¶
_, err := typeid.ParseUUID[userPrefix]("team_01h455vb4pex5vsknk084sn02q")
fmt.Println(err)
Output: typeid: prefix mismatch: expected "user", got "team"
func UUIDFrom ¶
Example ¶
raw := uuid.Must(uuid.NewV7())
id, err := typeid.UUIDFrom[userPrefix](raw)
if err != nil {
fmt.Println("error:", err)
return
}
fmt.Println(id.UUID() == raw)
Output: true
Example (RejectsV4) ¶
v4 := uuid.New() _, err := typeid.UUIDFrom[userPrefix](v4) fmt.Println(err)
Output: typeid: only UUIDv7 is supported
func (UUID[P]) IsZero ¶
Example ¶
var id UserID fmt.Println(id.IsZero()) id, _ = typeid.NewUUID[userPrefix]() fmt.Println(id.IsZero())
Output: true false
func (UUID[P]) MarshalText ¶
func (*UUID[P]) Scan ¶
Example ¶
id, _ := typeid.NewUUID[userPrefix]() raw := id.UUID().String() var scanned UserID err := scanned.Scan(raw) fmt.Println(err == nil) fmt.Println(id == scanned)
Output: true true