ruby-ulid 0.0.18 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,70 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+ # Copyright (C) 2021 Kenichi Kamiya
4
+
5
+ class ULID
6
+ # Currently supporting only for `subset` for actual use-case`
7
+ # Original decoding spec allows other characters.
8
+ # But I think ULID should allow `subset` of Crockford's Base32.
9
+ # See below
10
+ # * https://github.com/ulid/spec/pull/57
11
+ # * https://github.com/kachick/ruby-ulid/issues/57
12
+ # * https://github.com/kachick/ruby-ulid/issues/78
13
+ module CrockfordBase32
14
+ class SetupError < ScriptError; end
15
+
16
+ n32_chars = [*'0'..'9', *'A'..'V'].map(&:freeze).freeze
17
+ raise SetupError, 'obvious bug exists in the mapping algorithm' unless n32_chars.size == 32
18
+
19
+ n32_char_by_number = {}
20
+ n32_chars.each_with_index do |char, index|
21
+ n32_char_by_number[index] = char
22
+ end
23
+ n32_char_by_number.freeze
24
+
25
+ crockford_base32_mappings = {
26
+ 'J' => 18,
27
+ 'K' => 19,
28
+ 'M' => 20,
29
+ 'N' => 21,
30
+ 'P' => 22,
31
+ 'Q' => 23,
32
+ 'R' => 24,
33
+ 'S' => 25,
34
+ 'T' => 26,
35
+ 'V' => 27,
36
+ 'W' => 28,
37
+ 'X' => 29,
38
+ 'Y' => 30,
39
+ 'Z' => 31
40
+ }.freeze
41
+
42
+ N32_CHAR_BY_CROCKFORD_BASE32_CHAR = CROCKFORD_BASE32_ENCODING_STRING.chars.map(&:freeze).each_with_object({}) do |encoding_char, map|
43
+ if n = crockford_base32_mappings[encoding_char]
44
+ char_32 = n32_char_by_number.fetch(n)
45
+ map[encoding_char] = char_32
46
+ end
47
+ end.freeze
48
+ raise SetupError, 'obvious bug exists in the mapping algorithm' unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
49
+ CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
50
+
51
+ CROCKFORD_BASE32_CHAR_BY_N32_CHAR = N32_CHAR_BY_CROCKFORD_BASE32_CHAR.invert.freeze
52
+ N32_CHAR_PATTERN = /[#{CROCKFORD_BASE32_CHAR_BY_N32_CHAR.keys.join}]/.freeze
53
+
54
+ # @api private
55
+ # @param [String] string
56
+ # @return [Integer]
57
+ def self.decode(string)
58
+ n32encoded = string.upcase.gsub(CROCKFORD_BASE32_CHAR_PATTERN, N32_CHAR_BY_CROCKFORD_BASE32_CHAR)
59
+ n32encoded.to_i(32)
60
+ end
61
+
62
+ # @api private
63
+ # @param [Integer] integer
64
+ # @return [String]
65
+ def self.encode(integer)
66
+ n32encoded = integer.to_s(32)
67
+ n32encoded.upcase.gsub(N32_CHAR_PATTERN, CROCKFORD_BASE32_CHAR_BY_N32_CHAR).rjust(ENCODED_LENGTH, '0')
68
+ end
69
+ end
70
+ end
@@ -4,39 +4,60 @@
4
4
 
5
5
  class ULID
6
6
  class MonotonicGenerator
7
- # @api private
8
- attr_accessor :latest_milliseconds, :latest_entropy
7
+ # @return [ULID, nil]
8
+ attr_reader :prev
9
+
10
+ undef_method :instance_variable_set
9
11
 
10
12
  def initialize
11
- reset
13
+ @mutex = Thread::Mutex.new
14
+ @prev = nil
15
+ end
16
+
17
+ # @return [String]
18
+ def inspect
19
+ "ULID::MonotonicGenerator(prev: #{@prev.inspect})"
12
20
  end
21
+ alias_method :to_s, :inspect
13
22
 
14
23
  # @param [Time, Integer] moment
15
24
  # @return [ULID]
16
25
  # @raise [OverflowError] if the entropy part is larger than the ULID limit in same milliseconds
17
- # @raise [ArgumentError] if the given moment(milliseconds) is negative number
26
+ # @raise [UnexpectedError] if the generated ULID is an invalid value in monotonicity spec.
27
+ # Basically will not happen. Just means this feature prefers error rather than invalid value.
18
28
  def generate(moment: ULID.current_milliseconds)
19
- milliseconds = ULID.milliseconds_from_moment(moment)
20
- raise ArgumentError, "milliseconds should not be negative: given: #{milliseconds}" if milliseconds.negative?
21
-
22
- if @latest_milliseconds < milliseconds
23
- @latest_milliseconds = milliseconds
24
- @latest_entropy = ULID.reasonable_entropy
25
- else
26
- @latest_entropy += 1
27
- end
29
+ @mutex.synchronize do
30
+ unless @prev
31
+ @prev = ULID.generate(moment: moment)
32
+ return @prev
33
+ end
28
34
 
29
- ULID.from_monotonic_generator(self)
30
- end
35
+ milliseconds = ULID.milliseconds_from_moment(moment)
31
36
 
32
- # @api private
33
- # @return [void]
34
- def reset
35
- @latest_milliseconds = 0
36
- @latest_entropy = ULID.reasonable_entropy
37
- nil
37
+ ulid = if @prev.milliseconds < milliseconds
38
+ ULID.generate(moment: milliseconds)
39
+ else
40
+ ULID.from_milliseconds_and_entropy(milliseconds: @prev.milliseconds, entropy: @prev.entropy.succ)
41
+ end
42
+
43
+ unless ulid > @prev
44
+ base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{@prev.inspect}"
45
+ additional_information = if Thread.list == [Thread.main]
46
+ '# NOTE: looks single thread only exist'
47
+ else
48
+ '# NOTE: ran on multi threads, so this might from concurrency issue'
49
+ end
50
+
51
+ raise UnexpectedError, base_message + additional_information
52
+ end
53
+
54
+ @prev = ulid
55
+ ulid
56
+ end
38
57
  end
39
58
 
59
+ undef_method :freeze
60
+
40
61
  # @raise [TypeError] always raises exception and does not freeze self
41
62
  # @return [void]
42
63
  def freeze
data/lib/ulid/uuid.rb CHANGED
@@ -15,15 +15,16 @@ class ULID
15
15
  # @return [ULID]
16
16
  # @raise [ParserError] if the given format is not correct for UUIDv4 specs
17
17
  def self.from_uuidv4(uuid)
18
- begin
19
- uuid = uuid.to_str
20
- prefix_trimmed = uuid.sub(/\Aurn:uuid:/, '')
21
- raise "given string is not matched to pattern #{UUIDV4_PATTERN.inspect}" unless UUIDV4_PATTERN.match?(prefix_trimmed)
22
- normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
23
- from_integer(normalized.to_i(16))
24
- rescue => err
25
- raise ParserError, "parsing failure as #{err.inspect} for given #{uuid}"
18
+ uuid = String.try_convert(uuid)
19
+ raise ArgumentError, 'ULID.from_uuidv4 takes only strings' unless uuid
20
+
21
+ prefix_trimmed = uuid.delete_prefix('urn:uuid:')
22
+ unless UUIDV4_PATTERN.match?(prefix_trimmed)
23
+ raise ParserError, "given `#{uuid}` does not match to `#{UUIDV4_PATTERN.inspect}`"
26
24
  end
25
+
26
+ normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
27
+ from_integer(normalized.to_i(16))
27
28
  end
28
29
 
29
30
  # @return [String]
data/lib/ulid/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class ULID
5
- VERSION = '0.0.18'
5
+ VERSION = '0.1.3'
6
6
  end
data/sig/ulid.rbs CHANGED
@@ -1,6 +1,6 @@
1
- # Classes
2
- class ULID
1
+ class ULID < Object
3
2
  VERSION: String
3
+ CROCKFORD_BASE32_ENCODING_STRING: String
4
4
  TIMESTAMP_ENCODED_LENGTH: 10
5
5
  RANDOMNESS_ENCODED_LENGTH: 16
6
6
  ENCODED_LENGTH: 26
@@ -11,16 +11,13 @@ class ULID
11
11
  MAX_ENTROPY: 1208925819614629174706175
12
12
  MAX_INTEGER: 340282366920938463463374607431768211455
13
13
  TIME_FORMAT_IN_INSPECT: '%Y-%m-%d %H:%M:%S.%3N %Z'
14
- PATTERN: Regexp
15
- STRICT_PATTERN: Regexp
14
+ RANDOM_INTEGER_GENERATOR: ^() -> Integer
15
+ PATTERN_WITH_CROCKFORD_BASE32_SUBSET: Regexp
16
+ STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET: Regexp
17
+ SCANNING_PATTERN: Regexp
16
18
  UUIDV4_PATTERN: Regexp
17
- N32_CHAR_BY_CROCKFORD_BASE32_CHAR: Hash[String, String]
18
- CROCKFORD_BASE32_CHAR_PATTERN: Regexp
19
- CROCKFORD_BASE32_CHAR_BY_N32_CHAR: Hash[String, String]
20
- N32_CHAR_PATTERN: Regexp
21
19
  MIN: ULID
22
20
  MAX: ULID
23
- UNDEFINED: BasicObject
24
21
  include Comparable
25
22
 
26
23
  # The `moment` is a `Time` or `Intger of the milliseconds`
@@ -35,84 +32,462 @@ class ULID
35
32
  class ParserError < Error
36
33
  end
37
34
 
38
- class SetupError < ScriptError
35
+ class UnexpectedError < Error
36
+ end
37
+
38
+ module CrockfordBase32
39
+ class SetupError < ScriptError
40
+ end
41
+
42
+ N32_CHAR_BY_CROCKFORD_BASE32_CHAR: Hash[String, String]
43
+ CROCKFORD_BASE32_CHAR_PATTERN: Regexp
44
+ CROCKFORD_BASE32_CHAR_BY_N32_CHAR: Hash[String, String]
45
+ N32_CHAR_PATTERN: Regexp
46
+
47
+ # A pribate API. Should not be used in your code.
48
+ def self.encode: (Integer integer) -> String
49
+
50
+ # A pribate API. Should not be used in your code.
51
+ def self.decode: (String string) -> Integer
39
52
  end
40
53
 
41
54
  class MonotonicGenerator
42
- attr_accessor latest_milliseconds: Integer
43
- attr_accessor latest_entropy: Integer
55
+ @mutex: Thread::Mutex
56
+
57
+ # The returned value is `not` Thready-safety
58
+ attr_reader prev: ULID | nil
59
+
60
+ # A pribate API. Should not be used in your code.
44
61
  def initialize: -> void
62
+
63
+ # See [How to keep `Sortable` even if in same timestamp](https://github.com/kachick/ruby-ulid#how-to-keep-sortable-even-if-in-same-timestamp)
45
64
  def generate: (?moment: moment) -> ULID
46
- def reset: -> void
65
+
66
+ # The returned value is `not` Thready-safety
67
+ def inspect: -> String
68
+ alias to_s inspect
47
69
  def freeze: -> void
48
70
  end
49
71
 
72
+ interface _ToULID
73
+ def to_ulid: () -> ULID
74
+ end
75
+
50
76
  type octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
51
77
  type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
52
78
  type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
79
+ type period = Range[Time] | Range[nil] | Range[ULID]
53
80
 
54
- @milliseconds: Integer
55
- @entropy: Integer
56
81
  @string: String?
57
- @integer: Integer?
58
- @octets: octets?
59
- @timestamp_octets: timestamp_octets?
60
- @randomness_octets: randomness_octets?
82
+ @integer: Integer
61
83
  @timestamp: String?
62
84
  @randomness: String?
63
85
  @inspect: String?
64
86
  @time: Time?
65
- @next: ULID?
66
87
 
67
- def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
68
- def self.at: (Time time) -> ULID
88
+ # Retuns a ULID
89
+ #
90
+ # They are sortable when generated in different timestamp with milliseconds precision
91
+ #
92
+ # ```ruby
93
+ # ulids = 1000.times.map do
94
+ # sleep(0.001)
95
+ # ULID.generate
96
+ # end
97
+ # ulids.uniq(&:to_time).size #=> 1000
98
+ # ulids.sort == ulids #=> true
99
+ # ```
100
+ #
101
+ # `ULID.generate` can take fixed `Time` instance.
102
+ # See also the short hand [ULID.at](https://kachick.github.io/ruby-ulid/ULID.html#at-class_method)
103
+ #
104
+ # ```ruby
105
+ # time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
106
+ # ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
107
+ # ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
108
+ # ```
109
+ #
110
+ # The basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
111
+ #
112
+ # ```ruby
113
+ # ulids = 10000.times.map do
114
+ # ULID.generate
115
+ # end
116
+ # ulids.uniq(&:to_time).size #=> 35 (the size is not fixed, might be changed in environment)
117
+ # ulids.sort == ulids #=> false
118
+ # ```
119
+ #
120
+ # If you want to keep sortable even if in same timestamp, See also [ULID::MonotonicGenerator](https://github.com/kachick/ruby-ulid#how-to-keep-sortable-even-if-in-same-timestamp)
121
+ #
122
+ def self.generate: (?moment: moment, ?entropy: Integer) -> self
123
+
124
+ # Shorthand of `ULID.generate(moment: Time)`
125
+ # See also [ULID.generate](https://kachick.github.io/ruby-ulid/ULID.html#generate-class_method)
126
+ #
127
+ # ```ruby
128
+ # time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
129
+ # ULID.at(time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB002W5BGWWKN76N22H6)
130
+ #
131
+ # ulids = 1000.times.map do |n|
132
+ # ULID.at(time + n)
133
+ # end
134
+ # ulids.sort == ulids #=> true
135
+ # ```
136
+ def self.at: (Time time) -> self
137
+
138
+ # A pribate API. Should not be used in your code.
69
139
  def self.current_milliseconds: -> Integer
70
- def self.milliseconds_from_time: (Time time) -> Integer
140
+
141
+ # A pribate API. Should not be used in your code.
71
142
  def self.milliseconds_from_moment: (moment moment) -> Integer
72
- def self.range: (Range[Time] | Range[nil] time_range) -> Range[ULID]
143
+
144
+ # `ULID` can be element of the `Range`. If you generated the IDs in monotonic generator, ID based filtering is easy and reliable
145
+ #
146
+ # ```ruby
147
+ # include_end = ulid1..ulid2
148
+ # exclude_end = ulid1...ulid2
149
+ #
150
+ # ulids.grep(one_of_the_above)
151
+ # ulids.grep_v(one_of_the_above)
152
+ # ```
153
+ #
154
+ # When want to filter ULIDs with `Time`, we should consider to handle the precision.
155
+ # So this gem provides `ULID.range` to generate reasonable `Range[ULID]` from `Range[Time]`
156
+ #
157
+ # ```ruby
158
+ # # Both of below, The begin of `Range[ULID]` will be the minimum in the floored milliseconds of the time1
159
+ # include_end = ULID.range(time1..time2) #=> The end of `Range[ULID]` will be the maximum in the floored milliseconds of the time2
160
+ # exclude_end = ULID.range(time1...time2) #=> The end of `Range[ULID]` will be the minimum in the floored milliseconds of the time2
161
+ #
162
+ # # Below patterns are acceptable
163
+ # pinpointing = ULID.range(time1..time1) #=> This will match only for all IDs in `time1`
164
+ # until_the_end = ULID.range(..time1) #=> This will match only for all IDs upto `time1` (The `nil` starting `Range` can be used since Ruby 2.7)
165
+ # until_the_end = ULID.range(ULID.min.to_time..time1) #=> This is same as above for Ruby 2.6
166
+ # until_the_ulid_limit = ULID.range(time1..) # This will match only for all IDs from `time1` to max value of the ULID limit
167
+ #
168
+ # # So you can use the generated range objects as below
169
+ # ulids.grep(one_of_the_above)
170
+ # ulids.grep_v(one_of_the_above)
171
+ # #=> I hope the results should be actually you want!
172
+ # ```
173
+ #
174
+ def self.range: (period period) -> Range[ULID]
175
+
176
+ # Returns new `Time` with truncating excess precisions in ULID spec.
177
+ #
178
+ # ```ruby
179
+ # time = Time.at(946684800, Rational('123456.789')).utc
180
+ # #=> 2000-01-01 00:00:00.123456789 UTC
181
+ # ULID.floor(time) #=> 2000-01-01 00:00:00.123 UTC
182
+ # ```
73
183
  def self.floor: (Time time) -> Time
74
- def self.reasonable_entropy: -> Integer
75
- def self.parse: (String string) -> ULID
184
+
185
+ # Get ULID instance from encoded String.
186
+ #
187
+ # ```ruby
188
+ # ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV')
189
+ # #=> ULID(2016-07-30 23:54:10.259 UTC: 01ARZ3NDEKTSV4RRFFQ69G5FAV)
190
+ # ```
191
+ def self.parse: (_ToStr string) -> self
192
+
193
+ # ```ruby
194
+ # # Currently experimental feature, so needed to load the extension.
195
+ # require 'ulid/uuid'
196
+ #
197
+ # # Basically reversible
198
+ # ulid = ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
199
+ # #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
200
+ # ulid.to_uuidv4 #=> "0983d0a2-ff15-4d83-8f37-7dd945b5aa39"
201
+ # ```
202
+ #
203
+ # See also [Why this is experimental?](https://github.com/kachick/ruby-ulid/issues/76)
76
204
  def self.from_uuidv4: (String uuid) -> ULID
77
- def self.from_integer: (Integer integer) -> ULID
78
- def self.min: (?moment: moment) -> ULID
79
- def self.max: (?moment: moment) -> ULID
80
- def self.sample: -> ULID
81
- | (Integer number) -> Array[ULID]
82
- def self.valid?: (untyped string) -> bool
83
- def self.scan: (String string) -> Enumerator[ULID, singleton(ULID)]
84
- | (String string) { (ULID ulid) -> void } -> singleton(ULID)
85
- def self.octets_from_integer: (Integer integer) -> octets
86
- def self.inverse_of_digits: (Array[Integer] reversed_digits) -> Integer
87
- def self.from_monotonic_generator: (MonotonicGenerator generator) -> ULID
205
+ def self.from_integer: (Integer integer) -> self
206
+
207
+ # Returns termination values for ULID spec.
208
+ #
209
+ # ```ruby
210
+ # ULID.min
211
+ # #=> ULID(1970-01-01 00:00:00.000 UTC: 00000000000000000000000000)
212
+ # ```
213
+ #
214
+ # It can take `Time` instance as an optional argument.
215
+ # Then returns ULID that has minimum value of randomness part in the timestamp.
216
+ #
217
+ # ```ruby
218
+ # time = Time.at(946684800, Rational('123456.789')).utc
219
+ # #=> 2000-01-01 00:00:00.123456789 UTC
220
+ # ULID.min(time)
221
+ # #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
222
+ # ```
223
+ #
224
+ # See also [ULID.max](https://kachick.github.io/ruby-ulid/ULID.html#max-class_method)
225
+ def self.min: (?moment moment) -> ULID
226
+
227
+ # Returns termination values for ULID spec.
228
+ #
229
+ # ```ruby
230
+ # ULID.max
231
+ # #=> ULID(10889-08-02 05:31:50.655 UTC: 7ZZZZZZZZZZZZZZZZZZZZZZZZZ)
232
+ # ```
233
+ #
234
+ # It can take `Time` instance as an optional argument.
235
+ # Then returns ULID that has maximum value of randomness part in the timestamp.
236
+ #
237
+ # ```ruby
238
+ # time = Time.at(946684800, Rational('123456.789')).utc
239
+ # #=> 2000-01-01 00:00:00.123456789 UTC
240
+ # ULID.max(time)
241
+ # #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
242
+ # ```
243
+ #
244
+ # See also [ULID.min](https://kachick.github.io/ruby-ulid/ULID.html#min-class_method)
245
+ def self.max: (?moment moment) -> ULID
246
+
247
+ # Returns random ULIDs.
248
+ #
249
+ # Basically ignores generating time.
250
+ #
251
+ # ```ruby
252
+ # ULID.sample #=> ULID(2545-07-26 06:51:20.085 UTC: 0GGKQ45GMNMZR6N8A8GFG0ZXST)
253
+ # ULID.sample #=> ULID(5098-07-26 21:31:06.946 UTC: 2SSBNGGYA272J7BMDCG4Z6EEM5)
254
+ # ULID.sample(0) #=> []
255
+ # ULID.sample(1) #=> [ULID(2241-04-16 03:31:18.440 UTC: 07S52YWZ98AZ8T565MD9VRYMQH)]
256
+ # ULID.sample(5)
257
+ # #=>
258
+ # #[ULID(5701-04-29 12:41:19.647 UTC: 3B2YH2DV0ZYDDATGTYSKMM1CMT),
259
+ # # ULID(2816-08-01 01:21:46.612 UTC: 0R9GT6RZKMK3RG02Q2HAFVKEY2),
260
+ # # ULID(10408-10-05 17:06:27.848 UTC: 7J6CPTEEC86Y24EQ4F1Y93YYN0),
261
+ # # ULID(2741-09-02 16:24:18.803 UTC: 0P4Q4V34KKAJW46QW47WQB5463),
262
+ # # ULID(2665-03-16 14:50:22.724 UTC: 0KYFW9DWM4CEGFNTAC6YFAVVJ6)]
263
+ # ```
264
+ #
265
+ # You can specify a range object for the timestamp restriction, See also [ULID.range](https://kachick.github.io/ruby-ulid/ULID.html#range-class_method).
266
+ #
267
+ # ```ruby
268
+ # ulid1 = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
269
+ # ulid2 = ULID.parse('01F4PTVCSN9ZPFKYTY2DDJVRK4') #=> ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4)
270
+ # ulids = ULID.sample(1000, period: ulid1..ulid2)
271
+ # ulids.uniq.size #=> 1000
272
+ # ulids.take(10)
273
+ # #=>
274
+ # #[ULID(2021-05-02 06:57:19.954 UTC: 01F4NXW02JNB8H0J0TK48JD39X),
275
+ # # ULID(2021-05-02 07:06:07.458 UTC: 01F4NYC372GVP7NS0YAYQGT4VZ),
276
+ # # ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW),
277
+ # # ULID(2021-04-27 22:17:37.844 UTC: 01F4APHGSMFJZQTGXKZBFFBPJP),
278
+ # # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3),
279
+ # # ULID(2021-04-30 07:18:54.307 UTC: 01F4GTA2332AS2VPHC4FMKC7R5),
280
+ # # ULID(2021-05-02 12:26:03.480 UTC: 01F4PGNXARG554Y3HYVBDW4T9S),
281
+ # # ULID(2021-04-29 09:52:15.107 UTC: 01F4EGP483ZX2747FQPWQNPPMW),
282
+ # # ULID(2021-04-29 03:18:24.152 UTC: 01F4DT4Z4RA0QV8WFQGRAG63EH),
283
+ # # ULID(2021-05-02 13:27:16.394 UTC: 01F4PM605ABF5SDVMEHBH8JJ9R)]
284
+ # ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
285
+ # #=>
286
+ # # [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
287
+ # # ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
288
+ # # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
289
+ # # ULID(2021-05-01 03:06:09.130 UTC: 01F4JY7ZBABCBMX16XH2Q4JW4W),
290
+ # # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G),
291
+ # # ULID(2021-04-29 17:14:14.116 UTC: 01F4F9ZDQ449BE8BBZFEHYQWG2),
292
+ # # ULID(2021-04-30 16:18:08.205 UTC: 01F4HS5DPD1HWDVJNJ6YKJXKSK),
293
+ # # ULID(2021-04-30 10:31:33.602 UTC: 01F4H5ATF2A1CSQF0XV5NKZ288),
294
+ # # ULID(2021-04-28 16:49:06.484 UTC: 01F4CP4PDM214Q6H3KJP7DYJRR),
295
+ # # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
296
+ # ```
297
+ def self.sample: (?period: period) -> self
298
+ | (Integer number, ?period: period) -> Array[self]
299
+ def self.valid?: (untyped) -> bool
300
+
301
+ # Returns parsed ULIDs from given String for rough operations.
302
+ #
303
+ # ```ruby
304
+ # json =<<'EOD'
305
+ # {
306
+ # "id": "01F4GNAV5ZR6FJQ5SFQC7WDSY3",
307
+ # "author": {
308
+ # "id": "01F4GNBXW1AM2KWW52PVT3ZY9X",
309
+ # "name": "kachick"
310
+ # },
311
+ # "title": "My awesome blog post",
312
+ # "comments": [
313
+ # {
314
+ # "id": "01F4GNCNC3CH0BCRZBPPDEKBKS",
315
+ # "commenter": {
316
+ # "id": "01F4GNBXW1AM2KWW52PVT3ZY9X",
317
+ # "name": "kachick"
318
+ # }
319
+ # },
320
+ # {
321
+ # "id": "01F4GNCXAMXQ1SGBH5XCR6ZH0M",
322
+ # "commenter": {
323
+ # "id": "01F4GND4RYYSKNAADHQ9BNXAWJ",
324
+ # "name": "pankona"
325
+ # }
326
+ # }
327
+ # ]
328
+ # }
329
+ # EOD
330
+ #
331
+ # ULID.scan(json).to_a
332
+ # #=>
333
+ # # [ULID(2021-04-30 05:51:57.119 UTC: 01F4GNAV5ZR6FJQ5SFQC7WDSY3),
334
+ # # ULID(2021-04-30 05:52:32.641 UTC: 01F4GNBXW1AM2KWW52PVT3ZY9X),
335
+ # # ULID(2021-04-30 05:52:56.707 UTC: 01F4GNCNC3CH0BCRZBPPDEKBKS),
336
+ # # ULID(2021-04-30 05:52:32.641 UTC: 01F4GNBXW1AM2KWW52PVT3ZY9X),
337
+ # # ULID(2021-04-30 05:53:04.852 UTC: 01F4GNCXAMXQ1SGBH5XCR6ZH0M),
338
+ # # ULID(2021-04-30 05:53:12.478 UTC: 01F4GND4RYYSKNAADHQ9BNXAWJ)]
339
+ # ```
340
+ def self.scan: (_ToStr string) -> Enumerator[self, singleton(ULID)]
341
+ | (_ToStr string) { (self ulid) -> void } -> singleton(ULID)
342
+ def self.from_milliseconds_and_entropy: (milliseconds: Integer, entropy: Integer) -> self
343
+ def self.try_convert: (_ToULID) -> ULID
344
+ | (untyped) -> nil
345
+
346
+ # ```ruby
347
+ # ulid = ULID.generate
348
+ # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
349
+ # ulid.milliseconds #=> 1619544442826
350
+ # ```
88
351
  attr_reader milliseconds: Integer
89
352
  attr_reader entropy: Integer
90
- def initialize: (milliseconds: Integer, entropy: Integer, ?integer: Integer) -> void
353
+
354
+ # ```ruby
355
+ # ulid = ULID.generate
356
+ # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
357
+ # ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
358
+ # ```
91
359
  def to_s: -> String
360
+
361
+ # ```ruby
362
+ # ulid = ULID.generate
363
+ # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
364
+ # ulid.to_i #=> 1957909092946624190749577070267409738
365
+ # ```
92
366
  def to_i: -> Integer
93
367
  alias hash to_i
368
+
369
+ # Basically same as String based sort.
370
+ #
371
+ # ```ruby
372
+ # ulids = ULID.sample(10000); nil
373
+ # ulids.map(&:to_s).sort == ulids.sort.map(&:to_s)
374
+ # #=> true
375
+ # ```
376
+ #
377
+ # To be precise, this sorting unaffected with `case sensitive or not` and might handle [ulid/spec#57](https://github.com/ulid/spec/pull/57) in future. So preferable than `lexicographically sortable` in actual case.
378
+ #
94
379
  def <=>: (ULID other) -> Integer
95
380
  | (untyped other) -> nil
381
+
382
+ # ```ruby
383
+ # ulid = ULID.generate
384
+ # ulid.inspect #=> "ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)"
385
+ # ```
96
386
  def inspect: -> String
387
+
388
+ # ```ruby
389
+ # ULID.parse('4NNB20D9C1ME2NGMTX51ERZJX0') == ULID.parse('4nnb20d9c1me2ngmtx51erzjx0')
390
+ # #=> true
391
+ # ```
97
392
  def eql?: (untyped other) -> bool
98
393
  alias == eql?
99
394
  def ===: (untyped other) -> bool
395
+
396
+ # ```ruby
397
+ # ulid = ULID.generate
398
+ # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
399
+ # ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
400
+ # ```
100
401
  def to_time: -> Time
402
+
403
+ # ```ruby
404
+ # ulid = ULID.generate
405
+ # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
406
+ # ulid.timestamp #=> "01F4A5Y1YA"
407
+ # ```
101
408
  def timestamp: -> String
409
+
410
+ # ```ruby
411
+ # ulid = ULID.generate
412
+ # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
413
+ # ulid.randomness #=> "QCYAYCTC7GRMJ9AA"
414
+ # ```
102
415
  def randomness: -> String
416
+
417
+ # Intentionally avoiding to use `Record type` ref: https://github.com/ruby/rbs/blob/4fb4c33b2325d1a73d79ff7aaeb49f21cec1e0e5/docs/syntax.md#record-type
418
+ # Because the returning values are not fixed.
103
419
  def patterns: -> Hash[Symbol, Regexp | String]
104
- def pattern: -> Regexp
105
- def strict_pattern: -> Regexp
106
420
  def octets: -> octets
107
421
  def timestamp_octets: -> timestamp_octets
108
422
  def randomness_octets: -> randomness_octets
423
+
424
+ # ```ruby
425
+ # # Currently experimental feature, so needed to load the extension.
426
+ # require 'ulid/uuid'
427
+ #
428
+ # # Basically reversible
429
+ # ulid = ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
430
+ # #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
431
+ # ulid.to_uuidv4 #=> "0983d0a2-ff15-4d83-8f37-7dd945b5aa39"
432
+ # ```
433
+ #
434
+ # See also [Why this is experimental?](https://github.com/kachick/ruby-ulid/issues/76)
109
435
  def to_uuidv4: -> String
110
- def next: -> ULID?
111
- alias succ next
436
+
437
+ # Returns next(successor) ULID.
438
+ # Especially `ULID#succ` makes it possible `Range[ULID]#each`.
439
+ #
440
+ # NOTE: But basically `Range[ULID]#each` should not be used, incrementing 128 bits IDs are not reasonable operation in most case
441
+ #
442
+ # ```ruby
443
+ # ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZY').next.to_s #=> "01BX5ZZKBKZZZZZZZZZZZZZZZZ"
444
+ # ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZZ').next.to_s #=> "01BX5ZZKBM0000000000000000"
445
+ # ULID.parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').next #=> nil
446
+ # ```
447
+ #
448
+ # See also [ULID#pred](https://kachick.github.io/ruby-ulid/ULID.html#pred-instance_method)
449
+ def succ: -> ULID?
450
+ alias next succ
451
+
452
+ # Returns predecessor ULID.
453
+ #
454
+ # ```ruby
455
+ # ULID.parse('01BX5ZZKBK0000000000000001').pred.to_s #=> "01BX5ZZKBK0000000000000000"
456
+ # ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZZZ"
457
+ # ULID.parse('00000000000000000000000000').pred #=> nil
458
+ # ```
459
+ #
460
+ # See also [ULID#succ](https://kachick.github.io/ruby-ulid/ULID.html#succ-instance_method)
112
461
  def pred: -> ULID?
113
462
  def freeze: -> self
114
463
 
464
+ # Returns `self`
465
+ def to_ulid: -> self
466
+
467
+ # Returns `self`. Not a new instance.
468
+ def dup: -> self
469
+
470
+ # Same API as [Object#clone](https://github.com/ruby/rbs/blob/4fb4c33b2325d1a73d79ff7aaeb49f21cec1e0e5/core/object.rbs#L79)
471
+ # But actually returns `self`. Not a new instance.
472
+ def clone: (?freeze: bool) -> self
473
+
115
474
  private
116
- def self.argument_error_for_range_building: (untyped argument) -> ArgumentError
475
+
476
+ # A pribate API. Should not be used in your code.
477
+ def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> self
478
+
479
+ # A pribate API. Should not be used in your code.
480
+ def self.reasonable_entropy: -> Integer
481
+
482
+ # A pribate API. Should not be used in your code.
483
+ def self.milliseconds_from_time: (Time time) -> Integer
484
+
485
+ # A pribate API. Should not be used in your code.
486
+ def self.safe_get_class_name: (untyped object) -> String
487
+
488
+ # A pribate API. Should not be used in your code.
489
+ def initialize: (milliseconds: Integer, entropy: Integer, integer: Integer) -> void
490
+
491
+ # A pribate API. Should not be used in your code.
117
492
  def cache_all_instance_variables: -> void
118
493
  end