hyperuuid 0.0.1-aarch64-linux
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 +7 -0
- data/README.md +99 -0
- data/lib/hyperuuid/namespaces.rb +13 -0
- data/lib/hyperuuid/native/README.md +6 -0
- data/lib/hyperuuid/native/linux-arm64/libhyperuuid.so +0 -0
- data/lib/hyperuuid/native/linux-x64/libhyperuuid.so +0 -0
- data/lib/hyperuuid/native/osx-arm64/libhyperuuid.dylib +0 -0
- data/lib/hyperuuid/native/osx-x64/libhyperuuid.dylib +0 -0
- data/lib/hyperuuid/native/win-arm64/hyperuuid.dll +0 -0
- data/lib/hyperuuid/native/win-x64/hyperuuid.dll +0 -0
- data/lib/hyperuuid/native_platform.rb +22 -0
- data/lib/hyperuuid/runtime.rb +204 -0
- data/lib/hyperuuid/uuid.rb +153 -0
- data/lib/hyperuuid.rb +89 -0
- data/lib/hyperuuid_native.so +0 -0
- metadata +113 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6c70194e95ed5934800364517f24e261ba9908845ab7aa792323f76e82ade00c
|
|
4
|
+
data.tar.gz: 2005f034b025bd8f5552ee3973c70175d59dc244b9f59c4e2fbadfbb2fd079b8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 54ae222a52354ce34e13480a081ba166abcdd9c57c4f493242e1a89917b6c969f9411bc5518833ba03e49ba2920d518981963ccdc5710a5d2715604a90b28438
|
|
7
|
+
data.tar.gz: b14687f14ec691c2a9f7e7abe4547801b9bc9bc9b02cf2b0bf67410f133ba8778455d334d1c6c93ad29b1083313bfa1eb560b7af14567599b86c8088b2f7a1e3
|
data/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# hyperuuid
|
|
2
|
+
|
|
3
|
+
**Ruby's own stdlib stops at `SecureRandom.uuid` — random v4, full stop. No v5, no v6, no v7. 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.**
|
|
4
|
+
|
|
5
|
+
RFC 9562 UUID v4 (random), v5 (deterministic), v6 and v7 (time-sortable) generation, with
|
|
6
|
+
two backends sharing one public surface. The fast path is a native extension built with
|
|
7
|
+
[Magnus](https://github.com/matsadler/magnus) — the Rust core linked directly into the Ruby
|
|
8
|
+
VM, auto-selected when loadable — which redefines the low-level `Runtime` methods in place
|
|
9
|
+
on require; everything above them (`Uuid`, the module doors, batch slicing) is shared
|
|
10
|
+
byte-for-byte between backends. The universal fallback calls the native `libhyperuuid`
|
|
11
|
+
shared library via [`Fiddle`](https://docs.ruby-lang.org/en/master/Fiddle.html) —
|
|
12
|
+
dlopen/dlsym plus a raw C-ABI call, no runtime bridge, nothing to compile on
|
|
13
|
+
`bundle install`. Set `HYPERUUID_PURE=1` to force the Fiddle backend;
|
|
14
|
+
`HyperUuid::BACKEND` reports which one is live. Bundles a native build for every supported
|
|
15
|
+
platform (linux/darwin/windows × x64/arm64) and picks the right one at runtime.
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require "hyperuuid"
|
|
19
|
+
|
|
20
|
+
id = HyperUuid.new_v4
|
|
21
|
+
id2 = HyperUuid.new_v5(HyperUuid::Namespaces::DNS, "example.com")
|
|
22
|
+
id3 = HyperUuid.new_v6
|
|
23
|
+
id4 = HyperUuid.new_v7
|
|
24
|
+
|
|
25
|
+
id4.timestamp # recover the embedded UTC Time
|
|
26
|
+
id4.to_sql_order # byte order SQL Server's uniqueidentifier needs to sort by creation order
|
|
27
|
+
|
|
28
|
+
# One native call, one random-bytes fetch, one counter reservation for the whole batch:
|
|
29
|
+
batch = HyperUuid.new_v7_batch(1000)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Returns `HyperUuid::Uuid`, a minimal value object (`#bytes`, `#to_s`, `#version`, `#variant`,
|
|
33
|
+
comparable/hashable) — this gem has no runtime dependency on the `uuid` gem.
|
|
34
|
+
`HyperUuid::Namespaces::DNS`/`URL`/`OID`/`X500` are RFC 9562 Section 6.6's well-known
|
|
35
|
+
namespaces. `#timestamp` recovers the embedded UTC `Time` from a version 6 or 7 UUID.
|
|
36
|
+
`#to_sql_order`/`#from_sql_order` convert a version 6 or 7 UUID to and from the byte order SQL
|
|
37
|
+
Server's `uniqueidentifier` needs on the wire to sort by creation order (`#to_sql_order`
|
|
38
|
+
dispatches on the UUID's own version, matching `#timestamp`'s convention) — computed once in
|
|
39
|
+
the native Rust core rather than reimplemented in Ruby, and verified there (and independently
|
|
40
|
+
against the real `System.Data.SqlTypes.SqlGuid` comparator in the C# binding's test suite).
|
|
41
|
+
Same-millisecond v6 UUIDs aren't guaranteed to sort correctly afterward — v6 has no counter,
|
|
42
|
+
so `clock_seq`/`node` (not the timestamp) decide ties, the same pre-existing RFC 9562 v6
|
|
43
|
+
limitation plain order already has. `#from_sql_order` figures out which version to invert by checking a byte position that's
|
|
44
|
+
provably collision-free between the two (see the method's own doc comment).
|
|
45
|
+
`HyperUuid::Uuid::NIL`/`MAX` are the RFC 9562 §5.9/§5.10 special-value UUIDs.
|
|
46
|
+
`HyperUuid.new_v6_batch(count)`/`new_v7_batch(count)` generate `count` UUIDs sharing one
|
|
47
|
+
timestamp capture and one native call, instead of `count` of each.
|
|
48
|
+
|
|
49
|
+
## Why not `SecureRandom.uuid`?
|
|
50
|
+
|
|
51
|
+
`SecureRandom.uuid` only ever gives you a random v4 UUID — Ruby's stdlib has no built-in v5, v6, or v7 at all. If you need more than that, the choice is really "which gem":
|
|
52
|
+
|
|
53
|
+
1. **Full RFC 9562 coverage, one gem, zero extra dependency.** v4/v5/v6/v7 plus batch generation plus `Nil`/`Max`, and the only thing this gem adds to your `Gemfile.lock` beyond `Fiddle` — which is Ruby's own bundled FFI layer, not a third-party C extension to compile.
|
|
54
|
+
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, nothing to compile on `bundle install`.
|
|
55
|
+
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.
|
|
56
|
+
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.
|
|
57
|
+
|
|
58
|
+
The honest trade-off: this gem `dlopen`s a native library instead of being pure Ruby, so it needs a platform-specific `libhyperuuid.so`/`.dylib`/`.dll` bundled alongside it. If plain v4 randomness is all you need, `SecureRandom.uuid` is simpler and already in stdlib — that's a completely reasonable choice.
|
|
59
|
+
|
|
60
|
+
## Benchmarks
|
|
61
|
+
|
|
62
|
+
Real numbers, `benchmark-ips` on Ruby 4.0.6, linux-arm64 (`ruby benchmark/uuid_benchmark.rb`) — not claimed, measured. With the Magnus backend (the default wherever the extension loads):
|
|
63
|
+
|
|
64
|
+
| Call | i/s | vs `SecureRandom.uuid` |
|
|
65
|
+
|---|---:|---:|
|
|
66
|
+
| `SecureRandom.uuid` | 775,868 | baseline |
|
|
67
|
+
| `HyperUuid.new_v7` (explicit ms) | 2,275,763 | **2.9x faster** |
|
|
68
|
+
| `HyperUuid.new_v6` (explicit ms) | 2,197,698 | **2.8x faster** |
|
|
69
|
+
| `HyperUuid.new_v4` | 2,176,244 | **2.8x faster** |
|
|
70
|
+
| `HyperUuid.new_v5` | 1,458,385 | 1.9x faster |
|
|
71
|
+
| `HyperUuid.new_v7` (current time) | 701,373 | parity (1.1x slower) |
|
|
72
|
+
| `HyperUuid.new_v6` (current time) | 703,748 | parity (1.1x slower) |
|
|
73
|
+
|
|
74
|
+
An earlier edition of this section said single-item calls "lose to `SecureRandom.uuid`, full stop" and called the gap "structural, not a bug to fix — no amount of tuning closes that gap." That was wrong, and the receipts above are the correction: the gap was `Fiddle`'s per-call marshalling, and replacing the mechanism (the same play as this repo's Python PyO3 backend) closed it with room to spare. A `HyperUuid.new_v4` — real entropy, correct version/variant bits, minted by the shared Rust core — now costs a third of what `SecureRandom.uuid` does.
|
|
75
|
+
|
|
76
|
+
The two "current time" rows deserve their honest footnote: the explicit-ms rows isolate the binding's own cost (~440-460ns), and the difference is one `Process.clock_gettime(CLOCK_REALTIME)` wall-clock read — which this WSL2 measurement box prices at ~1µs because its Hyper-V clock defeats the vDSO fast path (verified: `CLOCK_REALTIME_COARSE` costs 102ns on the same box). On bare-metal Linux that read is tens of nanoseconds, and the default-time rows land next to the explicit-ms ones. `SecureRandom.uuid` never reads a clock — random v4 is the only thing it does.
|
|
77
|
+
|
|
78
|
+
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.27x slower than `SecureRandom.uuid` for v4 (was 1.30x before the diet, from a worse baseline run) with the same structural story as before: `Fiddle`'s interpreted marshalling is the floor, and the batch doors are how you amortize it.
|
|
79
|
+
|
|
80
|
+
Batch generation still amortizes per-call cost on both backends — one native call for the whole batch:
|
|
81
|
+
|
|
82
|
+
| Call | i/s (Magnus backend) |
|
|
83
|
+
|---|---:|
|
|
84
|
+
| `new_v6` × 1000 (individual) | 731.3 |
|
|
85
|
+
| `new_v6_batch(1000)` | 2,655.6 (**3.6x**) |
|
|
86
|
+
| `new_v7` × 1000 (individual) | 710.0 |
|
|
87
|
+
| `new_v7_batch(1000)` | 2,744.3 (**3.9x**) |
|
|
88
|
+
|
|
89
|
+
The batch multiplier shrank from 11x to ~3.8x for the best reason available: the individual calls got 3x faster, so there's less waste left to amortize. 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.
|
|
90
|
+
|
|
91
|
+
## Install
|
|
92
|
+
|
|
93
|
+
Not yet published to RubyGems.org under a registered `SkunkWerkx`/`buvinghausen` presence —
|
|
94
|
+
for now this is proven by CI building and testing the native core plus this gem on real
|
|
95
|
+
hardware for every platform leg. Consume via a direct
|
|
96
|
+
`gem "hyperuuid", git: "https://github.com/SkunkWerkx/HyperUuid", glob: "ruby/*.gemspec"` in
|
|
97
|
+
the meantime.
|
|
98
|
+
|
|
99
|
+
See [the repo root README](../README.md) for the full RFC 9562 coverage table and the state of every other language binding.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module HyperUuid
|
|
2
|
+
# Well-known namespace UUIDs defined in RFC 9562 Section 6.6.
|
|
3
|
+
module Namespaces
|
|
4
|
+
# The DNS namespace UUID.
|
|
5
|
+
DNS = Uuid.parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
|
6
|
+
# The URL namespace UUID.
|
|
7
|
+
URL = Uuid.parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
|
|
8
|
+
# The ISO OID namespace UUID.
|
|
9
|
+
OID = Uuid.parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
|
|
10
|
+
# The X.500 DN namespace UUID.
|
|
11
|
+
X500 = Uuid.parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# native/
|
|
2
|
+
|
|
3
|
+
Populated per-RID with the platform's native `libhyperuuid` build (`native/{rid}/{lib}`) by CI
|
|
4
|
+
and by `cargo build --release` for local dev — see `../../../.gitignore`. This file exists so
|
|
5
|
+
the directory has at least one tracked file on a fresh checkout, matching the Go/Swift
|
|
6
|
+
bindings' `native/`/`NativeLibs/` placeholder convention.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module HyperUuid
|
|
2
|
+
# Maps the running RUBY_PLATFORM to the RID-style directory (matching the other bindings'
|
|
3
|
+
# runtimes/{rid}/native/ / native/{rid}/ convention) and native library filename to load.
|
|
4
|
+
module NativePlatform
|
|
5
|
+
class UnsupportedPlatformError < StandardError; end
|
|
6
|
+
|
|
7
|
+
def self.rid_and_library_name
|
|
8
|
+
is_arm = RUBY_PLATFORM.match?(/arm64|aarch64/)
|
|
9
|
+
|
|
10
|
+
case RUBY_PLATFORM
|
|
11
|
+
when /mingw|mswin|windows/
|
|
12
|
+
is_arm ? ["win-arm64", "hyperuuid.dll"] : ["win-x64", "hyperuuid.dll"]
|
|
13
|
+
when /darwin/
|
|
14
|
+
is_arm ? ["osx-arm64", "libhyperuuid.dylib"] : ["osx-x64", "libhyperuuid.dylib"]
|
|
15
|
+
when /linux/
|
|
16
|
+
is_arm ? ["linux-arm64", "libhyperuuid.so"] : ["linux-x64", "libhyperuuid.so"]
|
|
17
|
+
else
|
|
18
|
+
raise UnsupportedPlatformError, "hyperuuid: unsupported platform RUBY_PLATFORM=#{RUBY_PLATFORM}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
require "fiddle"
|
|
2
|
+
|
|
3
|
+
module HyperUuid
|
|
4
|
+
# Fiddle plumbing for the native libhyperuuid shared library — dlopen/dlsym plus a raw
|
|
5
|
+
# C-ABI call, no runtime bridge (the same "no shim" positioning as the Go/Swift bindings'
|
|
6
|
+
# purego/dlopen approach). Fiddle ships with every Ruby install; it's a plain gem
|
|
7
|
+
# dependency here (see hyperuuid.gemspec) rather than a third-party one — mirroring Go's
|
|
8
|
+
# "no cgo" and Python's ctypes-only stance.
|
|
9
|
+
#
|
|
10
|
+
# Unlike the Go/Swift bindings, which embed their native builds inside a single compiled
|
|
11
|
+
# archive and must extract to a temp file before dlopen can see a real path, a Ruby gem's
|
|
12
|
+
# files are already plain files on disk once installed — native/{rid}/{lib} can be
|
|
13
|
+
# dlopen'd directly, no extraction step needed.
|
|
14
|
+
module Runtime
|
|
15
|
+
class RandomSourceError < StandardError; end
|
|
16
|
+
class TimestampOutOfRangeError < StandardError; end
|
|
17
|
+
|
|
18
|
+
NATIVE_DIR = File.join(__dir__, "native")
|
|
19
|
+
|
|
20
|
+
@mutex = Mutex.new
|
|
21
|
+
@functions = nil
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
def new_v4
|
|
25
|
+
out = scratch
|
|
26
|
+
rc = functions[:new_v4].call(out)
|
|
27
|
+
raise RandomSourceError, "uuid_new_v4 failed with code #{rc}" unless rc.zero?
|
|
28
|
+
out[0, 16]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def new_v5(namespace_bytes, name_bytes)
|
|
32
|
+
out = scratch
|
|
33
|
+
# Fiddle passes a String's bytes for void* directly (read-only) — no Pointer
|
|
34
|
+
# wrapper, no copy — the same zero-copy crossing every other input here uses.
|
|
35
|
+
name = name_bytes.empty? ? nil : name_bytes
|
|
36
|
+
rc = functions[:new_v5].call(namespace_bytes, name, name_bytes.bytesize, out)
|
|
37
|
+
raise RandomSourceError, "uuid_new_v5 failed with code #{rc}" unless rc.zero?
|
|
38
|
+
out[0, 16]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def new_v6(unix_millis)
|
|
42
|
+
out = scratch
|
|
43
|
+
rc = functions[:new_v6].call(unix_millis, out)
|
|
44
|
+
case rc
|
|
45
|
+
when 0 then out[0, 16]
|
|
46
|
+
when 2 then raise TimestampOutOfRangeError, "unix_millis does not fit the 60-bit v6 timestamp field"
|
|
47
|
+
else raise RandomSourceError, "uuid_new_v6 failed with code #{rc}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def v6_unix_millis(bytes)
|
|
52
|
+
functions[:v6_unix_millis].call(bytes)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def new_v6_batch(count, unix_millis)
|
|
56
|
+
return "" if count.zero?
|
|
57
|
+
out = Fiddle::Pointer.malloc(count * 16, Fiddle::RUBY_FREE)
|
|
58
|
+
rc = functions[:new_v6_batch].call(unix_millis, count, out)
|
|
59
|
+
case rc
|
|
60
|
+
when 0 then out[0, count * 16]
|
|
61
|
+
when 2 then raise TimestampOutOfRangeError, "unix_millis does not fit the 60-bit v6 timestamp field"
|
|
62
|
+
else raise RandomSourceError, "uuid_new_v6_batch failed with code #{rc}"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def new_v7(unix_millis)
|
|
67
|
+
out = scratch
|
|
68
|
+
rc = functions[:new_v7].call(unix_millis, out)
|
|
69
|
+
case rc
|
|
70
|
+
when 0 then out[0, 16]
|
|
71
|
+
when 2 then raise TimestampOutOfRangeError, "unix_millis must fit within the RFC 9562 48-bit field"
|
|
72
|
+
else raise RandomSourceError, "uuid_new_v7 failed with code #{rc}"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def v7_unix_millis(bytes)
|
|
77
|
+
functions[:v7_unix_millis].call(bytes)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def new_v7_batch(count, unix_millis)
|
|
81
|
+
return "" if count.zero?
|
|
82
|
+
out = Fiddle::Pointer.malloc(count * 16, Fiddle::RUBY_FREE)
|
|
83
|
+
rc = functions[:new_v7_batch].call(unix_millis, count, out)
|
|
84
|
+
case rc
|
|
85
|
+
when 0 then out[0, count * 16]
|
|
86
|
+
when 2 then raise TimestampOutOfRangeError, "unix_millis must fit within the RFC 9562 48-bit field"
|
|
87
|
+
else raise RandomSourceError, "uuid_new_v7_batch failed with code #{rc}"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def v7_to_sql_order(bytes)
|
|
92
|
+
rewrite(:v7_to_sql_order, bytes)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def v7_to_rfc_order(bytes)
|
|
96
|
+
rewrite(:v7_to_rfc_order, bytes)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def v6_to_sql_order(bytes)
|
|
100
|
+
rewrite(:v6_to_sql_order, bytes)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def v6_to_rfc_order(bytes)
|
|
104
|
+
rewrite(:v6_to_rfc_order, bytes)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
# One 16-byte scratch allocation per thread, reused by every single-item call —
|
|
110
|
+
# Fiddle::Pointer.malloc(..., RUBY_FREE) registers a GC finalizer per call, measured
|
|
111
|
+
# (in HyperCast, same mechanism) as the dominant per-call cost by an order of
|
|
112
|
+
# magnitude. Batches keep a per-call buffer: one malloc amortized over `count` IDs.
|
|
113
|
+
def scratch
|
|
114
|
+
Thread.current[:hyperuuid_scratch] ||= Fiddle::Pointer.malloc(16, Fiddle::RUBY_FREE)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The in-place byte-order rewrites are the one shape that must copy in: the native
|
|
118
|
+
# call genuinely mutates the buffer, and the input String is frozen.
|
|
119
|
+
def rewrite(symbol, bytes)
|
|
120
|
+
buf = scratch
|
|
121
|
+
buf[0, 16] = bytes
|
|
122
|
+
functions[symbol].call(buf)
|
|
123
|
+
buf[0, 16]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Loaded lazily and exactly once, mirroring the Go binding's sync.Once / Swift's lazy
|
|
127
|
+
# static let — the native library and its function pointers live for the process's
|
|
128
|
+
# lifetime, same as every other binding (never dlclose'd). The unsynchronized read is
|
|
129
|
+
# the hot path; the mutex only guards the one-time load (a benign race — idempotent).
|
|
130
|
+
def functions
|
|
131
|
+
@functions || @mutex.synchronize { @functions ||= load_functions }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def load_functions
|
|
135
|
+
rid, lib_name = NativePlatform.rid_and_library_name
|
|
136
|
+
path = File.join(NATIVE_DIR, rid, lib_name)
|
|
137
|
+
unless File.exist?(path)
|
|
138
|
+
raise LoadError,
|
|
139
|
+
"hyperuuid: #{path} not found (unsupported platform, or this gem was built without a native library for it)"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
handle = Fiddle.dlopen(path)
|
|
143
|
+
{
|
|
144
|
+
new_v4: Fiddle::Function.new(handle["uuid_new_v4"], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT),
|
|
145
|
+
new_v5: Fiddle::Function.new(
|
|
146
|
+
handle["uuid_new_v5"],
|
|
147
|
+
[Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_UINT32_T, Fiddle::TYPE_VOIDP],
|
|
148
|
+
Fiddle::TYPE_INT
|
|
149
|
+
),
|
|
150
|
+
new_v6: Fiddle::Function.new(
|
|
151
|
+
handle["uuid_new_v6"],
|
|
152
|
+
[Fiddle::TYPE_UINT64_T, Fiddle::TYPE_VOIDP],
|
|
153
|
+
Fiddle::TYPE_INT
|
|
154
|
+
),
|
|
155
|
+
v6_unix_millis: Fiddle::Function.new(
|
|
156
|
+
handle["uuid_v6_unix_millis"],
|
|
157
|
+
[Fiddle::TYPE_VOIDP],
|
|
158
|
+
Fiddle::TYPE_UINT64_T
|
|
159
|
+
),
|
|
160
|
+
new_v6_batch: Fiddle::Function.new(
|
|
161
|
+
handle["uuid_new_v6_batch"],
|
|
162
|
+
[Fiddle::TYPE_UINT64_T, Fiddle::TYPE_UINT32_T, Fiddle::TYPE_VOIDP],
|
|
163
|
+
Fiddle::TYPE_INT
|
|
164
|
+
),
|
|
165
|
+
new_v7: Fiddle::Function.new(
|
|
166
|
+
handle["uuid_new_v7"],
|
|
167
|
+
[Fiddle::TYPE_UINT64_T, Fiddle::TYPE_VOIDP],
|
|
168
|
+
Fiddle::TYPE_INT
|
|
169
|
+
),
|
|
170
|
+
v7_unix_millis: Fiddle::Function.new(
|
|
171
|
+
handle["uuid_v7_unix_millis"],
|
|
172
|
+
[Fiddle::TYPE_VOIDP],
|
|
173
|
+
Fiddle::TYPE_UINT64_T
|
|
174
|
+
),
|
|
175
|
+
new_v7_batch: Fiddle::Function.new(
|
|
176
|
+
handle["uuid_new_v7_batch"],
|
|
177
|
+
[Fiddle::TYPE_UINT64_T, Fiddle::TYPE_UINT32_T, Fiddle::TYPE_VOIDP],
|
|
178
|
+
Fiddle::TYPE_INT
|
|
179
|
+
),
|
|
180
|
+
v7_to_sql_order: Fiddle::Function.new(
|
|
181
|
+
handle["uuid_v7_to_sql_order"],
|
|
182
|
+
[Fiddle::TYPE_VOIDP],
|
|
183
|
+
Fiddle::TYPE_VOID
|
|
184
|
+
),
|
|
185
|
+
v7_to_rfc_order: Fiddle::Function.new(
|
|
186
|
+
handle["uuid_v7_to_rfc_order"],
|
|
187
|
+
[Fiddle::TYPE_VOIDP],
|
|
188
|
+
Fiddle::TYPE_VOID
|
|
189
|
+
),
|
|
190
|
+
v6_to_sql_order: Fiddle::Function.new(
|
|
191
|
+
handle["uuid_v6_to_sql_order"],
|
|
192
|
+
[Fiddle::TYPE_VOIDP],
|
|
193
|
+
Fiddle::TYPE_VOID
|
|
194
|
+
),
|
|
195
|
+
v6_to_rfc_order: Fiddle::Function.new(
|
|
196
|
+
handle["uuid_v6_to_rfc_order"],
|
|
197
|
+
[Fiddle::TYPE_VOIDP],
|
|
198
|
+
Fiddle::TYPE_VOID
|
|
199
|
+
),
|
|
200
|
+
}
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
module HyperUuid
|
|
2
|
+
# A parsed 16-byte RFC 9562 UUID value. Minimal by design — this gem has no runtime
|
|
3
|
+
# dependency on the `uuid` gem, the same "no extra dependency" positioning as the Go
|
|
4
|
+
# binding's purego-only/no-cgo approach and the Python binding's stdlib-ctypes-only one.
|
|
5
|
+
class Uuid
|
|
6
|
+
include Comparable
|
|
7
|
+
|
|
8
|
+
# The UUID's 16 raw bytes in RFC 9562 (big-endian) order.
|
|
9
|
+
attr_reader :bytes
|
|
10
|
+
|
|
11
|
+
# Wraps a raw 16-byte RFC 9562 (big-endian) UUID value.
|
|
12
|
+
#
|
|
13
|
+
# @raise [ArgumentError] if +bytes+ isn't exactly 16 bytes.
|
|
14
|
+
def initialize(bytes)
|
|
15
|
+
raise ArgumentError, "bytes must be exactly 16 bytes" unless bytes.bytesize == 16
|
|
16
|
+
@bytes = bytes.dup.force_encoding(Encoding::BINARY).freeze
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The RFC 9562 §5.9 Nil UUID — all 128 bits zero.
|
|
20
|
+
NIL = new(("\x00" * 16).b).freeze
|
|
21
|
+
|
|
22
|
+
# The RFC 9562 §5.10 Max UUID — all 128 bits one.
|
|
23
|
+
MAX = new(("\xFF" * 16).b).freeze
|
|
24
|
+
|
|
25
|
+
# Parses an 8-4-4-4-12 hyphenated hex UUID string.
|
|
26
|
+
#
|
|
27
|
+
# @raise [ArgumentError] if +string+ isn't a valid UUID string.
|
|
28
|
+
def self.parse(string)
|
|
29
|
+
hex = string.delete("-")
|
|
30
|
+
raise ArgumentError, "invalid UUID string: #{string.inspect}" unless hex.match?(/\A[0-9a-fA-F]{32}\z/)
|
|
31
|
+
new([hex].pack("H*"))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The RFC 9562 version nibble (bits 48-51, the high nibble of octet 6).
|
|
35
|
+
def version
|
|
36
|
+
(bytes.getbyte(6) >> 4) & 0x0F
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# The RFC 9562 variant bits (top two bits of octet 8). +0b10+ means RFC 9562/4122.
|
|
40
|
+
def variant
|
|
41
|
+
(bytes.getbyte(8) >> 6) & 0b11
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# The 8-4-4-4-12 hyphenated hex string representation.
|
|
45
|
+
def to_s
|
|
46
|
+
hex = bytes.unpack1("H*")
|
|
47
|
+
"#{hex[0, 8]}-#{hex[8, 4]}-#{hex[12, 4]}-#{hex[16, 4]}-#{hex[20, 12]}"
|
|
48
|
+
end
|
|
49
|
+
alias_method :to_str, :to_s
|
|
50
|
+
|
|
51
|
+
# The UTC timestamp embedded in a version 6 or 7 UUID's timestamp field. Only meaningful
|
|
52
|
+
# when `version` is 6 or 7 — the RFC 9562 bit layout doesn't distinguish "not a time-based
|
|
53
|
+
# UUID" from "time-based UUID with a very early timestamp", so the caller is responsible
|
|
54
|
+
# for checking `version` first if that matters.
|
|
55
|
+
def timestamp
|
|
56
|
+
millis =
|
|
57
|
+
case version
|
|
58
|
+
when 6 then Runtime.v6_unix_millis(bytes)
|
|
59
|
+
when 7 then Runtime.v7_unix_millis(bytes)
|
|
60
|
+
else raise ArgumentError, "timestamp is only defined for version 6 or 7 UUIDs, got version #{version}"
|
|
61
|
+
end
|
|
62
|
+
Time.at(millis / 1000, millis % 1000, :millisecond).utc
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Converts an RFC 9562-ordered version 6 or 7 UUID to the byte order SQL Server's
|
|
66
|
+
# `uniqueidentifier` needs on the wire to sort by creation order. Dispatches on `version`
|
|
67
|
+
# the same way #timestamp does.
|
|
68
|
+
#
|
|
69
|
+
# `System.Data.SqlTypes.SqlGuid` comparison — and therefore T-SQL `ORDER BY` on a
|
|
70
|
+
# `uniqueidentifier` column — doesn't compare a GUID's 16 bytes left to right; it uses a
|
|
71
|
+
# fixed, non-sequential byte significance order (octets 10,11,12,13,14,15,8,9,6,7,4,5,
|
|
72
|
+
# 0,1,2,3, most significant first). Computed once in the native Rust core and verified
|
|
73
|
+
# there (and independently, against the real SqlGuid comparator, in this project's C#
|
|
74
|
+
# test suite); this binding calls the same native functions rather than reimplementing
|
|
75
|
+
# the byte math.
|
|
76
|
+
#
|
|
77
|
+
# For v7, this moves the timestamp and counter — the two fields that determine creation
|
|
78
|
+
# order — into that comparison's most-significant bytes, and moves the trailing entropy,
|
|
79
|
+
# which carries no ordering information, into the least-significant ones as one intact
|
|
80
|
+
# block. For v6, which has no monotonic counter the way v7 does, the only field that
|
|
81
|
+
# determines creation order is the 60-bit timestamp itself, so that moves into the most
|
|
82
|
+
# significant bytes instead, with `clock_seq`/`node` (independently random per call, not
|
|
83
|
+
# a counter, so no ordering value either way) relocated into the rest. v6's much simpler
|
|
84
|
+
# byte layout needs no bit-level repacking to do this — just whole-octet-group
|
|
85
|
+
# relocation — unlike v7's, and its version/variant land at different sql-order offsets
|
|
86
|
+
# as a result (octet 8's top nibble / octet 6's top two bits, not 7/8).
|
|
87
|
+
#
|
|
88
|
+
# **v6-specific caveat, unlike v7:** two version 6 UUIDs minted at the same millisecond
|
|
89
|
+
# have identical timestamp bits, so they aren't guaranteed to sort in creation order any
|
|
90
|
+
# more than plain RFC order already does — a pre-existing RFC 9562 v6 limitation, not one
|
|
91
|
+
# this transform introduces.
|
|
92
|
+
#
|
|
93
|
+
# Meaningful only for a genuine version 6 or 7 UUID.
|
|
94
|
+
def to_sql_order
|
|
95
|
+
case version
|
|
96
|
+
when 7 then self.class.new(Runtime.v7_to_sql_order(bytes))
|
|
97
|
+
when 6 then self.class.new(Runtime.v6_to_sql_order(bytes))
|
|
98
|
+
else raise ArgumentError, "to_sql_order is only defined for version 6 or 7 UUIDs, got version #{version}"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Inverse of #to_sql_order — converts a SQL-Server-ordered version 6 or 7 UUID back to
|
|
103
|
+
# RFC 9562 order.
|
|
104
|
+
#
|
|
105
|
+
# A SQL-ordered value's version nibble sits at a different octet depending on which
|
|
106
|
+
# version produced it (octet 7's top nibble = 7 for v7-sql-order, octet 8's top nibble =
|
|
107
|
+
# 6 for v6-sql-order — #version itself assumes RFC order's octet 6 and can't tell these
|
|
108
|
+
# apart), so this checks both fixed positions directly rather than calling #version.
|
|
109
|
+
#
|
|
110
|
+
# Order matters here and isn't arbitrary: octet 8 must be checked *first*. For v6-sql-order
|
|
111
|
+
# it's deterministic (top nibble always 0x6, by construction), and for v7-sql-order it's
|
|
112
|
+
# also deterministic but structurally excluded from ever reading 0x6 (its top two bits are
|
|
113
|
+
# the fixed variant `10`, so the nibble only ever lands in 0x8-0xB) — no collision either
|
|
114
|
+
# way. Octet 7, by contrast, is *not* safe to check first: for v7-sql-order it's
|
|
115
|
+
# deterministically 0x7, but for v6-sql-order it holds `clock_seq`'s fully random low
|
|
116
|
+
# byte, which has a real (~1-in-16) chance of a top nibble that also happens to read 0x7 —
|
|
117
|
+
# confirmed by an actual test failure during development, not a hypothetical. Checking
|
|
118
|
+
# octet 8 first rules v6 in or out unambiguously before octet 7's reading can matter.
|
|
119
|
+
def from_sql_order
|
|
120
|
+
octet8_version = (bytes.getbyte(8) >> 4) & 0x0F
|
|
121
|
+
octet7_version = (bytes.getbyte(7) >> 4) & 0x0F
|
|
122
|
+
if octet8_version == 6
|
|
123
|
+
self.class.new(Runtime.v6_to_rfc_order(bytes))
|
|
124
|
+
elsif octet7_version == 7
|
|
125
|
+
self.class.new(Runtime.v7_to_rfc_order(bytes))
|
|
126
|
+
else
|
|
127
|
+
raise ArgumentError, "from_sql_order: not a recognized version 6 or 7 SQL-ordered UUID"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Whether +other+ wraps the same 16 raw bytes.
|
|
132
|
+
def ==(other)
|
|
133
|
+
other.is_a?(Uuid) && bytes == other.bytes
|
|
134
|
+
end
|
|
135
|
+
alias_method :eql?, :==
|
|
136
|
+
|
|
137
|
+
# Hash code consistent with #==, based on the raw bytes.
|
|
138
|
+
def hash
|
|
139
|
+
bytes.hash
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Byte-order comparison against +other+, or +nil+ if +other+ isn't a Uuid.
|
|
143
|
+
def <=>(other)
|
|
144
|
+
return nil unless other.is_a?(Uuid)
|
|
145
|
+
bytes <=> other.bytes
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Debug representation, e.g. <tt>#<HyperUuid::Uuid ...></tt>.
|
|
149
|
+
def inspect
|
|
150
|
+
"#<HyperUuid::Uuid #{self}>"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
data/lib/hyperuuid.rb
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require "time"
|
|
2
|
+
|
|
3
|
+
require_relative "hyperuuid/uuid"
|
|
4
|
+
require_relative "hyperuuid/namespaces"
|
|
5
|
+
require_relative "hyperuuid/native_platform"
|
|
6
|
+
require_relative "hyperuuid/runtime"
|
|
7
|
+
|
|
8
|
+
# RFC 9562 UUID v4 (random), v5 (deterministic), v6 and v7 (time-sortable) generation, calling
|
|
9
|
+
# directly into the native libhyperuuid shared library via Fiddle — no runtime bridge, no
|
|
10
|
+
# extra gem dependency. Bundles a native build for every supported platform (see
|
|
11
|
+
# HyperUuid::NativePlatform) and picks the right one at runtime, the same trick the Go/
|
|
12
|
+
# Java bindings use since RubyGems has no per-platform native selection wired up here.
|
|
13
|
+
module HyperUuid
|
|
14
|
+
# This gem's own version — distinct from the RFC 9562 UUID *versions* (v4/v5/v6/v7) the
|
|
15
|
+
# rest of this module generates.
|
|
16
|
+
VERSION = "0.1.0"
|
|
17
|
+
|
|
18
|
+
# Creates a random UUID version 4 (RFC 9562 §5.4).
|
|
19
|
+
def self.new_v4
|
|
20
|
+
Uuid.new(Runtime.new_v4)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Creates a deterministic UUID version 5 (RFC 9562 §5.5) from a namespace and a name. The
|
|
24
|
+
# same (namespace, name) pair always produces the same UUID. `name` may be a text String
|
|
25
|
+
# (encoded as UTF-8) or already-raw ASCII-8BIT bytes, which are used as-is.
|
|
26
|
+
def self.new_v5(namespace, name)
|
|
27
|
+
name_bytes =
|
|
28
|
+
if name.encoding == Encoding::ASCII_8BIT
|
|
29
|
+
name
|
|
30
|
+
else
|
|
31
|
+
name.encode(Encoding::UTF_8).dup.force_encoding(Encoding::BINARY)
|
|
32
|
+
end
|
|
33
|
+
Uuid.new(Runtime.new_v5(namespace.bytes, name_bytes))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Creates a time-sortable UUID version 6 (RFC 9562 §5.6), a field-compatible reordering of
|
|
37
|
+
# version 1 for better sort/index locality. Defaults to the current time; pass an explicit
|
|
38
|
+
# Unix-epoch millisecond timestamp to embed a specific time instead. `clock_seq` and `node`
|
|
39
|
+
# are randomly generated on every call — unlike version 7, there is no monotonic counter, so
|
|
40
|
+
# calls within the same millisecond are not guaranteed to sort in creation order.
|
|
41
|
+
def self.new_v6(unix_millis = nil)
|
|
42
|
+
unix_millis ||= Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
|
|
43
|
+
Uuid.new(Runtime.new_v6(unix_millis))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Creates `count` time-sortable version 6 UUIDs sharing one timestamp capture — one FFI call
|
|
47
|
+
# and one random-bytes fetch instead of `count` of each. Defaults to the current time.
|
|
48
|
+
def self.new_v6_batch(count, unix_millis = nil)
|
|
49
|
+
unix_millis ||= Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
|
|
50
|
+
bytes = Runtime.new_v6_batch(count, unix_millis)
|
|
51
|
+
Array.new(count) { |i| Uuid.new(bytes[i * 16, 16]) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Creates a time-sortable UUID version 7 (RFC 9562 §6.2). Defaults to the current time; pass
|
|
55
|
+
# an explicit Unix-epoch millisecond timestamp (non-negative, fitting in 48 bits) to embed a
|
|
56
|
+
# specific time instead.
|
|
57
|
+
def self.new_v7(unix_millis = nil)
|
|
58
|
+
unix_millis ||= Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
|
|
59
|
+
Uuid.new(Runtime.new_v7(unix_millis))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Creates `count` time-sortable version 7 UUIDs sharing one timestamp capture and one
|
|
63
|
+
# contiguous block of the monotonic counter — one FFI call and one random-bytes fetch
|
|
64
|
+
# instead of `count` of each. Defaults to the current time.
|
|
65
|
+
def self.new_v7_batch(count, unix_millis = nil)
|
|
66
|
+
unix_millis ||= Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
|
|
67
|
+
bytes = Runtime.new_v7_batch(count, unix_millis)
|
|
68
|
+
Array.new(count) { |i| Uuid.new(bytes[i * 16, 16]) }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# --- backend selection: the Magnus extension, when present, replaces the Runtime methods
|
|
73
|
+
# above in place (no delegation layer) — Fiddle's measured per-call marshalling floor drops
|
|
74
|
+
# to an ordinary extension call, while everything above Runtime (Uuid, the module doors,
|
|
75
|
+
# batch slicing) stays shared byte-for-byte between backends. The pure-Fiddle definitions
|
|
76
|
+
# remain the universal zero-compile fallback; precompiled platform gems are how the
|
|
77
|
+
# extension ships without ever making a consumer compile anything. Set HYPERUUID_PURE=1 to
|
|
78
|
+
# force Fiddle.
|
|
79
|
+
HyperUuid::BACKEND =
|
|
80
|
+
if ENV["HYPERUUID_PURE"]
|
|
81
|
+
:fiddle
|
|
82
|
+
else
|
|
83
|
+
begin
|
|
84
|
+
require "hyperuuid_native"
|
|
85
|
+
:native
|
|
86
|
+
rescue LoadError
|
|
87
|
+
:fiddle
|
|
88
|
+
end
|
|
89
|
+
end
|
|
Binary file
|
metadata
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hyperuuid
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: aarch64-linux
|
|
6
|
+
authors:
|
|
7
|
+
- Brian Buvinghausen
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: fiddle
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: benchmark-ips
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.15'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.15'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: yard
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.9'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.9'
|
|
68
|
+
description: |
|
|
69
|
+
High-performance, allocation-free RFC 9562 UUID v4 (random), v5 (deterministic), v6 and v7
|
|
70
|
+
(time-sortable) generation, calling directly into the native libhyperuuid shared library
|
|
71
|
+
via Fiddle (Ruby's standard-library FFI) — no runtime bridge, no extra gem dependency.
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- README.md
|
|
77
|
+
- lib/hyperuuid.rb
|
|
78
|
+
- lib/hyperuuid/namespaces.rb
|
|
79
|
+
- lib/hyperuuid/native/README.md
|
|
80
|
+
- lib/hyperuuid/native/linux-arm64/libhyperuuid.so
|
|
81
|
+
- lib/hyperuuid/native/linux-x64/libhyperuuid.so
|
|
82
|
+
- lib/hyperuuid/native/osx-arm64/libhyperuuid.dylib
|
|
83
|
+
- lib/hyperuuid/native/osx-x64/libhyperuuid.dylib
|
|
84
|
+
- lib/hyperuuid/native/win-arm64/hyperuuid.dll
|
|
85
|
+
- lib/hyperuuid/native/win-x64/hyperuuid.dll
|
|
86
|
+
- lib/hyperuuid/native_platform.rb
|
|
87
|
+
- lib/hyperuuid/runtime.rb
|
|
88
|
+
- lib/hyperuuid/uuid.rb
|
|
89
|
+
- lib/hyperuuid_native.so
|
|
90
|
+
homepage: https://github.com/SkunkWerkx/HyperUuid
|
|
91
|
+
licenses:
|
|
92
|
+
- MIT
|
|
93
|
+
metadata:
|
|
94
|
+
source_code_uri: https://github.com/SkunkWerkx/HyperUuid
|
|
95
|
+
rdoc_options: []
|
|
96
|
+
require_paths:
|
|
97
|
+
- lib
|
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '3.2'
|
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: '0'
|
|
108
|
+
requirements: []
|
|
109
|
+
rubygems_version: 3.6.9
|
|
110
|
+
specification_version: 4
|
|
111
|
+
summary: RFC 9562 UUID v4/v5/v6/v7 generation — direct native FFI into a Rust core,
|
|
112
|
+
no runtime bridge.
|
|
113
|
+
test_files: []
|