uniflake 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f80b6d4ffd25a72a9a88dcec412a922514d9c6e631ac4244c9dfb3d2e590018f
4
+ data.tar.gz: 5405b316f49e42ce17876f062302d44d447d165152138cc2286ce04d7e176070
5
+ SHA512:
6
+ metadata.gz: 39ae675c29e7de6674043d050d0ce7b133316b481a5be6b4026dd013312afd2b49cc75717760db94b14095ea680db4d960a0576270408b8588e7306861d80c59
7
+ data.tar.gz: 7c9270a843f01f0600fd0212dfbdfad0d4d1a1e26b7c60c6b3f143194a9d3a34248001ce5bdf9aae22a403fe061ad1089a48756d12c9699ce178e2537ebb7023
@@ -0,0 +1,18 @@
1
+ module Uniflake
2
+
3
+ class Number
4
+ BIT_LENGTH = 10
5
+ MAX_ID = (1 << BIT_LENGTH) - 1
6
+
7
+ attr_reader :id
8
+
9
+ def initialize(id = 0)
10
+ raise OverflowError, "invalid generator id (#{id} > #{MAX_ID})" if id > MAX_ID
11
+ @id = id
12
+ end
13
+
14
+ def to_i
15
+ @id
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ module Uniflake::Generators
2
+
3
+ class Discord < Uniflake
4
+ # https://en.wikipedia.org/wiki/Snowflake_ID
5
+ DEFAULT_EPOCH_START_AT = Time.new(2015,01,01)
6
+ end
7
+
8
+ end
@@ -0,0 +1,8 @@
1
+ module Uniflake::Generators
2
+
3
+ class Snowflake < Uniflake
4
+ # https://en.wikipedia.org/wiki/Snowflake_ID
5
+ DEFAULT_EPOCH_START_AT = Time.at(0, 1288834974657, :millisecond)
6
+ end
7
+
8
+ end
@@ -0,0 +1,65 @@
1
+ module Uniflake::Generators
2
+
3
+ class Uniflake
4
+ STRUCTURE = {
5
+ timestamp: 41,
6
+ generator_id: 10,
7
+ sequence: 12
8
+ }
9
+
10
+ DEFAULT_EPOCH_START_AT = Time.new(2022, 01, 01)
11
+
12
+ MAX_TIMESTAMP = (1 << STRUCTURE[:timestamp]) - 1
13
+ MAX_GENERATOR_ID = (1 << STRUCTURE[:generator_id]) - 1
14
+ MAX_SEQUENCE = (1 << STRUCTURE[:sequence]) - 1
15
+
16
+ def initialize(sequence_resolver, epoch_start_at = self::class::DEFAULT_EPOCH_START_AT, generator_id = 0)
17
+ @generator_id = generator_id.to_i
18
+ raise Error, "invalid generator_id (#{@generator_id} > #{self::class::MAX_GENERATOR_ID})" if @generator_id > self::class::MAX_GENERATOR_ID
19
+
20
+ @epoch_start_at = epoch_start_at.strftime('%s%L').to_i
21
+ now = Time.now.strftime('%s%L').to_i
22
+ raise Error, "invalid epoch (#{@epoch_start_at} > #{now})" if @epoch_start_at > now
23
+
24
+ @sequence_resolver = sequence_resolver
25
+ end
26
+
27
+ def generate
28
+ now = Time.now.strftime('%s%L').to_i
29
+
30
+ sequence = @sequence_resolver.resolve(@generator_id, now, self::class::MAX_SEQUENCE)
31
+ raise Error, "invalid sequence (#{sequence} > #{self::class::MAX_SEQUENCE})" if sequence > self::class::MAX_SEQUENCE
32
+
33
+ timestamp = now - @epoch_start_at
34
+ raise Error, "invalid timestamp (#{timestamp} > #{self::class::MAX_TIMESTAMP})" if timestamp > self::class::MAX_TIMESTAMP
35
+
36
+ self.class.compose(timestamp, @generator_id, sequence)
37
+ end
38
+
39
+ def self.compose(timestamp, generator_id, sequence)
40
+ shift = 0
41
+ id = 0
42
+ self::STRUCTURE.keys.reverse.each do |field|
43
+ id += binding.local_variable_get(field) << shift
44
+ shift += self::STRUCTURE[field]
45
+ end
46
+ id
47
+ end
48
+
49
+ def parse(id)
50
+ fields = self.class.parse(id)
51
+ fields[:timestamp] += @epoch_start_at if fields.has_key? :timestamp
52
+ fields
53
+ end
54
+
55
+ def self.parse(id)
56
+ id = id.to_s(2).rjust(self::STRUCTURE.values.sum, "0")
57
+ fields = {}
58
+ self::STRUCTURE.each do |field, length|
59
+ fields[field] = id[0..length - 1].to_i(2)
60
+ id[0..length - 1] = id[length..-1]
61
+ end
62
+ fields
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,18 @@
1
+ module Uniflake::SequenceResolvers
2
+
3
+ class Memory
4
+
5
+ def initialize
6
+ @sequences = {}
7
+ @last_time = {}
8
+ end
9
+
10
+ def resolve(int_generator_id, time, max_value = 0)
11
+ raise "invalid time" if time < @last_time[int_generator_id].to_i
12
+
13
+ @sequences[int_generator_id] = time == @last_time[int_generator_id].to_i ? @sequences[int_generator_id].to_i + 1 : 0
14
+ @last_time[int_generator_id] = time
15
+ @sequences[int_generator_id]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ module Uniflake::SequenceResolvers
2
+
3
+ class Random
4
+
5
+ def initialize
6
+ @prng = Random.new
7
+ end
8
+
9
+ def resolve(int_generator_id, time, max_value = 0)
10
+ raise "invalid time" if time < @last_time.to_i
11
+ @last_time = time
12
+ @prng.rand max_value
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ module Uniflake::SequenceResolvers
2
+
3
+ class Redis
4
+
5
+ def initialize(client, key_prefix = '')
6
+ @client = client
7
+ @prefix = key_prefix
8
+ end
9
+
10
+ def resolve(int_generator_id, time, max_value = 0)
11
+ last_time = connection.get(@prefix + int_generator_id.to_s + "_last_time").to_i
12
+ raise "invalid time" if time < last_time
13
+
14
+ connection.set @prefix + int_generator_id.to_s + "_last_time", time
15
+
16
+ connection.eval lua, [@prefix + int_generator_id.to_s + time.to_s], ['0', '10']
17
+ end
18
+
19
+ def connection
20
+ if @client.class.method_defined? 'with'
21
+ @client.with { |connection| return connection }
22
+ end
23
+
24
+ @client
25
+ end
26
+
27
+ def lua
28
+ <<~LUA
29
+ if redis.call('set', KEYS[1], ARGV[1], "EX", ARGV[2], "NX") then
30
+ return 0
31
+ else
32
+ return redis.call('incr', KEYS[1])
33
+ end
34
+ LUA
35
+ end
36
+ end
37
+ end
data/lib/uniflake.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Uniflake
2
+ require_relative 'uniflake/generators/uniflake.rb'
3
+ require_relative 'uniflake/generators/snowflake.rb'
4
+ require_relative 'uniflake/generators/discord.rb'
5
+
6
+ require_relative 'uniflake/sequence_resolvers/memory.rb'
7
+ require_relative 'uniflake/sequence_resolvers/random.rb'
8
+ require_relative 'uniflake/sequence_resolvers/redis.rb'
9
+
10
+ require_relative 'uniflake/generator_ids/number.rb'
11
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: uniflake
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - nebo7
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-02-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: ''
14
+ email: ''
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/uniflake.rb
20
+ - lib/uniflake/generator_ids/number.rb
21
+ - lib/uniflake/generators/discord.rb
22
+ - lib/uniflake/generators/snowflake.rb
23
+ - lib/uniflake/generators/uniflake.rb
24
+ - lib/uniflake/sequence_resolvers/memory.rb
25
+ - lib/uniflake/sequence_resolvers/random.rb
26
+ - lib/uniflake/sequence_resolvers/redis.rb
27
+ homepage: https://github.com/nebo7/uniflake
28
+ licenses:
29
+ - MIT
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubygems_version: 3.2.22
47
+ signing_key:
48
+ specification_version: 4
49
+ summary: ''
50
+ test_files: []