README.md

# Exid - Elixir Xid

An Elixir implementation of the globally unique ID generator `xid`, suited for web scale, ported from the Go `xid` package.

## Overview

Exid generates **12-byte globally unique IDs** using the MongoDB Object ID algorithm:

- **4 bytes**: Unix timestamp (seconds since epoch)
- **3 bytes**: Machine identifier
- **2 bytes**: Process ID
- **3 bytes**: Counter (auto-incrementing, starting with random value)

### Why Xid?

- **Size**: 12 bytes (96 bits) - smaller than UUID (16 bytes), larger than Snowflake (8 bytes)
- **Sortable**: K-ordered, can be lexicographically sorted
- **No Configuration**: Automatically detects machine and process IDs
- **Web Ready**: Base32 hex encoded string representation (20 characters)
- **Unique**: Guaranteed uniqueness for 16,777,216 (2^24) IDs per second per host/process
- **Cross-Platform**: Compatible with Go `xid` package

### String Format

IDs are encoded as 20-character strings using base32 hex alphabet (0-9, a-v):

```elixir
iex> id = Exid.new()
iex> Exid.to_string(id)
"c0nsb7f1en2k9b5ls9bg"
```

## Installation

Add `exid` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:exid, "~> 0.1.0"}
  ]
end
```

## Quick Start

```elixir
# Initialize (must be called once at application startup)
Exid.init()

# Generate a new ID
id = Exid.new()

# Convert to string
id_string = Exid.to_string(id)

# Parse from string
{:ok, id} = Exid.from_string(id_string)

# Extract components
timestamp = Exid.time(id)
machine_id = Exid.machine(id)
process_id = Exid.pid(id)
counter = Exid.counter(id)

# Sort IDs (they're sortable!)
ids = [Exid.new(), Exid.new(), Exid.new()]
sorted = Exid.sort(ids)
```

## API Reference

### Generation

- `Exid.new/0` - Generate a new ID using current time
- `Exid.new_with_time/1` - Generate an ID with specific Unix timestamp

### Conversion

- `Exid.to_string/1` - Encode ID to 20-character base32 hex string
- `Exid.from_string/1` - Parse ID from base32 hex string
- `Exid.to_bytes/1` - Get binary representation (12 bytes)
- `Exid.from_bytes/1` - Parse ID from 12-byte binary

### Inspection

- `Exid.time/1` - Extract Unix timestamp
- `Exid.machine/1` - Extract 3-byte machine identifier
- `Exid.pid/1` - Extract 2-byte process identifier
- `Exid.counter/1` - Extract 3-byte counter value

### Comparison & Sorting

- `Exid.compare/2` - Compare two IDs (-1, 0, or 1)
- `Exid.sort/1` - Sort list of IDs

### Utilities

- `Exid.nil_id/0` - Get nil ID (all zeros)
- `Exid.is_nil/1` - Check if ID is nil
- `Exid.init/0` - Initialize ETS table (called automatically by application)

## Machine ID Detection

Xid automatically detects the machine ID using the following priority order:

1. **Environment Variable**: `XID_MACHINE_ID` (must be a number 0-16777215)
2. **Platform-Specific ID**:
   - **Darwin (macOS)**: `ioreg -rd1 -c IOPlatformExpertDevice` → IOPlatformUUID
   - **FreeBSD**: `sysctl kern.hostuuid`
   - **OpenBSD**: `sysctl hw.uuid`
   - **Linux**: `/etc/machine-id` or `/sys/class/dmi/id/product_uuid`
   - **Windows**: Registry `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography` → MachineGuid
3. **Hostname Hash**: SHA256 hash of system hostname
4. **Random**: Cryptographically random bytes (fallback)

### Setting Machine ID

```bash
# Override with environment variable
export XID_MACHINE_ID=12345

# Must be a valid 24-bit number (0-16777215)
export XID_MACHINE_ID=16777214  # OK
export XID_MACHINE_ID=16777216  # Error: out of range
```

## Cross-Compatibility with Go xid

This implementation is compatible with the Go `xid` package. IDs generated in Go can be decoded in Elixir and vice versa:

```elixir
# Decode Go-generated ID
{:ok, id} = Exid.from_string("9m4e2mr0ui3e8a215n4g")

# Verify components match Go implementation
Exid.time(id)     # => 1300816219
Exid.machine(id)  # => <<0x60, 0xf4, 0x86>>
Exid.pid(id)      # => 58408 (0xe428)
Exid.counter(id)  # => 4271561
```

## ETS Table

Xid uses an ETS table named `:xid_counter` to maintain the atomic counter for generating unique IDs. The table is created automatically on application startup.

- **Table**: `:xid_counter` (named, public, set, compressed)
- **Keys**: `:machine_id`, `:counter`
- **Initialization**: Called by `Exid.Application.start/2`


## Testing

Run the test suite:

```bash
mix test
```



Tests include:
- ID generation and uniqueness
- String encoding/decoding
- Cross-compatibility with Go xid (test vectors)
- ID parts extraction
- Sorting and comparison
- Platform-specific machine ID detection
- Edge cases and error handling


They are mostly ported from the Go xid package, with some additional tests and benchmarks.


### Running Benchmarks

```bash
mix run benchmark/benchmark.exs
```

## References

- [Go xid Package](https://github.com/rs/xid)
- [MongoDB Object ID Specification](https://docs.mongodb.org/manual/reference/object-id/)

## License

The Elixir port maintains compatibility with the original Go implementation by Olivier Poitrey, licensed under the MIT License.