id_service 0.1.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.
- data/LICENSE +20 -0
- data/README.md +65 -0
- data/bin/id_server +47 -0
- data/ext/IdService.thrift +9 -0
- data/lib/id_generator.rb +78 -0
- data/lib/id_service.rb +4 -0
- data/lib/id_service/client.rb +55 -0
- data/lib/id_service/constants.rb +8 -0
- data/lib/id_service/helpers.rb +34 -0
- data/lib/id_service/processor.rb +12 -0
- data/lib/id_service/server.rb +34 -0
- data/lib/id_service/types.rb +23 -0
- data/lib/id_service/version.rb +4 -0
- metadata +175 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
Copyright (c) 2013 The Ready Project, LLC
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
5
|
+
this software and associated documentation files (the "Software"), to deal in
|
6
|
+
the Software without restriction, including without limitation the rights to
|
7
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
8
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
9
|
+
so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
12
|
+
copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
20
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
ID Service
|
2
|
+
==========
|
3
|
+
|
4
|
+
[](https://travis-ci.org/readyproject/IDService)
|
5
|
+
|
6
|
+
This gem provides both client and server classes for running an ID generation service, based on the Thrift framework.
|
7
|
+
The overall architecture is based on Twitter's Snowflake service, but written in Ruby. The core ID Generator is very
|
8
|
+
fast (10,000 IDs/sec) and the server is designed to be very fast as well (1,000 IDs/sec).
|
9
|
+
|
10
|
+
**WARNING** :: The IDs returned by the server, whether you use the pre-built one or customize will always return
|
11
|
+
sequential and unique IDs, except in the case that you run multiple servers with the same host and worker identifiers.
|
12
|
+
In that case you will not be guaranteed uniqueness of the IDs.
|
13
|
+
|
14
|
+
Running the Server
|
15
|
+
------------------
|
16
|
+
|
17
|
+
This gem comes with a pre-made server executable that can be used in lieu of building your own server implementation. To
|
18
|
+
start the provided server simple run the following command:
|
19
|
+
|
20
|
+
id_server
|
21
|
+
|
22
|
+
The server is designed to run in the foreground by default. It also accepts a number of options. You can check the
|
23
|
+
available options by passing the `--help` switch to the command.
|
24
|
+
|
25
|
+
Custom Server
|
26
|
+
-------------
|
27
|
+
|
28
|
+
Some may want to build their own server implementation. This is very simple. Just pass the relevant options when
|
29
|
+
initializing an instance of IdService::Server and then call `#serve` on your server instance.
|
30
|
+
|
31
|
+
require 'id_service/server'
|
32
|
+
|
33
|
+
options = {
|
34
|
+
hostname: 'localhost',
|
35
|
+
port: 9000,
|
36
|
+
host: 1,
|
37
|
+
worker: 1,
|
38
|
+
debug: false
|
39
|
+
}
|
40
|
+
|
41
|
+
server = IdService::Server.new(options)
|
42
|
+
server.serve
|
43
|
+
|
44
|
+
That's all there is to it.
|
45
|
+
|
46
|
+
Using the Client
|
47
|
+
----------------
|
48
|
+
|
49
|
+
Using the client is just as easy. If your server is running on localhost and port 9000 you can skip supplying the
|
50
|
+
options to client initialization.
|
51
|
+
|
52
|
+
require 'id_service'
|
53
|
+
|
54
|
+
options = {
|
55
|
+
host: 'localhost',
|
56
|
+
port: 9000
|
57
|
+
}
|
58
|
+
|
59
|
+
client = IdService::Client.new(options)
|
60
|
+
client.open
|
61
|
+
|
62
|
+
client.get_id
|
63
|
+
|
64
|
+
The `#get_id` method will always return sequential and unique IDs, given you don't have multiple servers using the same
|
65
|
+
host and worker identifiers responding to multiple clients.
|
data/bin/id_server
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'id_service/server'
|
5
|
+
|
6
|
+
opts = OptionParser.new do |opts|
|
7
|
+
opts.banner = "IDServer: a Thrift-based ID generation service"
|
8
|
+
opts.define_head "Usage: id_server [options]"
|
9
|
+
opts.separator ""
|
10
|
+
opts.separator "Examples:"
|
11
|
+
opts.separator " id_server --port 9000"
|
12
|
+
opts.separator " id_server --host 25 --worker 2"
|
13
|
+
opts.separator ""
|
14
|
+
opts.separator "Options:"
|
15
|
+
|
16
|
+
opts.on('--host [HOST]') do |v|
|
17
|
+
@host = v
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on('--port [PORT]') do |v|
|
21
|
+
@port = v
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on('--host_id [ID]') do |v|
|
25
|
+
@host_id = v
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on('--worker_id [ID]') do |v|
|
29
|
+
@worker_id = v
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on('--debug') do |v|
|
33
|
+
@debug = true unless v.nil?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
opts.parse!
|
37
|
+
|
38
|
+
options = {
|
39
|
+
hostname: @host,
|
40
|
+
port: @port,
|
41
|
+
host: @host_id,
|
42
|
+
worker: @worker_id,
|
43
|
+
debug: @debug
|
44
|
+
}.delete_if {|key, value| value.nil? }
|
45
|
+
|
46
|
+
server = IdService::Server.new(options)
|
47
|
+
server.serve
|
data/lib/id_generator.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
|
3
|
+
#require 'celluloid'
|
4
|
+
|
5
|
+
require 'id_service/types'
|
6
|
+
|
7
|
+
class IdGenerator
|
8
|
+
#include Celluloid
|
9
|
+
|
10
|
+
# Project Epoch: Midnight, Mountain Time, January 1st, 2013
|
11
|
+
EPOCH = 1357023600
|
12
|
+
|
13
|
+
# Field Bit Lengths
|
14
|
+
TIMESTAMP_BIT_LENGTH = 40
|
15
|
+
HOST_ID_BIT_LENGTH = 5
|
16
|
+
WORKER_ID_BIT_LENGTH = 5
|
17
|
+
SEQUENCE_BIT_LENGTH = 14
|
18
|
+
|
19
|
+
# Field Shifts
|
20
|
+
TIMESTAMP_SHIFT = HOST_ID_BIT_LENGTH + WORKER_ID_BIT_LENGTH + SEQUENCE_BIT_LENGTH
|
21
|
+
HOST_ID_SHIFT = WORKER_ID_BIT_LENGTH + SEQUENCE_BIT_LENGTH
|
22
|
+
WORKER_ID_SHIFT = SEQUENCE_BIT_LENGTH
|
23
|
+
|
24
|
+
# Field Masks
|
25
|
+
TIMESTAMP_MASK = 0xFFFFFFFFFF000000
|
26
|
+
HOST_ID_MASK = 0x0000000000F80000
|
27
|
+
WORKER_ID_MASK = 0x000000000007C000
|
28
|
+
SEQUENCE_MASK = 0x0000000000003FFF
|
29
|
+
|
30
|
+
def initialize(options = {})
|
31
|
+
options.symbolize_keys!
|
32
|
+
|
33
|
+
raise ArgumentError, 'missing host id' if options[:host].nil?
|
34
|
+
raise ArgumentError, 'missing worker id' if options[:worker].nil?
|
35
|
+
|
36
|
+
@debug = options[:debug]
|
37
|
+
@host = options[:host].to_i
|
38
|
+
@worker = options[:worker].to_i
|
39
|
+
@sequence = 0
|
40
|
+
@last_stamp = -1
|
41
|
+
|
42
|
+
puts 'Initialized IdGenerator' if @debug
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_id
|
46
|
+
now = get_timestamp
|
47
|
+
|
48
|
+
raise InvalidSystemClock if now < @last_stamp
|
49
|
+
|
50
|
+
if @last_stamp == now
|
51
|
+
@sequence = (@sequence + 1) & SEQUENCE_MASK
|
52
|
+
|
53
|
+
if @sequence == 0
|
54
|
+
sleep_until_next_millisecond
|
55
|
+
now = get_timestamp
|
56
|
+
end
|
57
|
+
else
|
58
|
+
@sequence = 0
|
59
|
+
end
|
60
|
+
|
61
|
+
@last_stamp = now
|
62
|
+
|
63
|
+
id = (now << TIMESTAMP_SHIFT) | (@host << HOST_ID_SHIFT) | (@worker << WORKER_ID_SHIFT) | @sequence
|
64
|
+
puts "Generated ID: #{id}" if @debug
|
65
|
+
id
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_timestamp
|
69
|
+
Time.now.to_i - EPOCH
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def sleep_until_next_millisecond
|
75
|
+
puts 'Waiting for next milliseconds' if @debug
|
76
|
+
false until get_timestamp > @last_stamp
|
77
|
+
end
|
78
|
+
end
|
data/lib/id_service.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'thrift'
|
2
|
+
|
3
|
+
require 'active_support/core_ext'
|
4
|
+
|
5
|
+
require 'id_service/types'
|
6
|
+
require 'id_service/helpers'
|
7
|
+
|
8
|
+
module IdService
|
9
|
+
class Client
|
10
|
+
include ::Thrift::Client
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
options.symbolize_keys!
|
14
|
+
options = default_options.merge(options)
|
15
|
+
|
16
|
+
@transport = Thrift::BufferedTransport.new(Thrift::Socket.new(options[:host], options[:port]))
|
17
|
+
protocol = Thrift::BinaryProtocol.new(@transport)
|
18
|
+
|
19
|
+
@iprot = protocol
|
20
|
+
@oprot = options[:oprot] || protocol
|
21
|
+
@seqid = 0
|
22
|
+
end
|
23
|
+
|
24
|
+
def open
|
25
|
+
@transport.open
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_id()
|
29
|
+
send_get_id()
|
30
|
+
return recv_get_id()
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def default_options
|
36
|
+
{
|
37
|
+
host: 'localhost',
|
38
|
+
port: 9000,
|
39
|
+
oprot: nil
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def send_get_id()
|
44
|
+
send_message('get_id', Get_id_args)
|
45
|
+
end
|
46
|
+
|
47
|
+
def recv_get_id()
|
48
|
+
result = receive_message(Get_id_result)
|
49
|
+
return result.success unless result.success.nil?
|
50
|
+
raise ::Thrift::ApplicationException.new(::Thrift::ApplicationException::MISSING_RESULT, 'get_id failed: unknown result')
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'thrift'
|
2
|
+
|
3
|
+
module IdService
|
4
|
+
class Get_id_args
|
5
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
6
|
+
|
7
|
+
FIELDS = {
|
8
|
+
|
9
|
+
}
|
10
|
+
|
11
|
+
def struct_fields; FIELDS; end
|
12
|
+
|
13
|
+
def validate
|
14
|
+
end
|
15
|
+
|
16
|
+
::Thrift::Struct.generate_accessors self
|
17
|
+
end
|
18
|
+
|
19
|
+
class Get_id_result
|
20
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
21
|
+
SUCCESS = 0
|
22
|
+
|
23
|
+
FIELDS = {
|
24
|
+
SUCCESS => {:type => ::Thrift::Types::I64, :name => 'success'}
|
25
|
+
}
|
26
|
+
|
27
|
+
def struct_fields; FIELDS; end
|
28
|
+
|
29
|
+
def validate
|
30
|
+
end
|
31
|
+
|
32
|
+
::Thrift::Struct.generate_accessors self
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module IdService
|
2
|
+
class Processor
|
3
|
+
include ::Thrift::Processor
|
4
|
+
|
5
|
+
def process_get_id(seqid, iprot, oprot)
|
6
|
+
args = read_args(iprot, Get_id_args)
|
7
|
+
result = Get_id_result.new()
|
8
|
+
result.success = @handler.get_id()
|
9
|
+
write_result(result, oprot, 'get_id', seqid)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'thrift'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
|
4
|
+
require 'id_service/helpers'
|
5
|
+
require 'id_service/processor'
|
6
|
+
require 'id_service/types'
|
7
|
+
|
8
|
+
require 'id_generator'
|
9
|
+
|
10
|
+
module IdService
|
11
|
+
class Server < Thrift::SimpleServer
|
12
|
+
def initialize(options = {})
|
13
|
+
options.symbolize_keys!
|
14
|
+
options = default_options.merge(options)
|
15
|
+
|
16
|
+
@handler = IdGenerator.new(options)
|
17
|
+
@processor = IdService::Processor.new(@handler)
|
18
|
+
@server_transport = Thrift::ServerSocket.new(options[:hostname], options[:port].to_s)
|
19
|
+
@transport_factory = Thrift::BufferedTransportFactory.new()
|
20
|
+
@protocol_factory = Thrift::BinaryProtocolFactory.new
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def default_options
|
25
|
+
{
|
26
|
+
hostname: 'localhost',
|
27
|
+
port: 9000,
|
28
|
+
host: 1,
|
29
|
+
worker: 1,
|
30
|
+
debug: false,
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'thrift'
|
2
|
+
|
3
|
+
class InvalidSystemClock < ::Thrift::Exception
|
4
|
+
include ::Thrift::Struct, ::Thrift::Struct_Union
|
5
|
+
def initialize(message=nil)
|
6
|
+
super()
|
7
|
+
self.message = message
|
8
|
+
end
|
9
|
+
|
10
|
+
MESSAGE = 1
|
11
|
+
|
12
|
+
FIELDS = {
|
13
|
+
MESSAGE => {:type => ::Thrift::Types::STRING, :name => 'message'}
|
14
|
+
}
|
15
|
+
|
16
|
+
def struct_fields; FIELDS; end
|
17
|
+
|
18
|
+
def validate
|
19
|
+
end
|
20
|
+
|
21
|
+
::Thrift::Struct.generate_accessors self
|
22
|
+
end
|
23
|
+
|
metadata
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: id_service
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- James Thompson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thrift
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activesupport
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: shoulda-matchers
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: simplecov
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: timecop
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description: IdService provides a client and server for setting up a sequential &
|
127
|
+
unique id generation service.
|
128
|
+
email:
|
129
|
+
- jamest@thereadyproject.com
|
130
|
+
executables:
|
131
|
+
- id_server
|
132
|
+
extensions: []
|
133
|
+
extra_rdoc_files: []
|
134
|
+
files:
|
135
|
+
- bin/id_server
|
136
|
+
- lib/id_generator.rb
|
137
|
+
- lib/id_service/client.rb
|
138
|
+
- lib/id_service/constants.rb
|
139
|
+
- lib/id_service/helpers.rb
|
140
|
+
- lib/id_service/processor.rb
|
141
|
+
- lib/id_service/server.rb
|
142
|
+
- lib/id_service/types.rb
|
143
|
+
- lib/id_service/version.rb
|
144
|
+
- lib/id_service.rb
|
145
|
+
- ext/IdService.thrift
|
146
|
+
- LICENSE
|
147
|
+
- README.md
|
148
|
+
homepage: http://github.com/readyproject/IDService
|
149
|
+
licenses: []
|
150
|
+
post_install_message:
|
151
|
+
rdoc_options: []
|
152
|
+
require_paths:
|
153
|
+
- lib
|
154
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
155
|
+
none: false
|
156
|
+
requirements:
|
157
|
+
- - ! '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
segments:
|
161
|
+
- 0
|
162
|
+
hash: 2474586230224755016
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
none: false
|
165
|
+
requirements:
|
166
|
+
- - ! '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: 1.3.6
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project:
|
171
|
+
rubygems_version: 1.8.25
|
172
|
+
signing_key:
|
173
|
+
specification_version: 3
|
174
|
+
summary: A client and server for sequential & unique id generation
|
175
|
+
test_files: []
|