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/cdvelop/unixid
package main
import (
"github.com/cdvelop/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
// Convert an ID to a human-readable date
dateStr, err := idHandler.UnixNanoToStringDate(id)
if err != nil {
panic(err)
}
println("ID timestamp represents: ", dateStr)
// Output: ID timestamp represents: 2021-06-23 15:38:54
}
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/cdvelop/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 (
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, tinyreflect.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)
-
UnixNanoToStringDate(unixNanoStr)
: Converts a Unix nanosecond timestamp ID to a human-readable date
UnixSecondsToTime(unixSeconds any) string
: Converts a Unix timestamp in seconds to a formatted time string (HH:mm:ss). e.g.,1624397134
->15:38:54
supportsint64
,string
, andfloat64
types
The ValidateID
function validates and parses a given ID string. It returns the parsed ID as an int64
and an error if the ID is invalid.
package main
import (
"fmt"
"github.com/cdvelop/unixid"
)
func main() {
id := "1624397134562544800"
parsedID, err := unixid.ValidateID(id)
if err != nil {
panic(err)
}
fmt.Printf("Parsed ID: %d\n", parsedID)
// Output: Parsed ID: 1624397134562544800
}
UnixID automatically detects the compilation environment and configures itself appropriately:
-
Server-side (
!wasm
build tag):- Uses Go's standard
time
package - Implements mutex-based thread safety
- Generates simple timestamp-based IDs
- Uses Go's standard
-
WebAssembly (
wasm
build 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.
This project is licensed under the MIT License. See the LICENSE file for details.