From 2aa7117b3319c8c7860a6f73526130f597977174 Mon Sep 17 00:00:00 2001 From: Ekk Date: Sat, 14 Mar 2026 09:28:50 +0900 Subject: [PATCH] docs: add package comment describing OrderlyID format --- construct.go | 24 +++++++++++++++++------- doc.go | 24 ++++++++++++++++++++++++ orderlyid.go | 32 +++++++++++++++++++++++++------- 3 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 doc.go diff --git a/construct.go b/construct.go index 4923def..d1edfa4 100644 --- a/construct.go +++ b/construct.go @@ -5,16 +5,25 @@ import ( "fmt" ) +// Components describes the public fields packed into an OrderlyID. type Components struct { - Prefix string - TimeMs int64 - Flags uint8 - Tenant uint16 - Seq uint16 - Shard uint16 + // Prefix is the type prefix before the underscore separator. + Prefix string + // TimeMs is the absolute UTC timestamp in Unix milliseconds. + TimeMs int64 + // Flags is the raw flags byte stored in the payload. + Flags uint8 + // Tenant is the embedded 16-bit tenant identifier. + Tenant uint16 + // Seq is the 12-bit sequence number packed into the payload. + Seq uint16 + // Shard is the embedded 16-bit shard identifier. + Shard uint16 + // Random60 is the low 60 bits of random payload data. Random60 uint64 } +// NewFromParts builds an OrderlyID from explicit component values. func NewFromParts(c Components, withChecksum bool) (string, error) { if err := validatePrefix(c.Prefix); err != nil { return "", err @@ -40,7 +49,8 @@ func NewFromParts(c Components, withChecksum bool) (string, error) { return base, nil } -// NewFromPartsHex is a convenience that accepts random as big-endian hex string. +// NewFromPartsHex builds an OrderlyID from explicit component values and a +// big-endian random value encoded as hex. func NewFromPartsHex(c Components, randomHex string, withChecksum bool) (string, error) { rb, err := hex.DecodeString(randomHex) if err != nil { diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..997e741 --- /dev/null +++ b/doc.go @@ -0,0 +1,24 @@ +// Package orderlyid generates and parses typed, lexicographically sortable IDs. +// +// An OrderlyID has the canonical form "prefix_payload" or +// "prefix_payload-checksum". The prefix is a lowercase typed identifier such as +// "order" or "user" and must match [a-z][a-z0-9]{1,30}. The payload is a +// 32-character Crockford Base32 encoding of a packed 160-bit big-endian body. +// +// The binary body contains: +// +// - timestamp: 48-bit milliseconds since 2020-01-01T00:00:00Z, which makes IDs +// approximately ordered by creation time +// - flags: an 8-bit field where bits 7..6 carry the wire version, bit 5 marks +// privacy bucketing, and bits 4..0 are reserved +// - tenant: a 16-bit tenant identifier for multi-tenant systems +// - sequence: a 12-bit counter for bursts within the same millisecond +// - shard: a 16-bit routing hint, either provided directly or derived from +// bytes +// - random: 60 bits of cryptographic randomness +// +// Canonical output from this package is lowercase. Parsing is +// case-insensitive for payload and checksum characters, but prefixes must still +// satisfy the lowercase prefix rule. A trailing 4-character checksum is +// optional and can be used to detect copy/paste and transcription errors. +package orderlyid diff --git a/orderlyid.go b/orderlyid.go index 02da658..945e7b7 100644 --- a/orderlyid.go +++ b/orderlyid.go @@ -18,20 +18,24 @@ type options struct { bucketSeconds int } +// Option configures ID generation in New. type Option func(*options) +// WithTenant sets the 16-bit tenant value embedded in generated IDs. func WithTenant(t uint16) Option { return func(o *options) { o.tenant = t } } +// WithShard sets the 16-bit shard value embedded in generated IDs. func WithShard(s uint16) Option { return func(o *options) { o.shard = s } } +// WithShardFromBytes hashes b into a deterministic 16-bit shard value. func WithShardFromBytes(b []byte) Option { return func(o *options) { var h uint32 @@ -42,12 +46,14 @@ func WithShardFromBytes(b []byte) Option { } } +// WithChecksum enables or disables the trailing 4-character checksum. func WithChecksum(v bool) Option { return func(o *options) { o.withChecksum = v } } +// WithBucketSeconds rounds the embedded timestamp down to sec-second buckets. func WithBucketSeconds(sec int) Option { return func(o *options) { o.bucketSeconds = sec @@ -98,7 +104,11 @@ var ( seq12 uint16 // 12-bit ) -// New generates a new OrderlyID string like "order_0r8h...". +// New generates a new OrderlyID such as "order_0r8h...". +// +// The prefix must match the public ID type naming rules used by Parse. New +// panics if the prefix is invalid or if cryptographic randomness cannot be +// read. func New(prefix string, opts ...Option) string { if !prefixRe.MatchString(prefix) { panic("invalid prefix") @@ -154,17 +164,25 @@ func New(prefix string, opts ...Option) string { return id } -// Parse decodes an OrderlyID and returns its components. +// Parsed is the decoded representation of an OrderlyID. type Parsed struct { + // Prefix is the type prefix before the underscore separator. Prefix string - TimeMs int64 // epoch ms (UTC) - Flags byte + // TimeMs is the absolute UTC timestamp in Unix milliseconds. + TimeMs int64 + // Flags is the raw flags byte stored in the payload. + Flags byte + // Tenant is the embedded 16-bit tenant identifier. Tenant uint16 - Seq uint16 // 12-bit - Shard uint16 - Random uint64 // 60-bit + // Seq is the 12-bit per-millisecond sequence number. + Seq uint16 + // Shard is the embedded 16-bit shard identifier. + Shard uint16 + // Random is the 60-bit random suffix stored in the payload. + Random uint64 } +// Parse decodes an OrderlyID string and returns its components. func Parse(s string) (*Parsed, error) { s = strings.TrimSpace(s) base := s