uuidx 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 +7 -0
- data/CHANGELOG.md +46 -0
- data/LICENSE.txt +21 -0
- data/README.md +195 -0
- data/lib/uuid/errors.rb +11 -0
- data/lib/uuid/gem_version.rb +6 -0
- data/lib/uuid/version4.rb +36 -0
- data/lib/uuid/version6.rb +80 -0
- data/lib/uuid/version7.rb +69 -0
- data/lib/uuid/version8.rb +59 -0
- data/lib/uuid.rb +130 -0
- data/sig/uuid/errors.rbs +7 -0
- data/sig/uuid/version4.rbs +6 -0
- data/sig/uuid/version6.rbs +11 -0
- data/sig/uuid/version7.rbs +10 -0
- data/sig/uuid/version8.rbs +14 -0
- data/sig/uuid.rbs +30 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d2e41b677c8a6cb523605c6f10620796d1b5831ad511a32b49e8d98415629677
|
4
|
+
data.tar.gz: 8e8935a0b62e6538a5de355ec22119a0cba5242a3c76767bb720d5d13192431d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ff313efefe5c2ccca58479e2e208cd98d636b74e5311f5e29de20faaf5207f47baf708c304c2e15275fa2a75b26d0f050f7c38174ddeaadcbf8e9449e9dd8a1c
|
7
|
+
data.tar.gz: 4def103fd92484499eef8474454eed8e7f6bc2a0e975e7f22deabe986d02f12d957b7bf2457b8d6c6101e66c4789cffaf3844812b6b0e973c95950e7b7abfbf5
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Unreleased
|
2
|
+
|
3
|
+
# Stable Releases
|
4
|
+
|
5
|
+
## 1.0.0 – ???
|
6
|
+
|
7
|
+
# Alpha Releases
|
8
|
+
|
9
|
+
## 0.9.0 – 2023-02-11
|
10
|
+
- Swap gem name to shorter uuidx
|
11
|
+
- Main documentation written
|
12
|
+
- Add type signatures
|
13
|
+
- Add monotonic batch support to simple API.
|
14
|
+
|
15
|
+
## 0.8.0 – 2023-02-10
|
16
|
+
- Add simple Uuid generation API
|
17
|
+
- Generator classes are now lock-free
|
18
|
+
- Remove UUID value type
|
19
|
+
|
20
|
+
## 0.7.0 – 2023-02-10
|
21
|
+
- Generator APIs now return opaque string values for UUIDs
|
22
|
+
- Add faster UUID v4 implementation
|
23
|
+
|
24
|
+
## 0.6.0 – 2023-02-04
|
25
|
+
- Convert generator modules to classes
|
26
|
+
- Fix clock ID references in UUID v7
|
27
|
+
- Pass tests on all supported versions of Ruby
|
28
|
+
|
29
|
+
## 0.5.0 – 2023-01-31
|
30
|
+
- Threading safety for clock sequence in UUID v6
|
31
|
+
- Clock drift detection in UUID v6
|
32
|
+
|
33
|
+
## 0.4.0 – 2023-01-26
|
34
|
+
- Add clock resolution verification methods
|
35
|
+
- UUID v6 and v7 are now thread-safe
|
36
|
+
|
37
|
+
## 0.3.0 – 2023-01-25
|
38
|
+
- New, faster design of UUID generator modules
|
39
|
+
- Removal of old implementations
|
40
|
+
|
41
|
+
## 0.2.0 – 2023-01-22
|
42
|
+
- UUID v6, v7, and v8 implemented quickly
|
43
|
+
- Some performance optimization done
|
44
|
+
|
45
|
+
## 0.1.0 – 2023-01-18
|
46
|
+
- Initial release without design considerations
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 Stephan Tarulli
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
<h1 align="center">uuidx</h1>
|
2
|
+
<p align="center">Fast Ruby implementations of UUID versions 4, 6, 7, and 8 🪪</p>
|
3
|
+
|
4
|
+
---
|
5
|
+
|
6
|
+
## What's in the box?
|
7
|
+
|
8
|
+
✅ Simple usage documentation written to get started fast. [30 seconds and you're off.](#usage)
|
9
|
+
|
10
|
+
📚 API documentation for the library. [Read the docs.](https://tinychameleon.github.io/uuidx/)
|
11
|
+
|
12
|
+
⚡ A reasonably fast, zero dependency implementation of the new UUID standards.
|
13
|
+
|
14
|
+
🤖 RBS types for your type checking wants. [Typed goodness.](./sig)
|
15
|
+
|
16
|
+
💎 Tests against Ruby 2.7, 3.0, 3.1, and 3.2.
|
17
|
+
|
18
|
+
🔒 MFA protection on gem owners.
|
19
|
+
|
20
|
+
---
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
### Using Bundler
|
25
|
+
|
26
|
+
Run `bundle add uuidx` to update your `Gemfile` and install the gem. You may
|
27
|
+
also add the gem to your `Gemfile` manually and then run `bundle install`.
|
28
|
+
|
29
|
+
### Manual
|
30
|
+
|
31
|
+
Use `gem install uuidx` to install the gem from [RubyGems](https://rubygems.org).
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
To get started using the default generators for UUID v4, v6, or v7 `require`
|
36
|
+
the library and call the associated method.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
require "uuidx"
|
40
|
+
|
41
|
+
Uuid.v4 # => "2b54639d-e43e-489f-9c64-30ecdcac3c95"
|
42
|
+
Uuid.v6 # => "1eda9761-9f6f-6414-8c5f-fd61f1239907"
|
43
|
+
Uuid.v7 # => "01863d24-6d1e-78ba-92ee-6e80c79c4e28"
|
44
|
+
```
|
45
|
+
|
46
|
+
These methods all use default generators and are thread-safe. However, if you
|
47
|
+
are using child processes (like Puma's clustered mode) you should also reset
|
48
|
+
the generators you are using when each child process starts.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# Puma example that resets all default generators.
|
52
|
+
on_worker_boot do
|
53
|
+
Uuid.reset_v4!
|
54
|
+
Uuid.reset_v6!
|
55
|
+
Uuid.reset_v7!
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
This way you will get thread-safe state access per process without requiring
|
60
|
+
IPC.
|
61
|
+
|
62
|
+
### Monotonicity & Batching
|
63
|
+
The simple API provided by `Uuid` also supports monotonic batches. Provide the
|
64
|
+
batch size and you will receive an array of UUID values back.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
Uuid.batch_v4(10) # => ["2b54639d-e43e-489f-9c64-30ecdcac3c95", ...]
|
68
|
+
Uuid.batch_v6(10) # => ["1eda9761-9f6f-6414-8c5f-fd61f1239907", ...]
|
69
|
+
Uuid.batch_v7(10) # => ["01863d24-6d1e-78ba-92ee-6e80c79c4e28", ...]
|
70
|
+
```
|
71
|
+
|
72
|
+
### Advanced Usage
|
73
|
+
If you require multiple generators you can drop below the simple API presented
|
74
|
+
above to create generators directly.
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
v6 = Uuid::Version6.new
|
78
|
+
v6.generate # => "1eda9adc-2ed9-629e-9a02-4d2ccc87c569"
|
79
|
+
```
|
80
|
+
|
81
|
+
These generators are lock-free, so if you use them to obtain higher throughput
|
82
|
+
keep in mind that you must ensure they are never shared between threads.
|
83
|
+
|
84
|
+
For typical MRI Ruby workloads using multi-process worker strategies state will
|
85
|
+
be cloned and modified using copy-on-write and the thread safety provided by
|
86
|
+
the simple API is adequate.
|
87
|
+
|
88
|
+
#### UUID v8
|
89
|
+
UUID v8 is a special case of advanced usage that always requires you to build
|
90
|
+
a generator directly. It takes a single parameter to its constructor which must
|
91
|
+
be the class name of your UUID v8 definition.
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
v8 = Uuid::Version8.new(MyV8Definition)
|
95
|
+
v8.generate # => "..."
|
96
|
+
```
|
97
|
+
|
98
|
+
The definition class should implement the methods `custom_a`, `custom_b`, and
|
99
|
+
`custom_c` in order to fill out the UUID data [according to the draft](https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-uuid-version-8).
|
100
|
+
|
101
|
+
See the [documentation for Version8](https://tinychameleon.github.io/uuidx/Uuid/Version8.html) for precise details.
|
102
|
+
|
103
|
+
#### Batching
|
104
|
+
Any custom UUID v8 generators can also participate in thread-safe batching by using
|
105
|
+
the `batch` method.
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
Uuid.batch(v8, 10) # => ["<a v8 uuid>", ...]
|
109
|
+
```
|
110
|
+
|
111
|
+
### Clock Resolution
|
112
|
+
If you have need to verify the clock resolution for UUID v6 or v7 you can call
|
113
|
+
the `verify_clock_resolution!` method on either class. A `ClockResolutionError`
|
114
|
+
is raised if the system has insufficient precision.
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
begin
|
118
|
+
Uuid::Version6.verify_clock_resolution! # or Uuid::Version7
|
119
|
+
rescue Uuid::ClockResolutionError
|
120
|
+
# ...
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
The API documentation has details about what the clock resolution must be for
|
125
|
+
each of the UUID versions. See the
|
126
|
+
[Version 6](https://tinychameleon.github.io/uuidx/Uuid/Version6.html) and
|
127
|
+
[Version 7](https://tinychameleon.github.io/uuidx/Uuid/Version7.html)
|
128
|
+
documentation for details.
|
129
|
+
|
130
|
+
### A Note on Clock Timings
|
131
|
+
The API documentation contains specific details around how the implementations
|
132
|
+
deal with clock drift. See the
|
133
|
+
[Uuid](https://tinychameleon.github.io/uuidx/Uuid.html),
|
134
|
+
[Version 6](https://tinychameleon.github.io/uuidx/Uuid/Version6.html), and
|
135
|
+
[Version 7](https://tinychameleon.github.io/uuidx/Uuid/Version7.html)
|
136
|
+
documentation for more information.
|
137
|
+
|
138
|
+
## Performance
|
139
|
+
This performance data was captured using `benchmark/ips`. It uses the following
|
140
|
+
Ruby version
|
141
|
+
|
142
|
+
ruby 3.2.0 (2022-12-25 revision a528908271) [arm64-darwin22]
|
143
|
+
|
144
|
+
and is run on a Macbook Pro with an M1 Max CPU.
|
145
|
+
|
146
|
+
❯ bundle exec ruby test/benchmarks/simple_api.rb
|
147
|
+
Warming up --------------------------------------
|
148
|
+
stdlib 60.617k i/100ms
|
149
|
+
uuid4 106.927k i/100ms
|
150
|
+
uuid6 111.417k i/100ms
|
151
|
+
uuid7 103.551k i/100ms
|
152
|
+
Calculating -------------------------------------
|
153
|
+
stdlib 626.957k (± 0.3%) i/s - 3.152M in 5.027633s
|
154
|
+
uuid4 1.077M (± 1.2%) i/s - 5.453M in 5.065726s
|
155
|
+
uuid6 1.121M (± 0.2%) i/s - 5.682M in 5.067392s
|
156
|
+
uuid7 1.027M (± 1.3%) i/s - 5.178M in 5.041666s
|
157
|
+
|
158
|
+
Comparison:
|
159
|
+
uuid6: 1121344.6 i/s
|
160
|
+
uuid4: 1076662.4 i/s - 1.04x (± 0.00) slower
|
161
|
+
uuid7: 1027127.8 i/s - 1.09x (± 0.00) slower
|
162
|
+
stdlib: 626957.1 i/s - 1.79x (± 0.00) slower
|
163
|
+
|
164
|
+
|
165
|
+
As reported, the `stdlib` version of `SecureRandom.uuid` is at least 1.70x
|
166
|
+
slower than the simple API implementation of UUID v4.
|
167
|
+
|
168
|
+
These timings are good enough that they shouldn't get in the way of any web
|
169
|
+
frameworks.
|
170
|
+
|
171
|
+
## Development
|
172
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
173
|
+
`rake test` to run the tests. You can also run `bin/console` for an interactive
|
174
|
+
prompt that will allow you to experiment.
|
175
|
+
|
176
|
+
To run tests across all supported Ruby versions on Debian and Alpine Linux run
|
177
|
+
`make test-all`. To run a specific version use `make test-[version]`;
|
178
|
+
for example, `make test-3.2.0`.
|
179
|
+
|
180
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
181
|
+
|
182
|
+
To release a new version:
|
183
|
+
|
184
|
+
1. ensure the documentation and changelog are ready
|
185
|
+
2. run `bundle exec rake rdoc` to generate the new documentation and commit it
|
186
|
+
3. update the version number in `lib/uuid/gem_version.rb`
|
187
|
+
4. run `bundle install` to update the `Gemfile.lock`
|
188
|
+
5. create a release commit with the version updates
|
189
|
+
6. run `bundle exec rake release` to tag and push the version to RubyGems
|
190
|
+
|
191
|
+
## Contributing
|
192
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tinychameleon/uuidx.
|
193
|
+
|
194
|
+
## License
|
195
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/lib/uuid/errors.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module Uuid
|
6
|
+
# UUID Version 7 defined by the
|
7
|
+
# {RFC 4122 BIS-01 Draft}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-uuid-version-4].
|
8
|
+
#
|
9
|
+
# To construct a new UUID v4 value create a generator, then use #generate.
|
10
|
+
# g = Uuid::Version4.new
|
11
|
+
# g.generate # => "2b54639d-e43e-489f-9c64-30ecdcac3c95"
|
12
|
+
#
|
13
|
+
# The implementation will cache 1024 bytes of random data from +SecureRandom+ to facilitate faster construction.
|
14
|
+
class Version4
|
15
|
+
VERSION_VARIANT = (0x4 << 76) | (0x2 << 62) # :nodoc:
|
16
|
+
BUFFER_SIZE = 64 # :nodoc:
|
17
|
+
NEEDED_BYTES = BUFFER_SIZE * 16 # :nodoc:
|
18
|
+
UNPACK_FORMAT = "QQ" * BUFFER_SIZE # :nodoc:
|
19
|
+
RANDOM_AB_MASK = 0xffffffff_ffff0fff # :nodoc:
|
20
|
+
AB_SHIFT = 64 # :nodoc:
|
21
|
+
RANDOM_C_MASK = 0x3fffffff_ffffffff # :nodoc:
|
22
|
+
|
23
|
+
# Construct a UUID v4 generator.
|
24
|
+
def initialize
|
25
|
+
@pool = []
|
26
|
+
end
|
27
|
+
|
28
|
+
# Construct a UUID v4 value.
|
29
|
+
def generate
|
30
|
+
@pool = SecureRandom.bytes(NEEDED_BYTES).unpack(UNPACK_FORMAT) if @pool.empty?
|
31
|
+
ab, c = @pool.pop(2)
|
32
|
+
|
33
|
+
Uuid.format(VERSION_VARIANT | ((ab & RANDOM_AB_MASK) << AB_SHIFT) | (c & RANDOM_C_MASK))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module Uuid
|
6
|
+
# UUID Version 6 defined by the
|
7
|
+
# {RFC 4122 BIS-01 Draft}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-uuid-version-6].
|
8
|
+
#
|
9
|
+
# To construct a new UUID v6 value create a generator, then use #generate.
|
10
|
+
# g = Uuid::Version6.new
|
11
|
+
# g.generate # => "1eda9761-9f6f-6414-8c5f-fd61f1239907"
|
12
|
+
#
|
13
|
+
# The implementation will use +SecureRandom+ to populate the Node and Clock Sequence bits with a random value
|
14
|
+
# at module load time.
|
15
|
+
#
|
16
|
+
# Generation is thread-safe, but if you are using multi-process clusters you should call #reset! at the start
|
17
|
+
# of each process to reduce the chance of two processes generating the same value.
|
18
|
+
#
|
19
|
+
# If you have need to make sure that the clock resolution is sufficient for the v6 specification
|
20
|
+
# you can call ::verify_clock_resolution! and handle the ClockResolutionError as you see fit.
|
21
|
+
#
|
22
|
+
# begin
|
23
|
+
# Uuid::Version6.verify_clock_resolution!
|
24
|
+
# rescue Uuid::ClockResolutionError
|
25
|
+
# # ...
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# The necessary clock resolution for v6 is 100ns.
|
29
|
+
#
|
30
|
+
# ===== A Note on Clock Timings
|
31
|
+
# To combat clock drift, leap-second smearing, and other clock value changes that can appear without
|
32
|
+
# requiring additional compute cost this implementation always increments the clock sequence number.
|
33
|
+
class Version6
|
34
|
+
VERSION_VARIANT = (0x6 << 76) + (0x2 << 62) # :nodoc:
|
35
|
+
GREGORIAN_MICROSECOND_TENTHS = 122_192_928_000_000_000 # :nodoc:
|
36
|
+
CLOCK_SEQ_SHIFT = 48 # :nodoc:
|
37
|
+
CLOCK_SEQ_INCREMENT = 1 << CLOCK_SEQ_SHIFT # :nodoc:
|
38
|
+
CLOCK_SEQ_MASK = 0x3fff << CLOCK_SEQ_SHIFT # :nodoc:
|
39
|
+
TS_NS_FACTOR = 100 # :nodoc:
|
40
|
+
TS_LOW_MASK = 0xfff # :nodoc:
|
41
|
+
TS_HIGH_MID_MASK = 0xffffffff_ffff0000 # :nodoc:
|
42
|
+
TS_MASK_SHIFT = 4 # :nodoc:
|
43
|
+
TS_POSITIONAL_SHIFT = 64 # :nodoc:
|
44
|
+
NODE_ID_MASK = 0xffff_ffffffff # :nodoc:
|
45
|
+
NODE_ID_MC_BIT = 0x0100_00000000 # :nodoc:
|
46
|
+
|
47
|
+
# Construct a new UUID v6 generator.
|
48
|
+
def initialize
|
49
|
+
reset!
|
50
|
+
end
|
51
|
+
|
52
|
+
# Construct a UUID v6 value.
|
53
|
+
def generate
|
54
|
+
@clock_sequence = (@clock_sequence + CLOCK_SEQ_INCREMENT) & CLOCK_SEQ_MASK
|
55
|
+
|
56
|
+
ts = GREGORIAN_MICROSECOND_TENTHS + (Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) / TS_NS_FACTOR)
|
57
|
+
ts = ((ts << TS_MASK_SHIFT) & TS_HIGH_MID_MASK) | (ts & TS_LOW_MASK)
|
58
|
+
|
59
|
+
Uuid.format(VERSION_VARIANT | (ts << TS_POSITIONAL_SHIFT) | @clock_sequence | @node_id)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Reset the generator with a new random node ID and clock sequence.
|
63
|
+
#
|
64
|
+
# This method is not thread-safe and should only be called at application or child process start.
|
65
|
+
def reset!
|
66
|
+
@node_id = (SecureRandom.bytes(8).unpack1("Q") & NODE_ID_MASK) | NODE_ID_MC_BIT
|
67
|
+
@clock_sequence = SecureRandom.bytes(4).unpack1("L") << CLOCK_SEQ_SHIFT
|
68
|
+
end
|
69
|
+
|
70
|
+
# Verify that the clock resolution is capable of 100ns resolution.
|
71
|
+
#
|
72
|
+
# Raises ClockResolutionError when the clock resolution is insufficient.
|
73
|
+
def self.verify_clock_resolution!
|
74
|
+
ns_res = Process.clock_getres(Process::CLOCK_REALTIME, :nanosecond)
|
75
|
+
raise ClockResolutionError, "Detected #{ns_res}ns resolution, need <= 100ns" if ns_res > 100
|
76
|
+
|
77
|
+
true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module Uuid
|
6
|
+
# UUID Version 7 defined by the
|
7
|
+
# {RFC 4122 BIS-01 Draft}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-uuid-version-7].
|
8
|
+
#
|
9
|
+
# To construct a new UUID v7 value create a generator, then use #generate.
|
10
|
+
# g = Uuid::Version7.new
|
11
|
+
# g.generate # => "01863d24-6d1e-78ba-92ee-6e80c79c4e28"
|
12
|
+
#
|
13
|
+
# The implementation will cache 640 bytes of random data from +SecureRandom+ to facilitate faster construction.
|
14
|
+
#
|
15
|
+
# If you have need to make sure that the clock resolution is sufficient for the v7 specification
|
16
|
+
# you can call ::verify_clock_resolution! and handle the ClockResolutionError as you see fit.
|
17
|
+
#
|
18
|
+
# begin
|
19
|
+
# Uuid::Version7.verify_clock_resolution!
|
20
|
+
# rescue Uuid::ClockResolutionError
|
21
|
+
# # ...
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# The necessary clock resolution for v6 is 1ms.
|
25
|
+
#
|
26
|
+
# ===== A Note on Clock Timings
|
27
|
+
# To combat clock drift, leap-second smearing, and other clock value changes that can appear without
|
28
|
+
# requiring additional compute cost this implementation relies on secure random data to have sufficient
|
29
|
+
# variation such that all generated UUIDs within 1ms have a low probability of collision.
|
30
|
+
#
|
31
|
+
# For a 0.001% chance of collision, the 74 bits of random data will require between 6.1×10^6 and 4.0×10^11
|
32
|
+
# UUIDs to be generated in 1ms.
|
33
|
+
class Version7
|
34
|
+
VERSION_VARIANT = (0x7 << 76) | (0x2 << 62) # :nodoc:
|
35
|
+
BUFFER_SIZE = 64 # :nodoc:
|
36
|
+
NEEDED_BYTES = BUFFER_SIZE * 10 # :nodoc:
|
37
|
+
UNPACK_FORMAT = "SQ" * BUFFER_SIZE # :nodoc:
|
38
|
+
TS_MASK = 0xffff_ffffffff # :nodoc:
|
39
|
+
RAND_A_MASK = 0xfff # :nodoc:
|
40
|
+
RAND_B_MASK = 0x3fffffff_ffffffff # :nodoc:
|
41
|
+
TS_SHIFT = 16 # :nodoc:
|
42
|
+
HIGH_SHIFT = 64 # :nodoc:
|
43
|
+
|
44
|
+
# Construct a UUID v7 generator.
|
45
|
+
def initialize
|
46
|
+
@pool = []
|
47
|
+
end
|
48
|
+
|
49
|
+
# Construct a UUID v7 value.
|
50
|
+
def generate
|
51
|
+
@pool = SecureRandom.bytes(NEEDED_BYTES).unpack(UNPACK_FORMAT) if @pool.empty?
|
52
|
+
a, b = @pool.pop(2)
|
53
|
+
ts = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) & TS_MASK
|
54
|
+
high = (ts << TS_SHIFT) | (a & RAND_A_MASK)
|
55
|
+
|
56
|
+
Uuid.format(VERSION_VARIANT | (high << HIGH_SHIFT) | (b & RAND_B_MASK))
|
57
|
+
end
|
58
|
+
|
59
|
+
# Verify that the clock resolution is capable of 1ms resolution.
|
60
|
+
#
|
61
|
+
# Raises ClockResolutionError when the clock resolution is insufficient.
|
62
|
+
def self.verify_clock_resolution!
|
63
|
+
ms_res = Process.clock_getres(Process::CLOCK_REALTIME, :millisecond)
|
64
|
+
raise ClockResolutionError, "Detected #{ms_res}ms resolution, need <= 1ms" if ms_res > 1
|
65
|
+
|
66
|
+
true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Uuid
|
4
|
+
# UUID Version 8 defined by the
|
5
|
+
# {RFC 4122 BIS-01 Draft}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-uuid-version-8].
|
6
|
+
#
|
7
|
+
# Since UUID v8 is entirely custom to your application, first create a generator definition class.
|
8
|
+
# class MyGeneratorDefinition
|
9
|
+
# def custom_a
|
10
|
+
# 1
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# def custom_b
|
14
|
+
# 2
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def custom_c
|
18
|
+
# 3
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# The requirements for each method are:
|
23
|
+
#
|
24
|
+
# - +custom_a+ should generate a 48-bit value which will be the most significant 6 octets.
|
25
|
+
# - +custom_b+ should generate a 12-bit value which is placed between the version and variant bits.
|
26
|
+
# - +custom_c+ should generate a 62-bit value which acts as the remaining least significant octets.
|
27
|
+
#
|
28
|
+
# Then create a UUID v8 generator by passing in the class, and call #generate.
|
29
|
+
# g = Uuid::Version8.new(MyGeneratorDefinition)
|
30
|
+
# g.generate # => "00000000-0001-8002-8000-000000000003"
|
31
|
+
#
|
32
|
+
# The implementation will truncate the results of each generator module method so that they abide by the bit lengths
|
33
|
+
# of the UUID specification. The thread safety of UUID v8 depends entirely on your implementation.
|
34
|
+
#
|
35
|
+
# There is no default implementation of UUID v8.
|
36
|
+
class Version8
|
37
|
+
VERSION_VARIANT = (0x8 << 76) | (0x2 << 62) # :nodoc:
|
38
|
+
CUSTOM_A_MASK = 0xffff_ffffffff # :nodoc:
|
39
|
+
CUSTOM_B_MASK = 0xfff # :nodoc:
|
40
|
+
CUSTOM_C_MASK = 0x3fffffff_ffffffff # :nodoc:
|
41
|
+
A_SHIFT = 16 # :nodoc:
|
42
|
+
HIGH_SHIFT = 64 # :nodoc:
|
43
|
+
|
44
|
+
# Construct a UUID v8 generator.
|
45
|
+
def initialize(definition_class)
|
46
|
+
@definition = definition_class.new
|
47
|
+
end
|
48
|
+
|
49
|
+
# Construct a UUID v8 value.
|
50
|
+
def generate
|
51
|
+
a = @definition.custom_a & CUSTOM_A_MASK
|
52
|
+
b = @definition.custom_b & CUSTOM_B_MASK
|
53
|
+
high = (a << A_SHIFT) | b
|
54
|
+
c = @definition.custom_c & CUSTOM_C_MASK
|
55
|
+
|
56
|
+
Uuid.format(VERSION_VARIANT | (high << HIGH_SHIFT) | c)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/uuid.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require_relative "uuid/gem_version"
|
5
|
+
require_relative "uuid/errors"
|
6
|
+
require_relative "uuid/version4"
|
7
|
+
require_relative "uuid/version6"
|
8
|
+
require_relative "uuid/version7"
|
9
|
+
require_relative "uuid/version8"
|
10
|
+
|
11
|
+
# The Uuid module contains a simple API to generate v4, v6, and v7 UUIDs
|
12
|
+
# without needing to create generators manually.
|
13
|
+
#
|
14
|
+
# The simple API is exposed as a set of methods ::v4, ::v6, and ::v7 which
|
15
|
+
# handle thread-safety and generator creation.
|
16
|
+
#
|
17
|
+
# Uuid.v4 # => "2b54639d-e43e-489f-9c64-30ecdcac3c95"
|
18
|
+
# Uuid.v6 # => "1eda9761-9f6f-6414-8c5f-fd61f1239907"
|
19
|
+
# Uuid.v7 # => "01863d24-6d1e-78ba-92ee-6e80c79c4e28"
|
20
|
+
#
|
21
|
+
# See the Version4, Version6, and Version7 classes for details on how to create
|
22
|
+
# generators manually.
|
23
|
+
#
|
24
|
+
# ===== Monotonic Batching
|
25
|
+
# The simple API also provides thread-safe monotonic batch methods which expect
|
26
|
+
# an amount.
|
27
|
+
#
|
28
|
+
# Uuid.batch_v4(10) # => ["2b54639d-e43e-489f-9c64-30ecdcac3c95", ...]
|
29
|
+
# Uuid.batch_v6(10) # => ["1eda9761-9f6f-6414-8c5f-fd61f1239907", ...]
|
30
|
+
# Uuid.batch_v7(10) # => ["01863d24-6d1e-78ba-92ee-6e80c79c4e28", ...]
|
31
|
+
#
|
32
|
+
# Monotonicity has little meaning with UUID v4, but the batches are ordered for
|
33
|
+
# consistency.
|
34
|
+
#
|
35
|
+
# ===== A Note on Clock Timings
|
36
|
+
# This library uses the +Process::CLOCK_REALTIME+ clock ID to obtain the current
|
37
|
+
# time. While the specification allows for implementations to manipulate time
|
38
|
+
# values, this library does not. Any system-based smearing or drift will appear
|
39
|
+
# within the timestamp values.
|
40
|
+
#
|
41
|
+
# See the Version6 and Version7 documentation for manifestation details.
|
42
|
+
module Uuid
|
43
|
+
# The nil UUID as defined by
|
44
|
+
# {§5.10 of RFC 4122 BIS-01}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-nil-uuid].
|
45
|
+
#
|
46
|
+
# This UUID is written as +00000000-0000-0000-0000-000000000000+ and is less than all other UUIDs in comparisons.
|
47
|
+
# It does not act as +nil+.
|
48
|
+
def self.nil_uuid
|
49
|
+
"00000000-0000-0000-0000-000000000000"
|
50
|
+
end
|
51
|
+
|
52
|
+
# The maximum UUID as defined by
|
53
|
+
# {§5.10 of RFC 4122 BIS-01}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-01.html#name-max-uuid].
|
54
|
+
#
|
55
|
+
# This UUID is written as +ffffffff-ffff-ffff-ffff-ffffffffffff+ and is greater than all other UUIDs in comparisons.
|
56
|
+
def self.max_uuid
|
57
|
+
"ffffffff-ffff-ffff-ffff-ffffffffffff"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Generate a new UUID v4 value using the default generator.
|
61
|
+
def self.v4
|
62
|
+
@lock4.synchronize { @uuid4.generate }
|
63
|
+
end
|
64
|
+
|
65
|
+
# Generate a new UUID v6 value using the default generator.
|
66
|
+
def self.v6
|
67
|
+
@lock6.synchronize { @uuid6.generate }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Generate a new UUID v7 value using the default generator.
|
71
|
+
def self.v7
|
72
|
+
@lock7.synchronize { @uuid7.generate }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Create a batch of UUID v4 values using the default generator.
|
76
|
+
def self.batch_v4(amount)
|
77
|
+
@lock4.synchronize { batch(@uuid4, amount) }
|
78
|
+
end
|
79
|
+
|
80
|
+
# Create a batch of UUID v6 values using the default generator.
|
81
|
+
def self.batch_v6(amount)
|
82
|
+
@lock6.synchronize { batch(@uuid6, amount) }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Create a batch of UUID v7 values using the default generator.
|
86
|
+
def self.batch_v7(amount)
|
87
|
+
@lock7.synchronize { batch(@uuid7, amount) }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Reset the UUID v4 default generator.
|
91
|
+
def self.reset_v4!
|
92
|
+
@lock4 = Mutex.new
|
93
|
+
@uuid4 = Version4.new
|
94
|
+
end
|
95
|
+
|
96
|
+
# Reset the UUID v6 default generator.
|
97
|
+
def self.reset_v6!
|
98
|
+
@lock6 = Mutex.new
|
99
|
+
@uuid6 = Version6.new
|
100
|
+
end
|
101
|
+
|
102
|
+
# Reset the UUID v7 default generator.
|
103
|
+
def self.reset_v7!
|
104
|
+
@lock7 = Mutex.new
|
105
|
+
@uuid7 = Version7.new
|
106
|
+
end
|
107
|
+
|
108
|
+
# Create a batch of UUIDs from a generator.
|
109
|
+
#
|
110
|
+
# This can be useful for custom Version8 generators.
|
111
|
+
def self.batch(generator, amount)
|
112
|
+
s = Set.new
|
113
|
+
s << generator.generate while s.length < amount
|
114
|
+
s.sort
|
115
|
+
end
|
116
|
+
|
117
|
+
reset_v4!
|
118
|
+
reset_v6!
|
119
|
+
reset_v7!
|
120
|
+
|
121
|
+
# :nodoc:
|
122
|
+
def self.format(value)
|
123
|
+
b = +value.to_s(16).rjust(32, "0")
|
124
|
+
b.insert(8, "-")
|
125
|
+
b.insert(13, "-")
|
126
|
+
b.insert(18, "-")
|
127
|
+
b.insert(23, "-")
|
128
|
+
b.freeze
|
129
|
+
end
|
130
|
+
end
|
data/sig/uuid/errors.rbs
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Uuid
|
2
|
+
class Version8
|
3
|
+
public
|
4
|
+
|
5
|
+
interface _GeneratorDefinition
|
6
|
+
def custom_a: () -> Integer
|
7
|
+
def custom_b: () -> Integer
|
8
|
+
def custom_c: () -> Integer
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize: [Definition < _GeneratorDefinition] (Definition) -> void
|
12
|
+
def generate: () -> uuid
|
13
|
+
end
|
14
|
+
end
|
data/sig/uuid.rbs
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# See the writing guide of rbs: https://github.com/ruby/rbs#guides
|
2
|
+
|
3
|
+
module Uuid
|
4
|
+
VERSION: String
|
5
|
+
|
6
|
+
type uuid = String
|
7
|
+
|
8
|
+
public
|
9
|
+
|
10
|
+
interface _Generator
|
11
|
+
def generate: () -> uuid
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.nil_uuid: () -> uuid
|
15
|
+
def self.max_uuid: () -> uuid
|
16
|
+
def self.v4: () -> uuid
|
17
|
+
def self.v6: () -> uuid
|
18
|
+
def self.v7: () -> uuid
|
19
|
+
def self.batch_v4: (Integer) -> Array[uuid]
|
20
|
+
def self.batch_v6: (Integer) -> Array[uuid]
|
21
|
+
def self.batch_v7: (Integer) -> Array[uuid]
|
22
|
+
def self.reset_v4!: () -> void
|
23
|
+
def self.reset_v6!: () -> void
|
24
|
+
def self.reset_v7!: () -> void
|
25
|
+
def self.batch: (_Generator, Integer) -> Array[uuid]
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def self.format: (Integer) -> uuid
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: uuidx
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stephan Tarulli
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-02-11 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: "A fast Ruby implementation of UUID versions 4, 6, 7, and 8 \U0001FAAA"
|
14
|
+
email:
|
15
|
+
- srt@tinychameleon.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- CHANGELOG.md
|
21
|
+
- LICENSE.txt
|
22
|
+
- README.md
|
23
|
+
- lib/uuid.rb
|
24
|
+
- lib/uuid/errors.rb
|
25
|
+
- lib/uuid/gem_version.rb
|
26
|
+
- lib/uuid/version4.rb
|
27
|
+
- lib/uuid/version6.rb
|
28
|
+
- lib/uuid/version7.rb
|
29
|
+
- lib/uuid/version8.rb
|
30
|
+
- sig/uuid.rbs
|
31
|
+
- sig/uuid/errors.rbs
|
32
|
+
- sig/uuid/version4.rbs
|
33
|
+
- sig/uuid/version6.rbs
|
34
|
+
- sig/uuid/version7.rbs
|
35
|
+
- sig/uuid/version8.rbs
|
36
|
+
homepage: https://github.com/tinychameleon/uuidx
|
37
|
+
licenses:
|
38
|
+
- MIT
|
39
|
+
metadata:
|
40
|
+
rubygems_mfa_required: 'true'
|
41
|
+
homepage_uri: https://github.com/tinychameleon/uuidx
|
42
|
+
source_code_uri: https://github.com/tinychameleon/uuidx
|
43
|
+
changelog_uri: https://github.com/tinychameleon/uuidx/blob/main/CHANGELOG.md
|
44
|
+
bug_tracker_uri: https://github.com/tinychameleon/uuidx/issues
|
45
|
+
documentation_uri: https://tinychameleon.github.io/uuidx/
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.7.0
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubygems_version: 3.4.3
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: "A fast Ruby implementation of UUID versions 4, 6, 7, and 8 \U0001FAAA"
|
65
|
+
test_files: []
|