hyperuuid-wasm 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba3ad5f11b5973d062a7dfff6d85f657a809873d39b166c7837ee30d99092a2d
4
- data.tar.gz: efde3c81dba7991a8238aaa2929f0abcf026c8c6b6b6454af334a2c6473860d1
3
+ metadata.gz: 8b35d26b737a64df74f7a10783112899a0386bbd7dd7b7aa6387b4cbd5fb3c09
4
+ data.tar.gz: c37cd5f4d1bc6ad5eef8d181aa263ac61212c73f4f73640724f63e53a9797f61
5
5
  SHA512:
6
- metadata.gz: 5e10e781dad666e3734d3bb01c05cf1eeb537e309e5a2e733561153a02bca3817cfe1b6358b4a2129eb86446be2f79cd0d72c210efbea37b0600c2776ed716e0
7
- data.tar.gz: cf012e5d94e0bbe5c9edd0d9c437fef470238deedad2871ab2aa74d3fc09323b07a47de12e98d9bbb7fb9e5a0cb7d26e98d3b76320f36930b84d904310405901
6
+ metadata.gz: 1580f8edd7863896d8b646db61e5918aa7efcaa22db88995e2c6aa2d4456f8dd2698e1200e214191830b984aee8fcdd56f22f5da51985a48305401bf10b84201
7
+ data.tar.gz: e5a86147bafa4f1e9bfb266656e073f135286f07750dbb930cf6a8b10e9a3b45060e5a8daf73cd6c7162ee060a75f3e4f02c3a609c63fc8b94bbb26a0a46c653
data/README.md CHANGED
@@ -4,10 +4,10 @@
4
4
  [![RubyGems](https://img.shields.io/gem/v/hyperuuid.svg)](https://rubygems.org/gems/hyperuuid)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/SkunkWerkx/HyperUuid/blob/master/LICENSE)
6
6
 
7
- **Ruby's own stdlib stops at `SecureRandom.uuid` and `SecureRandom.uuid_v7` — random v4 and v7, one at a time. No v5, no v6, no batches, no SQL Server order. This gem is the whole RFC, with zero gem dependency beyond `Fiddle` (which ships with every Ruby install) — and it's faster than `SecureRandom.uuid` too.**
7
+ **Ruby's own stdlib stops at `SecureRandom.uuid` and `SecureRandom.uuid_v7` — random v4 and v7, one at a time. No v5, no batches, no timestamp read-back, no SQL Server order. This gem fills that in, with zero gem dependency beyond `Fiddle` (which ships with every Ruby install) — and it's faster than `SecureRandom.uuid` too.**
8
8
 
9
- RFC 9562 UUID v4 (random), v5 (deterministic), v6 and v7 (time-sortable) generation, with
10
- two backends sharing one public surface. Ruby 3.3 is the floor. The fast path is a native
9
+ RFC 9562 UUID v4 (random), v5 (deterministic) and v7 (time-sortable) generation, with a
10
+ version 8 SQL Server order for v7 and two backends sharing one public surface. Ruby 3.3 is the floor. The fast path is a native
11
11
  extension built with [Magnus](https://github.com/matsadler/magnus) — the Rust core linked
12
12
  directly into the Ruby VM, auto-selected when loadable — which redefines the low-level
13
13
  `Runtime` methods in place on require; everything above them (`Uuid`, the module doors and
@@ -22,13 +22,12 @@ require "hyperuuid"
22
22
 
23
23
  id = HyperUuid.new_v4
24
24
  id2 = HyperUuid.new_v5(HyperUuid::Namespaces::DNS, "example.com")
25
- id3 = HyperUuid.new_v6
26
- id4 = HyperUuid.new_v7
25
+ id3 = HyperUuid.new_v7
27
26
 
28
- id4.timestamp # recover the embedded UTC Time
29
- id4.timestamp(raise_on_mismatch: false) # nil instead of raising if id4 isn't an RFC 9562 v6/v7
30
- id4.rfc?(7) # the RFC 9562 variant and version 7, the guard before trusting its fields
31
- sql = id4.to_sql_order # byte order SQL Server's uniqueidentifier needs to sort by creation order
27
+ id3.timestamp # recover the embedded UTC Time
28
+ id3.timestamp(raise_on_mismatch: false) # nil instead of raising if id3 isn't an RFC 9562 v7
29
+ id3.rfc?(7) # the RFC 9562 variant and version 7, the guard before trusting its fields
30
+ sql = id3.to_sql_order # a version 8 laid out so SQL Server's uniqueidentifier sorts by creation order
32
31
  sql.timestamp(layout: :sql_server) # read straight from SQL order, no round trip
33
32
 
34
33
  # One native call, one random-bytes fetch, one counter reservation for the whole batch:
@@ -41,9 +40,9 @@ batch = HyperUuid.new_v7_batch(1000)
41
40
  |---|---|---|
42
41
  | `new_v4` | `Uuid` | — |
43
42
  | `new_v5(namespace, name)` | `Uuid`, the same one for the same pair | a `Uuid` namespace (`Namespaces::DNS`/`URL`/`OID`/`X500`, or your own) and a `String` name — text is hashed as UTF-8, a binary String as its bytes, and it may be empty |
44
- | `new_v6(time = nil)` `new_v7(time = nil)` | `Uuid` | nothing (now), a `Time`, or an Integer of Unix-epoch milliseconds |
45
- | `new_v6_batch(count, time = nil)` `new_v7_batch(count, time = nil)` | `Array` of `Uuid` | a count (at most `MAX_V7_BATCH` for v7), and the same time forms |
46
- | `new_v6_batch_bytes(count, time = nil)` `new_v7_batch_bytes(count, time = nil)` | one binary `String`, 16 bytes per UUID | as the batch doors — see [Bulk generation into bytes](#bulk-generation-into-bytes) |
43
+ | `new_v7(time = nil)` | `Uuid` | nothing (now), a `Time`, or an Integer of Unix-epoch milliseconds |
44
+ | `new_v7_batch(count, time = nil)` | `Array` of `Uuid` | a count (at most `MAX_V7_BATCH`), and the same time forms |
45
+ | `new_v7_batch_bytes(count, time = nil)` | one binary `String`, 16 bytes per UUID | as `new_v7_batch` — see [Bulk generation into bytes](#bulk-generation-into-bytes) |
47
46
  | `native_version` | the loaded core's `"major.minor.patch"` | — |
48
47
  | `available?` | `true`/`false`, never raising | — |
49
48
 
@@ -56,50 +55,70 @@ runtime dependency on the `uuid` gem:
56
55
  | `Uuid.new(bytes)` | wraps 16 raw RFC 9562-ordered bytes |
57
56
  | `Uuid::NIL` `Uuid::MAX` | the RFC 9562 §5.9/§5.10 special values |
58
57
  | `#bytes` `#to_s` | the frozen binary String, the hyphenated text |
59
- | `#version(layout: :rfc9562)` | the version in that layout: the nibble, 0-15, in RFC order; 6 or 7 for a SQL-ordered v6/v7 in `:sql_server`, 0 when the bytes don't form one |
58
+ | `#version(layout: :rfc9562)` | the version in that layout: the nibble, 0-15, in RFC order; in `:sql_server`, 7, the version a SQL-ordered value was converted from, and 0 when the bytes aren't one |
60
59
  | `#variant` | the RFC 9562 §4.1 variant, as a Symbol: `:rfc9562` (every UUID this gem mints, and the only variant with versions), `:ncs` (Nil among them), `:microsoft`, `:future` (Max among them) |
61
60
  | `#rfc?(version, layout: :rfc9562)` | whether it is an RFC 9562 UUID of that version: the variant and the version together. A version outside 0-15 is `false` |
62
- | `#timestamp(raise_on_mismatch: true, layout: :rfc9562)` | the UTC `Time` embedded in an RFC 9562 version 6 or 7 UUID held in that layout, in one native call that checks the variant as well as the version (a 6 or 7 nibble under another variant has no timestamp); `nil` instead of an `ArgumentError` for anything that isn't one when passed `false` |
63
- | `#to_sql_order` `#from_sql_order` | to and from the byte order SQL Server's `uniqueidentifier` sorts by |
61
+ | `#timestamp(raise_on_mismatch: true, layout: :rfc9562)` | the UTC `Time` embedded in an RFC 9562 version 7 UUID held in that layout, in one native call that checks the variant as well as the version (a 7 nibble under another variant has no timestamp, and nor does a v1 or v6 minted elsewhere); `nil` instead of an `ArgumentError` for anything that isn't one when passed `false` |
62
+ | `#to_sql_order` `#from_sql_order` | to and from the layout SQL Server's `uniqueidentifier` sorts by, refusing what doesn't fit it |
64
63
  | `==` `eql?` `hash` `<=>` | value equality and byte-order comparison (`Comparable`) |
65
64
 
66
- `#to_sql_order`/`#from_sql_order` convert a version 6 or 7 UUID to and from the byte order SQL
67
- Server's `uniqueidentifier` needs on the wire to sort by creation order (`#to_sql_order`
68
- dispatches on the UUID's own version, matching `#timestamp`'s convention) — computed once in
69
- the native Rust core rather than reimplemented in Ruby, and verified there (and independently
70
- against the real `System.Data.SqlTypes.SqlGuid` comparator in the C# binding's test suite).
71
- Same-millisecond v6 UUIDs aren't guaranteed to sort correctly afterward — v6 has no counter,
72
- so `clock_seq`/`node` (not the timestamp) decide ties, the same pre-existing RFC 9562 v6
73
- limitation plain order already has. `#from_sql_order` asks the core which version it holds
74
- (`#version(layout: :sql_server)`, which checks each version's nibble together with its variant
75
- bits, so a v6 whose random `clock_seq` byte reads as a v7 nibble is still a v6).
65
+ `#to_sql_order`/`#from_sql_order` convert a version 7 UUID to and from the layout SQL Server's
66
+ `uniqueidentifier` needs on the wire to sort by creation order — computed once in the native
67
+ Rust core rather than reimplemented in Ruby, and verified there (and independently against the
68
+ real `System.Data.SqlTypes.SqlGuid` comparator in the C# binding's test suite). The converted
69
+ value is an RFC 9562 version 8: the 8 and the RFC variant sit where SQL Server's text form and
70
+ a .NET `Guid` show them, and the v7's 122 other bits (timestamp, counter, random) fill the rest
71
+ in the order SQL Server compares them. Every v7 converts, and the conversion back is exact.
72
+ Either direction refuses a value of the wrong kind with an `ArgumentError`: anything that isn't
73
+ an RFC 9562 v7 for `#to_sql_order`, anything without the layout's 8 and variant for
74
+ `#from_sql_order`.
76
75
 
77
76
  ### Layouts
78
77
 
79
78
  `#version`, `#rfc?` and `#timestamp` take the byte order the value is held in as `layout:`, a
80
79
  Symbol: `:rfc9562` (the default, what every other method takes and returns) or `:sql_server`
81
- (what `#to_sql_order` returns, defined for versions 6 and 7 only). `Uuid::LAYOUTS` lists them.
80
+ (what `#to_sql_order` returns, a version 8 holding a converted v7). `Uuid::LAYOUTS` lists them.
82
81
  Anything else is an `ArgumentError`, never a guess. With the default, `#version` and `#rfc?`
83
82
  read the value as RFC 9562 order, which looks at the wrong bytes of a SQL-ordered value (one
84
83
  from `#to_sql_order`, or a `uniqueidentifier` read back from SQL Server), so pass
85
84
  `layout: :sql_server` at every call site that can see one. There is deliberately no
86
85
  layout-agnostic check: whoever holds the value knows its order.
87
86
 
88
- `#variant` reads RFC 9562 order only and has no layout form, because in SQL Server order the
89
- variant sits at a different byte for each version. Don't hand it a SQL-ordered value or a
90
- `uniqueidentifier` read-back; to validate one, `#rfc?(version, layout: :sql_server)` checks the
91
- variant where that version puts it.
87
+ `#variant` reads RFC 9562 order only and has no layout form. The SQL Server layout keeps the
88
+ variant in the same place, so a SQL-ordered value reads `:rfc9562` there, but that says nothing
89
+ about whether it holds a converted v7; to validate one, or a `uniqueidentifier` read-back,
90
+ `#rfc?(7, layout: :sql_server)` checks the layout's 8 and variant.
92
91
 
93
92
  ```ruby
94
- sql = HyperUuid.new_v6.to_sql_order
95
- sql.version(layout: :sql_server) # => 6
96
- sql.rfc?(6, layout: :sql_server) # => true
93
+ sql = HyperUuid.new_v7.to_sql_order
94
+ sql.version(layout: :sql_server) # => 7
95
+ sql.rfc?(7, layout: :sql_server) # => true
97
96
  sql.timestamp(layout: :sql_server) # the same Time as sql.from_sql_order.timestamp
98
97
  ```
99
98
 
100
99
  The bytes alone cannot say which order they are in, so the layout is yours to know: an
101
- RFC-ordered value can happen to form a SQL-ordered one (a random v4 reads as a SQL-ordered v7
102
- one time in 16), and `#from_sql_order` then converts it rather than refusing it.
100
+ RFC-ordered value can happen to carry the layout's 8 and variant (a random v4 does one time in
101
+ 16), and `#from_sql_order` then converts it rather than refusing it. The same goes the other
102
+ way: a value already in SQL order carries a 7 where `#to_sql_order` looks for the version about
103
+ one time in 16, and is then converted a second time rather than refused.
104
+
105
+ ### Binding to SQL Server
106
+
107
+ Send `#to_sql_order`'s result as its raw `#bytes`, not as UUID text. SQL Server stores a UUID
108
+ string's first three groups byte-reversed, which loses the creation order
109
+ ([details](https://github.com/SkunkWerkx/HyperUuid#sql-server-ordering)); tiny_tds 3.4 and an ActiveRecord `:uuid` attribute both send text. A binary
110
+ literal lands byte for byte:
111
+
112
+ ```ruby
113
+ sql = HyperUuid.new_v7.to_sql_order
114
+ client.execute("INSERT INTO orders (id) VALUES (0x#{sql.bytes.unpack1('H*')})").do
115
+
116
+ row = client.execute("SELECT CAST(id AS binary(16)) AS id FROM orders").first
117
+ sql = HyperUuid::Uuid.new(row["id"]) # read with layout: :sql_server
118
+ ```
119
+
120
+ In ActiveRecord, map the column as `:binary`, which quotes as `0x…`. Reading needs the `CAST`
121
+ too: tiny_tds returns a `uniqueidentifier` as text with those groups reversed.
103
122
 
104
123
  ### The version 7 batch limit
105
124
 
@@ -109,7 +128,7 @@ mints. A larger count is an `ArgumentError` naming the limit, raised before anyt
109
128
  allocated. Every batch is in strictly increasing order: the counter is one process-wide
110
129
  sequence, so a batch can straddle the point where it wraps back to 0, and the UUIDs from there
111
130
  on carry a timestamp one millisecond later than the one supplied rather than sorting before the
112
- ones ahead of them. Version 6 has no counter and no such limit.
131
+ ones ahead of them.
113
132
 
114
133
  That roll-forward orders one batch, not the stream. The next batch or single `new_v7` in the
115
134
  same real millisecond starts its counter just past the wrap and carries the supplied timestamp,
@@ -121,9 +140,8 @@ happens at most once per 2²⁶ UUIDs the process mints.
121
140
 
122
141
  Two exceptions are this gem's own, and both carry the same message on every backend:
123
142
 
124
- - `HyperUuid::TimestampOutOfRangeError` — the time cannot be embedded: past version 7's
125
- 48-bit millisecond field, past version 6's 60-bit one, or negative (a `Time` before the Unix
126
- epoch included).
143
+ - `HyperUuid::TimestampOutOfRangeError` — the time cannot be embedded in a version 7: past
144
+ its 48-bit millisecond field, or negative (a `Time` before the Unix epoch included).
127
145
  - `HyperUuid::RandomSourceError` — the operating system's random source failed.
128
146
 
129
147
  Through 0.3.0 these were reachable only as `HyperUuid::Runtime::TimestampOutOfRangeError` and
@@ -133,9 +151,10 @@ Everything else is a caller bug and raises Ruby's own error for it, checked once
133
151
  doors rather than left to whichever backend is live: a `TypeError` for a time that is not a
134
152
  `Time`, an Integer or `nil`, for a count that is not an Integer, for a namespace that is not a
135
153
  `Uuid` or a name that is not a `String`, for a version passed to `#rfc?` that is not an
136
- Integer; an `ArgumentError` for a count outside 0..2³² − 1 (0..`MAX_V7_BATCH` for version 7),
137
- for a layout other than `:rfc9562`/`:sql_server`, and for a batch too large for the platform to
138
- address (a 32-bit target such as ruby.wasm).
154
+ Integer; an `ArgumentError` for a count outside 0..`MAX_V7_BATCH`, for a layout other than
155
+ `:rfc9562`/`:sql_server`, and for a batch too large for the platform to address (a 32-bit
156
+ target such as ruby.wasm). The SQL Server conversions' one refusal (a value of the wrong
157
+ version) comes from the core, as an `ArgumentError` worded the same on both backends.
139
158
 
140
159
  ### Is the native core there?
141
160
 
@@ -143,6 +162,8 @@ address (a 32-bit target such as ruby.wasm).
143
162
  raising — for a consumer with a fallback of its own:
144
163
 
145
164
  ```ruby
165
+ require "securerandom"
166
+
146
167
  id = HyperUuid.available? ? HyperUuid.new_v7.to_s : SecureRandom.uuid
147
168
  ```
148
169
 
@@ -155,9 +176,9 @@ looked for, or `HyperUuid::NativePlatform::UnsupportedPlatformError` naming the
155
176
 
156
177
  ## Why not `SecureRandom.uuid`?
157
178
 
158
- `SecureRandom.uuid` gives you a random v4 UUID and `SecureRandom.uuid_v7` a v7 (Ruby 3.3 and later, so every Ruby this gem supports). Ruby's stdlib has no v5 or v6, nothing to read a timestamp back out, no batch form, and no SQL Server ordering. If you need more than that, the choice is really "which gem":
179
+ `SecureRandom.uuid` gives you a random v4 UUID and `SecureRandom.uuid_v7` a v7 (Ruby 3.3 and later, so every Ruby this gem supports). Ruby's stdlib has no v5, nothing to read a timestamp back out, no batch form, and no SQL Server ordering. If you need more than that, the choice is really "which gem":
159
180
 
160
- 1. **Full RFC 9562 coverage, one gem, zero extra dependency.** v4/v5/v6/v7 plus batch generation plus `Nil`/`Max`, and nothing added to your `Gemfile.lock` beyond `Fiddle` — Ruby's own bundled FFI layer, not a third-party C extension to compile — and on the precompiled platform gems not even that.
181
+ 1. **The RFC 9562 versions new systems use, one gem, zero extra dependency.** v4/v5/v7 plus batch generation plus `Nil`/`Max`, and nothing added to your `Gemfile.lock` beyond `Fiddle` — Ruby's own bundled FFI layer, not a third-party C extension to compile — and on the precompiled platform gems not even that.
161
182
  2. **No native-extension compile step.** Third-party UUID gems that go beyond v4 are typically pure Ruby or wrap a C extension compiled at install time; this gem ships its fast path as a prebuilt platform-gem extension and its fallback as a `dlopen`ed prebuilt library — either way, the gem itself compiles nothing at install time ([Install](#install) has the one caveat, which is Fiddle's own).
162
183
  3. **Batch generation.** `new_v7_batch(1000)` shares one timestamp capture, one random-bytes fetch, and one counter reservation across the whole batch instead of paying per-item overhead a thousand times over.
163
184
  4. **Cross-language consistency.** The same Rust core mints v5 namespace UUIDs for Python, Go, C#, and every other binding in this repo — verified in CI to match Python's own `uuid.uuid5` byte-for-byte. If your system isn't Ruby-only, no Ruby-only gem can offer that.
@@ -166,7 +187,7 @@ The honest trade-off: this gem is native code, not pure Ruby — a precompiled e
166
187
 
167
188
  ## Bulk generation into bytes
168
189
 
169
- `new_v6_batch_bytes` and `new_v7_batch_bytes` return the batch as one binary `String` of raw RFC 9562-ordered bytes — 16 per UUID — instead of an Array of `Uuid` objects:
190
+ `new_v7_batch_bytes` returns the batch as one binary `String` of raw RFC 9562-ordered bytes — 16 per UUID — instead of an Array of `Uuid` objects:
170
191
 
171
192
  ```ruby
172
193
  bytes = HyperUuid.new_v7_batch_bytes(1000)
@@ -191,9 +212,7 @@ Real numbers, `benchmark-ips` on Ruby 4.0.7, linux-x64 on an Intel Core i9-11900
191
212
  |---|---:|---:|
192
213
  | `SecureRandom.uuid` | 943,917 | baseline |
193
214
  | `HyperUuid.new_v4` | 5,236,000 | **5.5x faster** |
194
- | `HyperUuid.new_v6` (explicit ms) | 3,811,000 | **4.0x faster** |
195
215
  | `HyperUuid.new_v7` (explicit ms) | 3,785,000 | **4.0x faster** |
196
- | `HyperUuid.new_v6` (current time) | 3,677,000 | **3.9x faster** |
197
216
  | `HyperUuid.new_v7` (current time) | 3,574,000 | **3.8x faster** |
198
217
  | `HyperUuid.new_v5` | 1,942,000 | 2.1x faster |
199
218
 
@@ -201,18 +220,16 @@ A `HyperUuid.new_v4` — real entropy, correct version/variant bits, minted by t
201
220
 
202
221
  The "current time" rows deserve a footnote, because they will not look like this everywhere. Here they land beside the explicit-ms rows: the only difference between the two is one `Process.clock_gettime(CLOCK_REALTIME)` wall-clock read, and on this machine that read is too cheap to see. On a machine with a slow clock it is the whole story — where a virtualized clock defeats the vDSO fast path the same read costs ~1µs, which puts both current-time rows at parity with `SecureRandom.uuid` while the explicit-ms rows stay well ahead of it. `SecureRandom.uuid` never reads a clock — random v4 is the only thing it does. If your clock is slow and you are minting many, read it once and pass the timestamp, or use the batch doors.
203
222
 
204
- The Fiddle fallback (`HYPERUUID_PURE=1`, and any platform without a prebuilt extension) keeps its own diet — a reused thread-local scratch buffer instead of two GC-finalizer-registering mallocs per call, zero-copy `String` passes for read-only inputs, an unsynchronized fast path past the load mutex — landing at 1.16x slower than `SecureRandom.uuid` for v4 (1.24 µs against 1.07 µs in its own run) and about 1.3x slower for v6/v7, with the same structural story as before: `Fiddle`'s interpreted marshalling is the floor, and the batch doors are how you amortize it.
223
+ The Fiddle fallback (`HYPERUUID_PURE=1`, and any platform without a prebuilt extension) keeps its own diet — a reused thread-local scratch buffer instead of two GC-finalizer-registering mallocs per call, zero-copy `String` passes for read-only inputs, an unsynchronized fast path past the load mutex — landing at 1.16x slower than `SecureRandom.uuid` for v4 (1.24 µs against 1.07 µs in its own run) and about 1.3x slower for v7, with the same structural story as before: `Fiddle`'s interpreted marshalling is the floor, and the batch doors are how you amortize it.
205
224
 
206
225
  Batch generation still amortizes per-call cost on both backends — one native call for the whole batch:
207
226
 
208
227
  | Call | i/s (Magnus backend) |
209
228
  |---|---:|
210
- | `new_v6` × 1000 (individual) | 3,761 |
211
- | `new_v6_batch(1000)` | 4,857 (**1.3x**) |
212
229
  | `new_v7` × 1000 (individual) | 3,742 |
213
230
  | `new_v7_batch(1000)` | 4,850 (**1.3x**) |
214
231
 
215
- The multiplier is small on this backend for the best reason available: the individual calls are cheap, so there is little waste left to amortize, and what `new_v7_batch` spends its 206 µs on is building a thousand `Uuid` objects — the byte form above does the same native work in 5 µs. On the Fiddle backend, where each call costs 1.4 µs, the same batch is 6.7x the loop. If you need v5/v6/v7, need many at once, or need this Ruby service's IDs to agree byte-for-byte with a Go or Python service's, that's what this gem is for — and now it's the fast option too, not just the capable one.
232
+ The multiplier is small on this backend for the best reason available: the individual calls are cheap, so there is little waste left to amortize, and what `new_v7_batch` spends its 206 µs on is building a thousand `Uuid` objects — the byte form above does the same native work in 5 µs. On the Fiddle backend, where each call costs 1.4 µs, the same batch is 6.7x the loop. If you need v5/v7, need many at once, or need this Ruby service's IDs to agree byte-for-byte with a Go or Python service's, that's what this gem is for — and now it's the fast option too, not just the capable one.
216
233
 
217
234
  ## Backends
218
235
 
@@ -375,7 +392,7 @@ bundle exec rbwasm build --ruby-version 4.0 -o ruby.wasm
375
392
  Load `ruby.wasm` with [`@ruby/wasm-wasi`](https://www.npmjs.com/package/@ruby/wasm-wasi)
376
393
  (`DefaultRubyVM` in a browser, `RubyVM.instantiateModule` under Node), then
377
394
  `require "/bundle/setup"` and `require "hyperuuid"` as anywhere else. `HyperUuid::BACKEND` is
378
- `:native`: every door, the batch and raw-bytes forms, and `new_v6`/`new_v7` with no argument,
395
+ `:native`: every door, the batch and raw-bytes forms, and `new_v7` with no argument,
379
396
  which read the clock through WASI.
380
397
 
381
398
  - **Ruby 3.4 and 4.0**, one archive each, the same minors the platform gems cover, picked by
@@ -1,12 +1,11 @@
1
1
  module HyperUuid
2
2
  # Raised when the operating system's random source fails while a UUID is being minted —
3
- # the one way `new_v4`, `new_v6`, `new_v7` and their batch forms can fail once their
3
+ # the one way `new_v4`, `new_v7` and its batch forms can fail once their
4
4
  # arguments are valid. Carries the same message on every backend.
5
5
  class RandomSourceError < StandardError; end
6
6
 
7
- # Raised when a timestamp cannot be embedded in the UUID being minted: a version 7 value
8
- # past RFC 9562's 48-bit millisecond field, a version 6 value past its 60-bit field, or
9
- # any negative one (a `Time` before the Unix epoch included). Carries the same message on
10
- # every backend.
7
+ # Raised when a timestamp cannot be embedded in the version 7 UUID being minted: one past
8
+ # RFC 9562's 48-bit millisecond field, or a negative one (a `Time` before the Unix epoch
9
+ # included). Carries the same message on every backend.
11
10
  class TimestampOutOfRangeError < StandardError; end
12
11
  end
@@ -22,10 +22,9 @@ module HyperUuid
22
22
  RandomSourceError = HyperUuid::RandomSourceError
23
23
  TimestampOutOfRangeError = HyperUuid::TimestampOutOfRangeError
24
24
 
25
- # One copy of each out-of-range message for the Fiddle backend and for the
26
- # doors' own range check in hyperuuid.rb; the Magnus extension (rust/src/ruby_ext.rs)
27
- # carries the same text, and spec/native_backend_spec.rb pins that the two agree.
28
- V6_TIMESTAMP_OUT_OF_RANGE = "unix_millis does not fit the 60-bit v6 timestamp field"
25
+ # One copy of the out-of-range message for the Fiddle backend and for the doors' own
26
+ # range check in hyperuuid.rb; the Magnus extension (rust/src/ruby_ext.rs) carries the
27
+ # same text, and spec/native_backend_spec.rb pins that the two agree.
29
28
  V7_TIMESTAMP_OUT_OF_RANGE = "unix_millis must fit within the RFC 9562 48-bit field"
30
29
 
31
30
  # The two batch-size refusals, shared the same way: a version 7 batch past the 26-bit
@@ -36,6 +35,11 @@ module HyperUuid
36
35
  V7_BATCH_TOO_LARGE = "a single version 7 batch takes at most 67108864 UUIDs (the 26-bit counter space)"
37
36
  BATCH_TOO_LARGE_TO_ADDRESS = "the batch is too large to address on this platform"
38
37
 
38
+ # The SQL Server order conversions' one refusal, in the core's own words (the Magnus
39
+ # extension raises its SqlOrderError's text): a value that isn't a UUID of the
40
+ # conversion's version (code 5), an ArgumentError. Every version 7 converts.
41
+ SQL_ORDER_WRONG_VERSION = "the value is not a UUID of the version this conversion takes"
42
+
39
43
  # The core's v7::MAX_BATCH, which hyperuuid.rb publishes as HyperUuid::MAX_V7_BATCH.
40
44
  MAX_V7_BATCH = 1 << 26
41
45
 
@@ -65,33 +69,6 @@ module HyperUuid
65
69
  out[0, 16]
66
70
  end
67
71
 
68
- def new_v6(unix_millis)
69
- out = scratch
70
- rc = functions[:new_v6].call(unix_millis, out)
71
- case rc
72
- when 0 then out[0, 16]
73
- when 2 then raise TimestampOutOfRangeError, V6_TIMESTAMP_OUT_OF_RANGE
74
- else raise random_source_failure("uuid_new_v6")
75
- end
76
- end
77
-
78
- def v6_unix_millis(bytes)
79
- functions[:v6_unix_millis].call(bytes)
80
- end
81
-
82
- def new_v6_batch(count, unix_millis)
83
- return "" if count.zero?
84
-
85
- out = buffer(count * 16)
86
- rc = functions[:new_v6_batch].call(unix_millis, count, out)
87
- case rc
88
- when 0 then out[0, count * 16]
89
- when 2 then raise TimestampOutOfRangeError, V6_TIMESTAMP_OUT_OF_RANGE
90
- when 3 then raise ArgumentError, BATCH_TOO_LARGE_TO_ADDRESS
91
- else raise random_source_failure("uuid_new_v6_batch")
92
- end
93
- end
94
-
95
72
  def new_v7(unix_millis)
96
73
  out = scratch
97
74
  rc = functions[:new_v7].call(unix_millis, out)
@@ -140,19 +117,16 @@ module HyperUuid
140
117
  functions[:is_rfc].call(bytes, version, layout) == 1
141
118
  end
142
119
 
143
- # The Unix ms of an RFC 9562 version 6 or 7 UUID held in +layout+'s order, or nil for
144
- # anything else — another version, another variant. One core call; the version it
145
- # answers with is not needed above Runtime, so it is dropped here.
120
+ # The Unix ms of an RFC 9562 version 7 UUID held in +layout+'s order, or nil for
121
+ # anything else — another version (a version 6 or 1 included), another variant. One
122
+ # core call; the version it answers with is not needed above Runtime, so it is dropped
123
+ # here.
146
124
  def get_timestamp(bytes, layout)
147
125
  out = millis_scratch
148
126
  version = functions[:get_timestamp].call(bytes, layout, out)
149
127
  version.zero? ? nil : out[0, 8].unpack1("Q")
150
128
  end
151
129
 
152
- def v6_unix_millis_in(bytes, layout)
153
- functions[:v6_unix_millis_in].call(bytes, layout)
154
- end
155
-
156
130
  def v7_unix_millis_in(bytes, layout)
157
131
  functions[:v7_unix_millis_in].call(bytes, layout)
158
132
  end
@@ -165,14 +139,6 @@ module HyperUuid
165
139
  rewrite(:v7_to_rfc_order, bytes)
166
140
  end
167
141
 
168
- def v6_to_sql_order(bytes)
169
- rewrite(:v6_to_sql_order, bytes)
170
- end
171
-
172
- def v6_to_rfc_order(bytes)
173
- rewrite(:v6_to_rfc_order, bytes)
174
- end
175
-
176
142
  # The loaded core's version word, major << 16 | minor << 8 | patch, straight from the
177
143
  # library's zero-argument hyperuuid_version export. HyperUuid.native_version unpacks
178
144
  # it; like every method here, each backend replaces this one in place.
@@ -253,12 +219,16 @@ module HyperUuid
253
219
  end
254
220
 
255
221
  # The in-place byte-order rewrites are the one shape that must copy in: the native
256
- # call genuinely mutates the buffer, and the input String is frozen.
222
+ # call genuinely mutates the buffer, and the input String is frozen. A refusal leaves
223
+ # the buffer as it was and raises what the Magnus extension raises for it.
257
224
  def rewrite(symbol, bytes)
258
225
  buf = scratch
259
226
  buf[0, 16] = bytes
260
- functions[symbol].call(buf)
261
- buf[0, 16]
227
+ case (rc = functions[symbol].call(buf))
228
+ when 0 then buf[0, 16]
229
+ when 5 then raise ArgumentError, SQL_ORDER_WRONG_VERSION
230
+ else raise "uuid_#{symbol}: unexpected return code #{rc}"
231
+ end
262
232
  end
263
233
 
264
234
  # Loaded lazily and exactly once, mirroring Java's class initializer / Swift's lazy
@@ -281,21 +251,6 @@ module HyperUuid
281
251
  [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_UINT32_T, Fiddle::TYPE_VOIDP],
282
252
  Fiddle::TYPE_INT
283
253
  ),
284
- new_v6: Fiddle::Function.new(
285
- handle["uuid_new_v6"],
286
- [Fiddle::TYPE_UINT64_T, Fiddle::TYPE_VOIDP],
287
- Fiddle::TYPE_INT
288
- ),
289
- v6_unix_millis: Fiddle::Function.new(
290
- handle["uuid_v6_unix_millis"],
291
- [Fiddle::TYPE_VOIDP],
292
- Fiddle::TYPE_UINT64_T
293
- ),
294
- new_v6_batch: Fiddle::Function.new(
295
- handle["uuid_new_v6_batch"],
296
- [Fiddle::TYPE_UINT64_T, Fiddle::TYPE_UINT32_T, Fiddle::TYPE_VOIDP],
297
- Fiddle::TYPE_INT
298
- ),
299
254
  new_v7: Fiddle::Function.new(
300
255
  handle["uuid_new_v7"],
301
256
  [Fiddle::TYPE_UINT64_T, Fiddle::TYPE_VOIDP],
@@ -314,22 +269,12 @@ module HyperUuid
314
269
  v7_to_sql_order: Fiddle::Function.new(
315
270
  handle["uuid_v7_to_sql_order"],
316
271
  [Fiddle::TYPE_VOIDP],
317
- Fiddle::TYPE_VOID
272
+ Fiddle::TYPE_INT
318
273
  ),
319
274
  v7_to_rfc_order: Fiddle::Function.new(
320
275
  handle["uuid_v7_to_rfc_order"],
321
276
  [Fiddle::TYPE_VOIDP],
322
- Fiddle::TYPE_VOID
323
- ),
324
- v6_to_sql_order: Fiddle::Function.new(
325
- handle["uuid_v6_to_sql_order"],
326
- [Fiddle::TYPE_VOIDP],
327
- Fiddle::TYPE_VOID
328
- ),
329
- v6_to_rfc_order: Fiddle::Function.new(
330
- handle["uuid_v6_to_rfc_order"],
331
- [Fiddle::TYPE_VOIDP],
332
- Fiddle::TYPE_VOID
277
+ Fiddle::TYPE_INT
333
278
  ),
334
279
  version: Fiddle::Function.new(
335
280
  handle["uuid_version"],
@@ -347,11 +292,6 @@ module HyperUuid
347
292
  [Fiddle::TYPE_VOIDP, Fiddle::TYPE_UINT32_T, Fiddle::TYPE_VOIDP],
348
293
  Fiddle::TYPE_UINT32_T
349
294
  ),
350
- v6_unix_millis_in: Fiddle::Function.new(
351
- handle["uuid_v6_unix_millis_in"],
352
- [Fiddle::TYPE_VOIDP, Fiddle::TYPE_UINT32_T],
353
- Fiddle::TYPE_UINT64_T
354
- ),
355
295
  v7_unix_millis_in: Fiddle::Function.new(
356
296
  handle["uuid_v7_unix_millis_in"],
357
297
  [Fiddle::TYPE_VOIDP, Fiddle::TYPE_UINT32_T],
@@ -40,8 +40,8 @@ module HyperUuid
40
40
 
41
41
  # The byte orders a Uuid can hold, as the +layout:+ keyword of #version, #rfc? and
42
42
  # #timestamp takes them, mapped to the native core's layout codes. +:rfc9562+ is RFC 9562
43
- # order, what every other method here takes and returns. +:sql_server+ is the order
44
- # #to_sql_order returns, defined for versions 6 and 7 only.
43
+ # order, what every other method here takes and returns. +:sql_server+ is the layout
44
+ # #to_sql_order returns, a version 8 that holds a converted version 7.
45
45
  LAYOUTS = { rfc9562: 1, sql_server: 2 }.freeze
46
46
 
47
47
  # The Symbols #variant returns, indexed by the native core's variant code (1-4; 0 is
@@ -66,10 +66,11 @@ module HyperUuid
66
66
  #
67
67
  # In +:rfc9562+ (the default) it is the version nibble (bits 48-51, the high nibble of
68
68
  # octet 6), 0 through 15: Nil reads as 0 and Max as 15. It says nothing about the variant;
69
- # #rfc? checks both. In +:sql_server+, the order #to_sql_order returns, it is 6 or 7 for a
70
- # SQL-ordered version 6 or 7 UUID and 0 when the bytes don't form one, since no other
71
- # version has a SQL Server order. It reads the bytes as given: an RFC-ordered value can
72
- # happen to form a SQL-ordered v6 or v7, so the layout is the caller's to know.
69
+ # #rfc? checks both. In +:sql_server+, the layout #to_sql_order returns, it is the version
70
+ # the value was converted from, 7, when the bytes carry the layout's version 8 and RFC
71
+ # variant, and 0 when they don't. It reads the bytes as given: an
72
+ # RFC-ordered value can happen to carry them (a random v4 does one time in 16), so the
73
+ # layout is the caller's to know.
73
74
  #
74
75
  # With the default layout, the value is read as RFC 9562 order. A SQL-ordered value — from
75
76
  # #to_sql_order, or a +uniqueidentifier+ read back from SQL Server — needs
@@ -86,19 +87,19 @@ module HyperUuid
86
87
  # versions), +:ncs+ (+0xxx+, Nil among them), +:microsoft+ (+110x+) or +:future+ (+111x+,
87
88
  # Max among them).
88
89
  #
89
- # RFC 9562 order only. There is no layout form, because in SQL Server order the variant
90
- # sits at a different byte for each version, so don't hand it a SQL-ordered value or a
91
- # +uniqueidentifier+ read back from SQL Server. To validate one of those, use
92
- # <tt>rfc?(version, layout: :sql_server)</tt>, which checks the variant where that version
93
- # puts it.
90
+ # RFC 9562 order only, with no layout form. The SQL Server layout keeps the variant in the
91
+ # same place, so a SQL-ordered value reads +:rfc9562+ here, but that says nothing about
92
+ # which version it holds. To validate one, or a +uniqueidentifier+ read back from SQL
93
+ # Server, use <tt>rfc?(7, layout: :sql_server)</tt>, which checks the layout's version 8
94
+ # and variant.
94
95
  def variant
95
96
  VARIANTS.fetch(Runtime.variant(bytes))
96
97
  end
97
98
 
98
99
  # Whether this is an RFC 9562 UUID of version +version+, read in +layout+'s byte order —
99
100
  # the RFC 9562 variant and that version together, the guard to run before trusting a
100
- # value's version-specific fields. In +:sql_server+ only 6 and 7 can be true. A version
101
- # outside 0-15 is simply false.
101
+ # value's version-specific fields. In +:sql_server+ +version+ is the version the value was
102
+ # converted from, so only 7 can be true. A version outside 0-15 is simply false.
102
103
  #
103
104
  # With the default layout, the value is read as RFC 9562 order. A SQL-ordered value — from
104
105
  # #to_sql_order, or a +uniqueidentifier+ read back from SQL Server — needs
@@ -121,17 +122,19 @@ module HyperUuid
121
122
  end
122
123
  alias_method :to_str, :to_s
123
124
 
124
- # The UTC timestamp embedded in an RFC 9562 version 6 or 7 UUID, held in +layout+'s byte
125
- # order: +:rfc9562+ (the default), or +:sql_server+ for a value #to_sql_order returned. One
126
- # native call that checks the variant as well as the version: a 6 or 7 in the version
127
- # nibble under any variant but +:rfc9562+ is not a version 6 or 7 UUID, so it has no
128
- # timestamp (#rfc? is the same check on its own).
125
+ # The UTC timestamp embedded in an RFC 9562 version 7 UUID, held in +layout+'s byte order:
126
+ # +:rfc9562+ (the default), or +:sql_server+ for a value #to_sql_order returned (the
127
+ # timestamp of the version 7 it was converted from). One native call that checks the
128
+ # variant as well as the version: a 7 in the version nibble under any variant but
129
+ # +:rfc9562+ is not a version 7 UUID, so it has no timestamp (#rfc? is the same check on
130
+ # its own). Every other version has none either, a version 6 or 1 from another generator
131
+ # included.
129
132
  #
130
- # Raises by default for anything that isn't an RFC 9562 version 6 or 7 UUID; pass
133
+ # Raises by default for anything that isn't an RFC 9562 version 7 UUID; pass
131
134
  # `raise_on_mismatch: false` to get `nil` back instead — for a caller that doesn't already
132
135
  # know (or want to separately check) whether this UUID is time-based.
133
136
  #
134
- # @raise [ArgumentError] if this isn't an RFC 9562 version 6 or 7 UUID in +layout+ (unless
137
+ # @raise [ArgumentError] if this isn't an RFC 9562 version 7 UUID in +layout+ (unless
135
138
  # +raise_on_mismatch+ is false), or +layout+ isn't one of LAYOUTS' keys.
136
139
  def timestamp(raise_on_mismatch: true, layout: :rfc9562)
137
140
  millis = Runtime.get_timestamp(bytes, layout_code(layout))
@@ -141,63 +144,39 @@ module HyperUuid
141
144
  raise ArgumentError, timestamp_mismatch(layout)
142
145
  end
143
146
 
144
- # Converts an RFC 9562-ordered version 6 or 7 UUID to the byte order SQL Server's
145
- # `uniqueidentifier` needs on the wire to sort by creation order. Dispatches on `version`
146
- # the same way #timestamp does.
147
+ # Converts an RFC 9562 version 7 UUID to the layout SQL Server's `uniqueidentifier` needs
148
+ # on the wire to sort by creation order: an RFC 9562 version 8 UUID (§5.8, the version for
149
+ # custom layouts, where §6.11 sends a byte order other than the network one).
147
150
  #
148
151
  # `System.Data.SqlTypes.SqlGuid` comparison — and therefore T-SQL `ORDER BY` on a
149
152
  # `uniqueidentifier` column — doesn't compare a GUID's 16 bytes left to right; it uses a
150
153
  # fixed, non-sequential byte significance order (octets 10,11,12,13,14,15,8,9,6,7,4,5,
151
- # 0,1,2,3, most significant first). Computed once in the native Rust core and verified
152
- # there (and independently, against the real SqlGuid comparator, in this project's C#
153
- # test suite); this binding calls the same native functions rather than reimplementing
154
- # the byte math.
155
- #
156
- # For v7, this moves the timestamp and counter — the two fields that determine creation
157
- # order — into that comparison's most-significant bytes, and moves the trailing entropy,
158
- # which carries no ordering information, into the least-significant ones as one intact
159
- # block. For v6, which has no monotonic counter the way v7 does, the only field that
160
- # determines creation order is the 60-bit timestamp itself, so that moves into the most
161
- # significant bytes instead, with `clock_seq`/`node` (independently random per call, not
162
- # a counter, so no ordering value either way) relocated into the rest. v6's much simpler
163
- # byte layout needs no bit-level repacking to do this — just whole-octet-group
164
- # relocation — unlike v7's, and its version/variant land at different sql-order offsets
165
- # as a result (octet 8's top nibble / octet 6's top two bits, not 7/8).
166
- #
167
- # **v6-specific caveat, unlike v7:** two version 6 UUIDs minted at the same millisecond
168
- # have identical timestamp bits, so they aren't guaranteed to sort in creation order any
169
- # more than plain RFC order already does — a pre-existing RFC 9562 v6 limitation, not one
170
- # this transform introduces.
171
- #
172
- # Meaningful only for a genuine version 6 or 7 UUID.
154
+ # 0,1,2,3, most significant first). The layout writes the version nibble 8 at octet 7 and
155
+ # the RFC variant at octet 8, where SQL Server's text form and a .NET `Guid` show them, and
156
+ # fills the other 122 bits in that comparison's order: the 48-bit millisecond timestamp,
157
+ # the 26-bit counter and the 48 random bits as one intact block. A version 7 has exactly
158
+ # those 122 free bits, so every one converts, and #from_sql_order gives back the exact
159
+ # original. Computed once in the native Rust core and verified there (and independently,
160
+ # against the real SqlGuid comparator, in this project's C# test suite); this binding calls
161
+ # the same native functions rather than reimplementing the bit math.
162
+ #
163
+ # @raise [ArgumentError] if this isn't an RFC 9562 version 7 UUID.
173
164
  def to_sql_order
174
- case version
175
- when 7 then self.class.new(Runtime.v7_to_sql_order(bytes), true)
176
- when 6 then self.class.new(Runtime.v6_to_sql_order(bytes), true)
177
- else raise ArgumentError, "to_sql_order is only defined for version 6 or 7 UUIDs, got version #{version}"
178
- end
165
+ self.class.new(Runtime.v7_to_sql_order(bytes), true)
179
166
  end
180
167
 
181
- # Inverse of #to_sql_order — converts a SQL-Server-ordered version 6 or 7 UUID back to
182
- # RFC 9562 order.
183
- #
184
- # A SQL-ordered value's version sits at a different octet depending on which version
185
- # produced it, so this asks the native core which one it is — `version(layout:
186
- # :sql_server)`, which checks each version's nibble together with its variant bits and
187
- # so never mistakes a v6 whose random `clock_seq` byte happens to read as a v7 nibble.
168
+ # Inverse of #to_sql_order — converts a SQL-Server-ordered value back to the RFC 9562
169
+ # version 7 UUID it was converted from, exactly. Refuses a value without the layout's
170
+ # version 8 and RFC variant, an RFC-ordered version 7 among them.
188
171
  #
189
172
  # The layout is the caller's to know: bytes alone cannot say which order they are in. A
190
- # value that was never SQL-ordered can still read as one, and is then converted rather
191
- # than refused (a random RFC-ordered v4 reads as a SQL-ordered v7 one time in 16, since its
173
+ # value that was never SQL-ordered can still carry the layout's 8 and variant, and is then
174
+ # converted rather than refused (a random RFC-ordered v4 does one time in 16, since its
192
175
  # octet 8 already carries the RFC variant and its octet 7 is random).
193
176
  #
194
- # @raise [ArgumentError] if this isn't a SQL-ordered version 6 or 7 UUID.
177
+ # @raise [ArgumentError] if this isn't a SQL-ordered value converted from version 7.
195
178
  def from_sql_order
196
- case Runtime.version(bytes, LAYOUTS[:sql_server])
197
- when 6 then self.class.new(Runtime.v6_to_rfc_order(bytes), true)
198
- when 7 then self.class.new(Runtime.v7_to_rfc_order(bytes), true)
199
- else raise ArgumentError, "from_sql_order: not a recognized version 6 or 7 SQL-ordered UUID"
200
- end
179
+ self.class.new(Runtime.v7_to_rfc_order(bytes), true)
201
180
  end
202
181
 
203
182
  # Whether +other+ wraps the same 16 raw bytes.
@@ -227,11 +206,11 @@ module HyperUuid
227
206
 
228
207
  # Why #timestamp found none: the message is worked out only once it is going to be raised.
229
208
  def timestamp_mismatch(layout)
230
- return "timestamp: not a SQL-ordered version 6 or 7 UUID" if layout == :sql_server
209
+ return "timestamp: not a SQL-ordered version 7 UUID" if layout == :sql_server
231
210
 
232
211
  found = version
233
- message = "timestamp is only defined for version 6 or 7 UUIDs, got version #{found}"
234
- [6, 7].include?(found) ? "#{message} with the #{variant.inspect} variant" : message
212
+ message = "timestamp is only defined for version 7 UUIDs, got version #{found}"
213
+ found == 7 ? "#{message} with the #{variant.inspect} variant" : message
235
214
  end
236
215
 
237
216
  # The native core's code for +layout+, the one place a layout is checked: an unknown one
data/lib/hyperuuid.rb CHANGED
@@ -4,21 +4,22 @@ require_relative "hyperuuid/namespaces"
4
4
  require_relative "hyperuuid/native_platform"
5
5
  require_relative "hyperuuid/runtime"
6
6
 
7
- # RFC 9562 UUID v4 (random), v5 (deterministic), v6 and v7 (time-sortable) generation from one
8
- # Rust core, reached through whichever of two backends this install can load: the core
9
- # linked straight into a Magnus native extension (shipped precompiled in the platform gems),
10
- # or the native libhyperuuid shared library called through Fiddle (the universal gem bundles
11
- # one per supported platform, picked by HyperUuid::NativePlatform). No runtime bridge in
12
- # either, and nothing compiled at install time. HyperUuid::BACKEND names the one that loaded; the selection logic
13
- # is at the bottom of this file.
7
+ # RFC 9562 UUID v4 (random), v5 (deterministic) and v7 (time-sortable) generation, with a
8
+ # version 8 SQL Server order for v7, from one Rust core, reached through whichever of two
9
+ # backends this install can load: the core linked straight into a Magnus native extension
10
+ # (shipped precompiled in the platform gems), or the native libhyperuuid shared library
11
+ # called through Fiddle (the universal gem bundles one per supported platform, picked by
12
+ # HyperUuid::NativePlatform). No runtime bridge in either, and nothing compiled at install
13
+ # time. HyperUuid::BACKEND names the one that loaded; the selection logic is at the bottom of
14
+ # this file.
14
15
  #
15
16
  # The doors below — with Uuid, Namespaces and the two exception classes — are the public
16
17
  # surface, and are shared by every backend, argument validation included: a caller bug raises
17
18
  # the same error whichever backend is live.
18
19
  module HyperUuid
19
- # This gem's own version — distinct from the RFC 9562 UUID *versions* (v4/v5/v6/v7) the
20
- # rest of this module generates.
21
- VERSION = "0.8.0"
20
+ # This gem's own version — distinct from the RFC 9562 UUID *versions* (v4/v5/v7) the rest
21
+ # of this module generates.
22
+ VERSION = "0.9.0"
22
23
 
23
24
  # The widest batch count, and the longest v5 name in bytes, the native ABI carries (a u32).
24
25
  # (The millisecond count is a u64; unix_millis_from refuses anything wider.)
@@ -27,8 +28,7 @@ module HyperUuid
27
28
 
28
29
  # The most UUIDs one version 7 batch (#new_v7_batch, #new_v7_batch_bytes) mints:
29
30
  # 67,108,864, the size of the 26-bit counter that orders UUIDs within a millisecond. A larger
30
- # count is refused with ArgumentError before anything is allocated. Version 6 has no counter
31
- # and no such limit.
31
+ # count is refused with ArgumentError before anything is allocated.
32
32
  #
33
33
  # Every batch up to this size is in strictly increasing order: the counter is one
34
34
  # process-wide sequence, so a batch can straddle the point where it wraps back to 0, and the
@@ -68,7 +68,7 @@ module HyperUuid
68
68
  # Converts +value+ to a Unix-epoch millisecond integer: +nil+ becomes the current time, a
69
69
  # +Time+ is converted exactly (via its own Rational seconds, avoiding float rounding), and
70
70
  # an Integer millisecond count passes through unchanged. Shared by every
71
- # `new_v6`/`new_v7`/batch door below so a caller can pass either a `Time` or a raw
71
+ # `new_v7`/batch door below so a caller can pass either a `Time` or a raw
72
72
  # millisecond count interchangeably.
73
73
  #
74
74
  # This is also where a caller bug is caught once, for every backend: anything else is a
@@ -102,29 +102,6 @@ module HyperUuid
102
102
  count
103
103
  end
104
104
 
105
- # Creates a time-sortable UUID version 6 (RFC 9562 §5.6), a field-compatible reordering of
106
- # version 1 for better sort/index locality. Defaults to the current time; pass an explicit
107
- # `Time` or Unix-epoch millisecond integer to embed a specific time instead. `clock_seq` and
108
- # `node` are randomly generated on every call — unlike version 7, there is no monotonic
109
- # counter, so calls within the same millisecond are not guaranteed to sort in creation order.
110
- #
111
- # @raise [TimestampOutOfRangeError] if the time is negative or past the 60-bit v6 field.
112
- # @raise [TypeError] if +unix_millis+ isn't a Time, an Integer or nil.
113
- def self.new_v6(unix_millis = nil)
114
- Uuid.new(Runtime.new_v6(unix_millis_from(unix_millis, Runtime::V6_TIMESTAMP_OUT_OF_RANGE)), true)
115
- end
116
-
117
- # Creates `count` time-sortable version 6 UUIDs sharing one timestamp capture — one FFI call
118
- # and one random-bytes fetch instead of `count` of each. Defaults to the current time; pass
119
- # an explicit `Time` or Unix-epoch millisecond integer to embed a specific time instead.
120
- #
121
- # @raise [TypeError, ArgumentError] if +count+ isn't an Integer between 0 and 2**32 - 1.
122
- # @raise [TimestampOutOfRangeError] if the time is negative or past the 60-bit v6 field.
123
- def self.new_v6_batch(count, unix_millis = nil)
124
- bytes = new_v6_batch_bytes(count, unix_millis)
125
- Array.new(count) { |i| Uuid.new(bytes[i * 16, 16], true) }
126
- end
127
-
128
105
  # Creates a time-sortable UUID version 7 (RFC 9562 §6.2). Defaults to the current time; pass
129
106
  # an explicit `Time` or Unix-epoch millisecond integer (non-negative, fitting in 48 bits) to
130
107
  # embed a specific time instead.
@@ -184,21 +161,6 @@ module HyperUuid
184
161
  Runtime.new_v7_batch(count, unix_millis_from(unix_millis, Runtime::V7_TIMESTAMP_OUT_OF_RANGE))
185
162
  end
186
163
 
187
- # Returns `count` version 6 UUIDs as one binary String of raw RFC 9562-ordered bytes,
188
- # 16 per UUID. The version 6 counterpart to #new_v7_batch_bytes, with the same rationale and
189
- # the same guidance about when it is the right call.
190
- #
191
- # clock_seq and node are independently random per item; unlike version 7 there is no
192
- # monotonic counter, so items minted in the same millisecond are not guaranteed to sort in
193
- # creation order.
194
- #
195
- # @raise [TypeError, ArgumentError] if +count+ isn't an Integer between 0 and 2**32 - 1.
196
- # @raise [TimestampOutOfRangeError] if the time is negative or past the 60-bit v6 field.
197
- def self.new_v6_batch_bytes(count, unix_millis = nil)
198
- count = batch_count(count)
199
- Runtime.new_v6_batch(count, unix_millis_from(unix_millis, Runtime::V6_TIMESTAMP_OUT_OF_RANGE))
200
- end
201
-
202
164
  # The version of the native core actually loaded, as "major.minor.patch" — read from the
203
165
  # library itself (its `hyperuuid_version` export), not from this gem, so a consumer can
204
166
  # prove the core behind the doors is the one this binding was built against (and name the
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hyperuuid-wasm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Buvinghausen