Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@ Global helper functions are autoloaded via `src/functions.php`.
<?php

use Infocyph\UID\Id;
use Infocyph\UID\CUID2;
use Infocyph\UID\NanoID;

$uuid = Id::uuid(); // default UUID strategy (v7)
$ulid = Id::ulid();
$snowflake = Id::snowflake();
$sonyflake = Id::sonyflake();
$tbsl = Id::tbsl();
$nanoid = Id::nanoId(21);
$cuid2 = Id::cuid2(24);
$nanoid = NanoID::generate(21);
$cuid2 = CUID2::generate(24);
```

```php
Expand All @@ -66,6 +68,9 @@ $base58 = UUID::toBase($uuid, 58);
$decoded = UUID::fromBase($base58, 58);
```

The shared byte-level encoder is available as
`Infocyph\UID\Support\BaseEncoder` for bases `16`, `32`, `36`, `58`, and `62`.

## References

- UUID: https://datatracker.ietf.org/doc/html/rfc9562
Expand Down
114 changes: 108 additions & 6 deletions TBSL.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,111 @@
# TBSL
# TBSL Specification

A 20 character alphanumeric ID generated by a time-based lexicographically sortable algorithm. It stores time in first
13 characters and machine ID in 14th-15th characters. Rest 5 is randomly generated.
TBSL is a project-specific, time-based, lexicographically sortable identifier used by
`Infocyph\UID\TBSL`.

| Time Identifier | Machine Identifier | Random Character (can be sequenced) |
|:---------------:|:------------------:|:-----------------------------------:|
| 13 | 2 | 5 |
This document defines the canonical TBSL text format and the generation rules used
by this package.

## Canonical Format

A canonical TBSL identifier is exactly 20 uppercase hexadecimal characters:

```text
^[0-9A-F]{20}$
```

The canonical string represents 10 bytes, or 80 bits, of data. Lowercase
hexadecimal and non-hexadecimal characters are not canonical TBSL values.

## Field Layout

Character positions are 1-based.

| Characters | Length | Field | Description |
|:--|--:|:--|:--|
| 1-15 | 15 hex chars | Time-machine payload | Uppercase hexadecimal encoding of the decimal payload `SSSSSSSSSSUUUUUUMM`, left-padded with zeroes to 15 characters. |
| 16-20 | 5 hex chars | Entropy or sequence | Random suffix by default, or a sequence suffix when sequenced mode is enabled. |

The decimal payload is composed as follows:

| Decimal digits | Length | Field | Description |
|:--|--:|:--|:--|
| 1-10 | 10 digits | Unix seconds | Seconds since the Unix epoch. |
| 11-16 | 6 digits | Microseconds | Microsecond fraction of the current second. |
| 17-18 | 2 digits | Machine ID | Machine identifier from `00` to `99`. |

## Generation

Generation accepts:

- `machineId`: integer from `0` to `99`; default is `0`.
- `sequenced`: boolean; default is `false`.

The generator:

1. Reads the current Unix time with microsecond precision.
2. Builds the decimal time sequence as `seconds + microseconds`.
3. Appends the two-digit machine ID to form `SSSSSSSSSSUUUUUUMM`.
4. Converts that decimal payload to hexadecimal and left-pads it to 15
characters.
5. Appends a 5-character hexadecimal suffix:
- random mode: first 5 hex characters from 3 random bytes;
- sequenced mode: the next sequence value for the
`(type = "tbsl", machineId, timestamp)` key, encoded as hex and padded to
5 characters.
6. Returns the 20-character uppercase hexadecimal string.

Sequence providers should keep returned sequence values within the 20-bit suffix
range, `0x00000` through `0xFFFFF`.

## Parsing

To parse a canonical TBSL value:

1. Validate the string against `^[0-9A-F]{20}$`.
2. Decode characters `1-15` from hexadecimal to the decimal payload.
3. Read the first 10 decimal digits as Unix seconds.
4. Read the next 6 decimal digits as microseconds.
5. Read the final 2 decimal digits as the machine ID.

The suffix is intentionally opaque. It is not needed to recover the timestamp or
machine ID.

## Ordering

Canonical TBSL strings sort lexicographically by their time-machine payload first.
This preserves chronological ordering for generated IDs as long as system clocks
move forward.

For IDs generated within the same microsecond and machine ID:

- random mode provides uniqueness through entropy, but not generation order;
- sequenced mode provides deterministic suffix ordering while the sequence value
remains within the 5-character hexadecimal suffix.

If the clock moves backward, the implementation either waits for the next usable
time sequence or throws, depending on the configured clock-backward policy.

## Encodings

The canonical representation is uppercase hexadecimal. The package can also
convert canonical TBSL values to and from:

- raw 10-byte binary;
- base16;
- base32;
- base36;
- base58;
- base62.

Alternate-base encodings are transport encodings only. They do not replace the
canonical 20-character uppercase hexadecimal TBSL string.

## Limits

- Canonical size: 20 hex characters.
- Binary size: 10 bytes.
- Timestamp precision: microseconds.
- Machine ID range: `0` through `99`.
- Suffix size: 5 hex characters, or 20 bits.
- Maximum suffix cardinality per `(timestamp, machineId)` key: 1,048,576 values.
4 changes: 4 additions & 0 deletions captainhook.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
"action": "composer validate --strict",
"options": []
},
{
"action": "composer normalize --dry-run",
"options": []
},
{
"action": "composer ic:release:audit",
"options": []
Expand Down
39 changes: 20 additions & 19 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "infocyph/uid",
"description": "UUID (RFC 9562), ULID, Snowflake ID, Sonyflake ID, and TBSL generator for PHP.",
"type": "library",
"license": "MIT",
"type": "library",
"keywords": [
"uuid",
"ulid",
Expand All @@ -23,34 +23,35 @@
"email": "abmmhasan@gmail.com"
}
],
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"Infocyph\\UID\\": "src/"
}
},
"replace": {
"abmmhasan/uuid": "*"
},
"require": {
"php": ">=8.2",
"ext-bcmath": "*",
"psr/simple-cache": "^3.0"
},
"require-dev": {
"infocyph/phpforge": "dev-main"
},
"replace": {
"abmmhasan/uuid": "*"
},
"minimum-stability": "stable",
"prefer-stable": true,
"autoload": {
"psr-4": {
"Infocyph\\UID\\": "src/"
},
"files": [
"src/functions.php"
]
},
"config": {
"sort-packages": true,
"optimize-autoloader": true,
"classmap-authoritative": true,
"allow-plugins": {
"ergebnis/composer-normalize": true,
"infocyph/phpforge": true,
"pestphp/pest-plugin": true
}
},
"require-dev": {
"infocyph/phpforge": "dev-main"
},
"classmap-authoritative": true,
"optimize-autoloader": true,
"sort-packages": true
}
}
60 changes: 60 additions & 0 deletions docs/base-encoding.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
Base Encoding
=============

UID exposes base conversion through algorithm-specific APIs and a shared
``Infocyph\\UID\\Support\\BaseEncoder`` utility.

Supported Bases
---------------

- ``16``: lowercase hexadecimal alphabet
- ``32``: ``0-9a-v`` alphabet
- ``36``: ``0-9a-z`` alphabet
- ``58``: Bitcoin-style Base58 alphabet
- ``62``: ``0-9A-Za-z`` alphabet

Algorithm APIs
--------------

Prefer the algorithm-specific methods when converting IDs, because they validate
the canonical input and restore the canonical output type:

- ``UUID::toBase($uuid, $base)`` / ``UUID::fromBase($encoded, $base)``
- ``ULID::toBase($ulid, $base)`` / ``ULID::fromBase($encoded, $base)``
- ``Snowflake::toBase($id, $base)`` / ``Snowflake::fromBase($encoded, $base)``
- ``Sonyflake::toBase($id, $base)`` / ``Sonyflake::fromBase($encoded, $base)``
- ``TBSL::toBase($id, $base)`` / ``TBSL::fromBase($encoded, $base)``

.. code-block:: php

<?php

use Infocyph\UID\UUID;

$uuid = UUID::v7();
$base58 = UUID::toBase($uuid, 58);
$canonical = UUID::fromBase($base58, 58);

Low-Level Encoder
-----------------

``BaseEncoder`` works with raw bytes:

.. code-block:: php

<?php

use Infocyph\UID\Support\BaseEncoder;

$bytes = random_bytes(16);
$encoded = BaseEncoder::encodeBytes($bytes, 62);
$decoded = BaseEncoder::decodeToBytes($encoded, 62, 16);

``decodeToBytes()`` requires the expected byte length so the decoded value can be
left-padded and validated consistently.

Notes
-----

Alternate-base values are transport encodings. They do not replace each
algorithm's canonical representation.
12 changes: 12 additions & 0 deletions docs/compatibility.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,24 @@ Non-UUID Families
- NanoID and CUID2: URL-safe random IDs.
- KSUID and XID: sortable short ID families.

Algorithm Interface
-------------------

``NanoID``, ``CUID2``, ``KSUID``, and ``XID`` implement
``Infocyph\\UID\\Contracts\\IdAlgorithmInterface`` with:

- ``generate()``
- ``isValid()``
- ``parse()``

Binary and Alternate Encodings
------------------------------

- UUID / ULID / TBSL: ``toBytes()`` / ``fromBytes()``.
- UUID / ULID / Snowflake / Sonyflake / TBSL: ``toBase()`` / ``fromBase()``.
- KSUID / XID: ``toBytes()`` / ``fromBytes()``.
- Shared byte-level encoder: ``Infocyph\\UID\\Support\\BaseEncoder``.
- Supported bases: ``16``, ``32``, ``36``, ``58``, ``62``.

Runtime Requirements
--------------------
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ It supports:
:caption: Advanced

sequence-providers
base-encoding
value-objects
helpers
db-storage
Expand Down
6 changes: 6 additions & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ If you prefer explicit static APIs only, call the namespaced classes directly:
- ``Infocyph\\UID\\Snowflake``
- ``Infocyph\\UID\\Sonyflake``
- ``Infocyph\\UID\\TBSL``
- ``Infocyph\\UID\\NanoID``
- ``Infocyph\\UID\\CUID2``
- ``Infocyph\\UID\\KSUID``
- ``Infocyph\\UID\\XID``
- ``Infocyph\\UID\\OpaqueId``
- ``Infocyph\\UID\\DeterministicId``

Read the Docs Build
-------------------
Expand Down
18 changes: 18 additions & 0 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ Using the ``Id`` Facade
$nano = Id::nanoId(21);
$cuid2 = Id::cuid2(24);

Using Algorithm Classes
-----------------------

.. code-block:: php

<?php

use Infocyph\UID\CUID2;
use Infocyph\UID\NanoID;

$nano = NanoID::generate(21);
$cuid2 = CUID2::generate(24);

NanoID::isValid($nano, 21); // bool
CUID2::isValid($cuid2); // bool

Validation and Parsing
----------------------

Expand Down Expand Up @@ -51,6 +67,8 @@ Binary and Base Conversion
$base58 = Id::uuidToBase($uuid, 58);
$uuidAgain = Id::uuidFromBase($base58, 58);

$tbslBase62 = tbsl_to_base(Id::tbsl(), 62);

Configuration-Based Generators
------------------------------

Expand Down
Loading
Loading