Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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