typeid

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Dec 17, 2025 License: Apache-2.0 Imports: 8 Imported by: 0

README

TypeID

Stars Go Reference CI Status License Contributor Covenant

TypeIDs are a draft standard for type-safe, globally unique identifiers based on the UUIDv7 standard. Their properties, particularly k-sortability, make them suitable primary identifiers for classic database systems like PostgreSQL. However, k-sortability may not always be desirable. For instance, you might require an identifier with high randomness entropy for security reasons. Additionally, in distributed database systems like CockroachDB, having a k-sortable primary key can lead to hotspots and performance issues.

While this package draws inspiration from the original typeid-go package (github.com/jetify-com/typeid-go), it provides multiple ID types:

  • typeid.Sortable is based on UUIDv7^UUIDv7 and is k-sortable. Its implementation adheres to the draft standard. The suffix part is encoded in lowercase crockford base32.
  • typeid.Random is also based on UUIDv4^UUIDv4 and is completely random. Unlike typeid.Sortable, the suffix part is encoded in uppercase crockford base32.

Please refer to the respective type documentation for more details.

Install

go get github.com/sumup/typeid

Usage

To create a new ID type, define a prefix type that implements the typeid.Prefix interface. Then, define a TypeAlias for your ID type to typeid.Random or typeid.Sortable with your prefix type as generic argument.

Example:

import "github.com/sumup/typeid"

type UserPrefix struct{}

func (UserPrefix) Prefix() string {
    return "user"
}

type UserID = typeid.Sortable[UserPrefix]

userID, err := typeid.New[UserID]()
if err != nil {
    fmt.Println("create user id:", err)
}

fmt.Println(userID) // --> user_01hf98sp99fs2b4qf2jm11hse4

Database Support

ID types in this package can be used with database/sql and github.com/jackc/pgx.

When using the standard library SQL, IDs will be stored as their string representation and can be scanned and valued accordingly. When using pgx, both TEXT and UUID columns can be used directly. However, note that the type information is lost when using UUID columns, unless you take additional steps at the database layer. Be mindful of your identifier semantics, especially in complex JOIN queries.

If using pgx with PostgreSQL, you can generate UUIDv4 (for usage with typeid.Random) as the default value for your primary key:

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT GEN_RANDOM_UUID(),
    ...
);

For k-sortable TypeIDs (typeid.Sortable, using UUIDv7) you will have to generate the UUID in your application unless you are running PostgreSQL 18 or new where both are supported:

-- With random TypeID
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT uuidv4(),
    ...
);

-- With sortable TypeID
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT uuidv7(),
    ...
);

Using with sqlc

TypeIDs work seamlessly with sqlc by using column overrides in your sqlc.yaml configuration:

version: "2"
sql:
  - schema: "schema.sql"
    queries: "queries"
    engine: postgresql
    gen:
      go:
        package: postgres
        out: postgres
        sql_package: pgx/v5
        overrides:
          - column: users.id
            go_type:
              import: github.com/yourorg/yourproject/internal/domain
              type: "UserID"

With this configuration, sqlc will generate Go code that uses your TypeID type directly:

package domain

import "github.com/sumup/typeid"

type UserPrefix struct{}

func (UserPrefix) Prefix() string {
    return "user"
}

type UserID = typeid.Sortable[UserPrefix]

type User struct {
    ID          UserID
    Name        string
    // ... other fields
}

You can then use your TypeID types directly in your queries:

-- name: CreateUser :one
INSERT INTO users (
    id,
    name
) VALUES (
    @id,
    @name
) RETURNING *;

-- name: GetUser :one
SELECT * FROM users
WHERE id = @id;

And call them from Go:

userID := typeid.Must(typeid.New[domain.UserID]())
user, err := queries.CreateUser(ctx, postgres.CreateUserParams{
    ID:          userID,
    Name:        "Karl",
})

Using with oapi-codegen

TypeIDs can be used with oapi-codegen to generate type-safe API clients and servers. Use the x-go-type and x-go-type-import extensions in your OpenAPI specification:

paths:
  /users/{user_id}:
    parameters:
      - in: path
        name: user_id
        description: The ID of the uesr to retrieve.
        required: true
        schema:
          type: string
          example: user_01hf98sp99fs2b4qf2jm11hse4
          x-go-type: "domain.UserID"
          x-go-type-import:
            path: github.com/yourorg/yourproject/internal/domain

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier of the user.
          example: user_01hf98sp99fs2b4qf2jm11hse4
          x-go-type: "domain.UserID"
          x-go-type-import:
            path: github.com/yourorg/yourproject/internal/domain
        name:
          type: string

The generated code will use your TypeID types:

type User struct {
    Id   domain.UserID `json:"id"`
    Name string        `json:"name"`
}

TypeIDs implement encoding.TextMarshaler and encoding.TextUnmarshaler, so they work with JSON encoding/decoding in generated API code without any additional configuration.

Maintainers

Based on the go implementation of typeid found at: https://github.com/jetify-com/typeid-go by Jetify. Modifications made available under the same license as the original.

Documentation

Overview

TypeIDs are a draft standard for *type-safe, globally unique identifiers* based on the upcoming UUIDv7 standard. Their properties, particularly k-sortability, make them suitable primary identifiers for classic database systems like PostgreSQL. However, k-sortability may not always be desirable. For instance, you might require an identifier with high randomness entropy for security reasons. Additionally, in distributed database systems like CockroachDB, having a k-sortable primary key can lead to hotspots and performance issues.

While this package draws inspiration from the original typeid Go package (go.jetpack.io/typeid), it provides multiple ID types:

  • typeid.Sortable is based on UUIDv7 and is k-sortable. Its implementation adheres to the draft standard. The suffix part is encoded in **lowercase** crockford base32.
  • typeid.Random is also based on UUIDv4 and is completely random. Unlike `typeid.Sortable`, the suffix part is encoded in **uppercase** crockford base32.

Please refer to the respective type documentation for more details.

Database Support

ID types in this package can be used with database/sql and github.com/jackc/pgx.

When using the standard library sql, IDs will be stored as their string representation and can be scanned and valued accordingly. When using pgx, both TEXT and UUID columns can be used directly. However, note that the type information is lost when using UUID columns, unless you take additional steps at the database layer. Be mindful of your identifier semantics, especially in complex JOIN queries.

Usage

To create a new ID type, define a prefix type that implements the Prefix interface. Then, define a TypeAlias for your ID type to Random or Sortable with your prefix type as generic argument.

Example:

import "github.com/sumup/typeid"

type UserPrefix struct{}

func (UserPrefix) Prefix() string {
    return "user"
}

type UserID = typeid.Sortable[UserPrefix]

userID, err := typeid.New[UserID]()
if err != nil {
    fmt.Println("create user id:", err)
}
fmt.Println(userID) // --> user_01hf98sp99fs2b4qf2jm11hse4

Index

Constants

View Source
const (
	// MaxPrefixLen is the maximum string length of a [Prefix]. Any generation or parsing of an ID type with a longer prefix will fail.
	MaxPrefixLen = 63
)

Variables

View Source
var (
	ErrParse = errors.New("parse typeid")
)

Functions

func FromString

func FromString[T instance[P], P Prefix](s string) (T, error)

func FromUUID

func FromUUID[T instance[P], P Prefix](u uuid.UUID) (T, error)

func FromUUIDBytes

func FromUUIDBytes[T instance[P], P Prefix](bytes []byte) (T, error)

func FromUUIDStr

func FromUUIDStr[T instance[P], P Prefix](uuidStr string) (T, error)

func Must

func Must[T any](tid T, err error) T

Must returns a TypeID if the error is nil, otherwise panics. This is a helper function to ease intialization in tests, etc. For generating a new id, use MustNew

Example:

testID := typeid.Must(typeid.FromString[UserID]("user_01hf98sp99fs2b4qf2jm11hse4"))

func MustNew

func MustNew[T instance[P], P Prefix]() T

MustNew returns a generated TypeID if the error is null, otherwise panics. Equivalent to:

typeid.Must(typeid.New[IDType]())

func New

func New[T instance[P], P Prefix]() (T, error)

New returns a new TypeID of the specified type with a randomly generated suffix.

Use the 'T' generic argument to indicate your ID type.

Example:

type UserID = typeid.Sortable[UserPrefix]
id, err := typeid.New[UserID]()

func Nil

func Nil[T instance[P], P Prefix]() T

Nil returns the nil identifier for the specified ID type. The nil identifier is a type identifier (typeid) with all corresponding UUID bytes set to zero. Functions in this package return the nil identifier in case of errors.

Types

type Prefix

type Prefix interface {
	Prefix() string
}

type Random

type Random[P Prefix] struct {
	// contains filtered or unexported fields
}

Random represents an unique identifier that is entirely random. Internally, it's based on UUIDv4.

func (Random[P]) MarshalText

func (r Random[P]) MarshalText() ([]byte, error)

MarshalText implements the encoding.TextMarshaler interface. Internally it use Random.String

func (Random) Prefix

func (tid Random) Prefix() string

func (*Random[P]) Scan

func (r *Random[P]) Scan(src any) error

func (*Random[P]) ScanText

func (r *Random[P]) ScanText(v pgtype.Text) error

func (*Random[P]) ScanUUID

func (r *Random[P]) ScanUUID(v pgtype.UUID) error

func (Random[P]) String

func (r Random[P]) String() string

func (Random[P]) TextValue

func (r Random[P]) TextValue() (pgtype.Text, error)

func (Random[P]) Type

func (Random[P]) Type() string

func (Random[P]) UUID

func (r Random[P]) UUID() uuid.UUID

func (Random[P]) UUIDValue

func (r Random[P]) UUIDValue() (pgtype.UUID, error)

func (*Random[P]) UnmarshalText

func (r *Random[P]) UnmarshalText(text []byte) error

UnmarshalText implements the encoding.TextUnmarshaler interface. It parses a TypeID string using FromString

func (Random[P]) Value

func (r Random[P]) Value() (driver.Value, error)

type Sortable

type Sortable[P Prefix] struct {
	// contains filtered or unexported fields
}

Sortable represents an unique identifier that is k-sortable. Internally, it's based on UUIDv7.

func (Sortable[P]) MarshalText

func (r Sortable[P]) MarshalText() ([]byte, error)

MarshalText implements the encoding.TextMarshaler interface. Internally it use Random.String

func (Sortable) Prefix

func (tid Sortable) Prefix() string

func (*Sortable[P]) Scan

func (s *Sortable[P]) Scan(src any) error

func (*Sortable[P]) ScanText

func (s *Sortable[P]) ScanText(v pgtype.Text) error

func (*Sortable[P]) ScanUUID

func (s *Sortable[P]) ScanUUID(v pgtype.UUID) error

func (Sortable[P]) String

func (s Sortable[P]) String() string

func (Sortable[P]) TextValue

func (s Sortable[P]) TextValue() (pgtype.Text, error)

func (Sortable[P]) Type

func (Sortable[P]) Type() string

func (Sortable[P]) UUID

func (r Sortable[P]) UUID() uuid.UUID

func (Sortable[P]) UUIDValue

func (s Sortable[P]) UUIDValue() (pgtype.UUID, error)

func (*Sortable[P]) UnmarshalText

func (r *Sortable[P]) UnmarshalText(text []byte) error

UnmarshalText implements the encoding.TextUnmarshaler interface. It parses a TypeID string using FromString

func (Sortable[P]) Value

func (s Sortable[P]) Value() (driver.Value, error)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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