A Go library for generating unique, time-based IDs using Unix timestamps at nanosecond precision.
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
go get github.com/tinywasm/unixidpackage 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
}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)
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
}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.
-
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 (
wasmor!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.Mutexor*sync.Mutexto 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- Fast validation for checking ID format
- Returns error if format is invalid
- Example usage:
err := idHandler.Validate("1624397134562544800") if err != nil { // Invalid ID format }
-
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
The library provides two methods for working with existing IDs:
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")
}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
}UnixID automatically detects the compilation environment and configures itself appropriately:
-
Server-side (
!wasmbuild tag):- Uses Go's standard
timepackage - Implements mutex-based thread safety
- Generates simple timestamp-based IDs
- Uses Go's standard
-
WebAssembly (
wasmbuild tag):- Uses JavaScript's Date API through
syscall/js - Requires a session handler to manage user identifiers
- Appends a user session number to IDs for cross-client uniqueness
- Uses JavaScript's Date API through
This automatic configuration allows you to use the same API in both environments while the library handles the implementation details internally.