UnixID

A Go library for generating unique, time-based IDs using Unix timestamps at nanosecond precision.
Overview
UnixID provides functionality for generating and managing unique identifiers with the following features:
- High-performance ID generation based on Unix nanosecond timestamps
- Thread-safe concurrent ID generation
- Built-in collision avoidance through sequential numbering
- Support for both server-side and client-side (WebAssembly) environments
- Date conversion utilities for timestamp-to-date formatting
- Smart environment detection for automatic configuration
- Versatile ID assignment for strings, struct fields and byte slices
Installation
go get github.com/tinywasm/unixid
Quick Start
Server-side Usage
package main
import (
"github.com/tinywasm/unixid"
)
func main() {
// Create a new UnixID handler (server-side)
idHandler, err := unixid.NewUnixID()
if err != nil {
panic(err)
}
// Generate a new unique ID
id := idHandler.GetNewID()
fmt.Printf("Generated ID: %s\n", id)
// Output: Generated ID: 1624397134562544800
}
Client-side (WebAssembly) Usage
For WebAssembly environments, you need to provide a session number handler:
// Example session handler implementation
type sessionHandler struct{}
func (sessionHandler) userSessionNumber() string {
// In a real application, this would return the user's session number
return "42"
}
// Create a new UnixID handler with session handler
idHandler, err := unixid.NewUnixID(&sessionHandler{})
The generated IDs follow this format:
- Server-side:
[unix_timestamp_in_nanoseconds] (e.g., 1624397134562544800)
- Client-side:
[unix_timestamp_in_nanoseconds].[user_session_number] (e.g., 1624397134562544800.42)
Thread Safety & Avoiding Deadlocks
The library handles concurrent ID generation safely through mutex locking in server-side environments.
IMPORTANT: When integrating this library with other libraries that also use sync.Mutex, infinite deadlocks can occur. To avoid this issue, you can pass an existing mutex when initializing UnixID:
package main
import (
"fmt"
"sync"
"github.com/tinywasm/unixid"
"github.com/someother/library"
)
func main() {
// Create a shared mutex
var mu sync.Mutex
// Pass the shared mutex to UnixID
idHandler, err := unixid.NewUnixID(&mu)
if (err != nil) {
panic(err)
}
// Pass the same mutex to other libraries if they support it
otherLib := library.New(&mu)
// Now both libraries will use the same mutex,
// preventing deadlocks when they need to lock resources
}
Deadlock Prevention with External Mutex
When an external mutex is provided to NewUnixID(), the library automatically detects this and changes its behavior:
- Instead of using the provided mutex internally, it switches to a no-op mutex that doesn't perform any actual locking.
- This allows
GetNewID() to be safely called from within a context that has already acquired the same mutex.
Example of using GetNewID() inside a locked context:
var mu sync.Mutex
idHandler, err := unixid.NewUnixID(&mu)
if err != nil {
panic(err)
}
// Later in your code...
mu.Lock()
defer mu.Unlock()
// This won't deadlock because internally the library uses a no-op mutex
// when an external mutex is provided
id := idHandler.GetNewID()
// Do something with id...
This behavior assumes that external synchronization is being properly handled by the caller, eliminating the risk of deadlocks when the same mutex is used in nested contexts.
API Reference
Core Functions
-
NewUnixID(...): Creates a new UnixID handler for ID generation with automatic environment detection
- In server environments, no parameters are required
- In WebAssembly environments, requires a userSessionNumber implementation
- Uses build tags (
wasm or !wasm) to determine the appropriate implementation
- Thread-safe in server environments with mutex locking
- No mutex in WebAssembly as JavaScript is single-threaded
- Can accept an existing
sync.Mutex or *sync.Mutex to prevent deadlocks when integrating with other libraries
-
GetNewID(): Generates a new unique ID
- Returns a string representation of the ID
- In WebAssembly builds, appends a user session number to the timestamp
-
SetNewID(target any): Sets a new unique ID to various target types
- Accepts pointers to string, reflect.Value, or byte slices
- Thread-safe in server environments
- Example usages:
// Set ID to a string variable
var id string
idHandler.SetNewID(&id)
// Set ID to a struct field
type User struct { ID string }
user := User{}
idHandler.SetNewID(&user.ID)
// Append ID to a byte slice
buf := make([]byte, 0, 64)
idHandler.SetNewID(buf)
// Set ID to a tinyreflect.Value
v := tinyreflect.ValueOf(&user)
// The ValueOf(&data) returns a Ptr. We need to get the element it points to
// before we can access its fields. This is what Elem() does.
structVal, err := v.Elem()
if err != nil {
// Failed to get element from pointer value:...
}
IDField, err := structVal.Field(0)
if err != nil {
// Failed to get field 'ID':...
}
idHandler.SetNewID(&IDField)
-
Validate(id string) error: Validates the format of an ID string without parsing it
-
Parse(id string) (timestamp int64, userNum string, error): Parses an ID string and extracts its components
- Validates format first, then extracts timestamp and optional user number
- Returns timestamp as int64, userNum as string (empty if not present)
- Example usage:
timestamp, userNum, err := idHandler.Parse("1624397134562544800.42")
if err != nil {
// Invalid ID format
}
fmt.Printf("Timestamp: %d, UserNum: %s\n", timestamp, userNum)
// Output: Timestamp: 1624397134562544800, UserNum: 42
Validate and Parse IDs
The library provides two methods for working with existing IDs:
Validation Only
Use Validate() when you only need to check if an ID format is valid:
package main
import (
"fmt"
"github.com/tinywasm/unixid"
)
func main() {
idHandler, _ := unixid.NewUnixID()
id := "1624397134562544800"
err := idHandler.Validate(id)
if err != nil {
fmt.Println("Invalid ID format")
return
}
fmt.Println("Valid ID format")
}
Parsing ID Components
Use Parse() when you need to extract the timestamp and user number:
package main
import (
"fmt"
"github.com/tinywasm/unixid"
)
func main() {
idHandler, _ := unixid.NewUnixID()
// Parse server-side ID
timestamp, userNum, err := idHandler.Parse("1624397134562544800")
if err != nil {
panic(err)
}
fmt.Printf("Timestamp: %d, UserNum: %s\n", timestamp, userNum)
// Output: Timestamp: 1624397134562544800, UserNum:
// Parse client-side ID
timestamp, userNum, err = idHandler.Parse("1624397134562544800.42")
if err != nil {
panic(err)
}
fmt.Printf("Timestamp: %d, UserNum: %s\n", timestamp, userNum)
// Output: Timestamp: 1624397134562544800, UserNum: 42
}
Environment-Based Configuration
UnixID automatically detects the compilation environment and configures itself appropriately:
This automatic configuration allows you to use the same API in both environments while the library handles the implementation details internally.