squirreldb-sdk 0.1.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/lib/squirreldb/cache.rb +163 -0
- data/lib/squirreldb/client.rb +202 -0
- data/lib/squirreldb/storage.rb +177 -0
- data/lib/squirreldb/types.rb +8 -0
- data/lib/squirreldb.rb +14 -0
- metadata +99 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a43ce4beedd5703705fb80885a05a914a7b46466cf6a2503629bbe2f6c49ade7
|
|
4
|
+
data.tar.gz: cab0bcc0c3e779169a5ec73c60ad5ba32b3d957c5a566205303ed768563b5915
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d7152d7f43378e2abffcbf77b1309abd276e13a37759dc70e48bd19ded705ad32de31fdf2b17e6673360d9ce1709d7e408bbdc34f5656e09116e9d135b55f3d3
|
|
7
|
+
data.tar.gz: 0b36d4f5f4e8a1f6141113eb768264ccd2c4c7079d448892f8cb497e10b3f164ff5456ea418d72cf0b3cba343a86985489a975c87a9b97ab645dbf344f1b87fc
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# SquirrelDB Ruby SDK - Cache (Redis-compatible)
|
|
2
|
+
# Generated by sdk-generator v0.1.0
|
|
3
|
+
# DO NOT EDIT MANUALLY
|
|
4
|
+
|
|
5
|
+
# frozen_string_literal: true
|
|
6
|
+
|
|
7
|
+
require "socket"
|
|
8
|
+
|
|
9
|
+
module SquirrelDB
|
|
10
|
+
# Redis-compatible cache client using RESP protocol
|
|
11
|
+
class Cache
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
|
|
14
|
+
def initialize(host: "localhost", port: 6379)
|
|
15
|
+
@host = host
|
|
16
|
+
@port = port
|
|
17
|
+
@socket = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.connect(host: "localhost", port: 6379)
|
|
21
|
+
cache = new(host: host, port: port)
|
|
22
|
+
cache.send(:do_connect)
|
|
23
|
+
cache
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def get(key)
|
|
27
|
+
resp = command("GET", key)
|
|
28
|
+
resp.is_a?(NilClass) ? nil : resp
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def set(key, value, ttl: nil)
|
|
32
|
+
if ttl
|
|
33
|
+
command("SET", key, value, "EX", ttl.to_s)
|
|
34
|
+
else
|
|
35
|
+
command("SET", key, value)
|
|
36
|
+
end
|
|
37
|
+
true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def del(key)
|
|
41
|
+
command("DEL", key) > 0
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def exists(key)
|
|
45
|
+
command("EXISTS", key) > 0
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def expire(key, seconds)
|
|
49
|
+
command("EXPIRE", key, seconds.to_s) > 0
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def ttl(key)
|
|
53
|
+
command("TTL", key)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def incr(key)
|
|
57
|
+
command("INCR", key)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def decr(key)
|
|
61
|
+
command("DECR", key)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def incrby(key, amount)
|
|
65
|
+
command("INCRBY", key, amount.to_s)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def decrby(key, amount)
|
|
69
|
+
command("DECRBY", key, amount.to_s)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def mget(*keys)
|
|
73
|
+
command("MGET", *keys)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def mset(hash)
|
|
77
|
+
args = hash.flat_map { |k, v| [k.to_s, v.to_s] }
|
|
78
|
+
command("MSET", *args)
|
|
79
|
+
true
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def keys(pattern = "*")
|
|
83
|
+
command("KEYS", pattern)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def dbsize
|
|
87
|
+
command("DBSIZE")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def flushdb
|
|
91
|
+
command("FLUSHDB")
|
|
92
|
+
true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def info
|
|
96
|
+
result = command("INFO")
|
|
97
|
+
result.split("\n").reject { |l| l.empty? || l.start_with?("#") }.map do |line|
|
|
98
|
+
line.split(":", 2)
|
|
99
|
+
end.to_h
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def ping
|
|
103
|
+
command("PING") == "PONG"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def close
|
|
107
|
+
command("QUIT") rescue nil
|
|
108
|
+
@socket&.close
|
|
109
|
+
@socket = nil
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def do_connect
|
|
115
|
+
@socket = TCPSocket.new(@host, @port)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def command(*args)
|
|
119
|
+
raise Error, "Not connected" unless @socket
|
|
120
|
+
|
|
121
|
+
@socket.write(encode_command(args))
|
|
122
|
+
read_response
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def encode_command(args)
|
|
126
|
+
parts = ["*#{args.length}\r\n"]
|
|
127
|
+
args.each do |arg|
|
|
128
|
+
arg = arg.to_s
|
|
129
|
+
parts << "$#{arg.bytesize}\r\n#{arg}\r\n"
|
|
130
|
+
end
|
|
131
|
+
parts.join
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def read_response
|
|
135
|
+
line = @socket.gets.chomp
|
|
136
|
+
prefix = line[0]
|
|
137
|
+
content = line[1..]
|
|
138
|
+
|
|
139
|
+
case prefix
|
|
140
|
+
when "+"
|
|
141
|
+
content
|
|
142
|
+
when "-"
|
|
143
|
+
raise Error, content
|
|
144
|
+
when ":"
|
|
145
|
+
content.to_i
|
|
146
|
+
when "$"
|
|
147
|
+
len = content.to_i
|
|
148
|
+
return nil if len == -1
|
|
149
|
+
|
|
150
|
+
data = @socket.read(len)
|
|
151
|
+
@socket.read(2) # Read \r\n
|
|
152
|
+
data
|
|
153
|
+
when "*"
|
|
154
|
+
count = content.to_i
|
|
155
|
+
return nil if count == -1
|
|
156
|
+
|
|
157
|
+
count.times.map { read_response }
|
|
158
|
+
else
|
|
159
|
+
raise Error, "Unknown RESP type: #{prefix}"
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# SquirrelDB Ruby SDK - Client
|
|
2
|
+
# Generated by sdk-generator v0.1.0
|
|
3
|
+
# DO NOT EDIT MANUALLY
|
|
4
|
+
|
|
5
|
+
# frozen_string_literal: true
|
|
6
|
+
|
|
7
|
+
require "json"
|
|
8
|
+
require "securerandom"
|
|
9
|
+
require "websocket-client-simple"
|
|
10
|
+
require "thread"
|
|
11
|
+
|
|
12
|
+
module SquirrelDB
|
|
13
|
+
# Client for connecting to SquirrelDB
|
|
14
|
+
class Client
|
|
15
|
+
def initialize(url, reconnect: true, max_reconnect_attempts: 10, reconnect_delay: 1.0)
|
|
16
|
+
@url = url.start_with?("ws://", "wss://") ? url : "ws://#{url}"
|
|
17
|
+
@reconnect = reconnect
|
|
18
|
+
@max_reconnect_attempts = max_reconnect_attempts
|
|
19
|
+
@reconnect_delay = reconnect_delay
|
|
20
|
+
@pending = {}
|
|
21
|
+
@subscriptions = {}
|
|
22
|
+
@mutex = Mutex.new
|
|
23
|
+
@closed = false
|
|
24
|
+
@reconnect_attempts = 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.connect(url, **options)
|
|
28
|
+
client = new(url, **options)
|
|
29
|
+
client.send(:do_connect)
|
|
30
|
+
client
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def query(q)
|
|
34
|
+
resp = send_message({ type: "query", id: generate_id, query: q })
|
|
35
|
+
raise resp["error"] if resp["type"] == "error"
|
|
36
|
+
resp["data"].map { |d| Document.from_hash(d) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def subscribe(table_name)
|
|
40
|
+
SubscriptionBuilder.new(self, table_name)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def subscribe_raw(q, &callback)
|
|
44
|
+
sub_id = generate_id
|
|
45
|
+
resp = send_message({ type: "subscribe", id: sub_id, query: q })
|
|
46
|
+
raise resp["error"] if resp["type"] == "error"
|
|
47
|
+
@mutex.synchronize { @subscriptions[sub_id] = callback }
|
|
48
|
+
sub_id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def unsubscribe(subscription_id)
|
|
52
|
+
send_message({ type: "unsubscribe", id: subscription_id })
|
|
53
|
+
@mutex.synchronize { @subscriptions.delete(subscription_id) }
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def insert(collection, data)
|
|
58
|
+
resp = send_message({
|
|
59
|
+
type: "insert",
|
|
60
|
+
id: generate_id,
|
|
61
|
+
collection: collection,
|
|
62
|
+
data: data
|
|
63
|
+
})
|
|
64
|
+
raise resp["error"] if resp["type"] == "error"
|
|
65
|
+
Document.from_hash(resp["data"])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def update(collection, document_id, data)
|
|
69
|
+
resp = send_message({
|
|
70
|
+
type: "update",
|
|
71
|
+
id: generate_id,
|
|
72
|
+
collection: collection,
|
|
73
|
+
document_id: document_id,
|
|
74
|
+
data: data
|
|
75
|
+
})
|
|
76
|
+
raise resp["error"] if resp["type"] == "error"
|
|
77
|
+
Document.from_hash(resp["data"])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def delete(collection, document_id)
|
|
81
|
+
resp = send_message({
|
|
82
|
+
type: "delete",
|
|
83
|
+
id: generate_id,
|
|
84
|
+
collection: collection,
|
|
85
|
+
document_id: document_id
|
|
86
|
+
})
|
|
87
|
+
raise resp["error"] if resp["type"] == "error"
|
|
88
|
+
Document.from_hash(resp["data"])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def list_collections
|
|
92
|
+
resp = send_message({ type: "listcollections", id: generate_id })
|
|
93
|
+
raise resp["error"] if resp["type"] == "error"
|
|
94
|
+
resp["data"]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def ping
|
|
98
|
+
resp = send_message({ type: "ping", id: generate_id })
|
|
99
|
+
raise "Unexpected response" unless resp["type"] == "pong"
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def close
|
|
104
|
+
@closed = true
|
|
105
|
+
@mutex.synchronize { @subscriptions.clear }
|
|
106
|
+
@ws&.close
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def do_connect
|
|
112
|
+
@ws = WebSocket::Client::Simple.connect(@url)
|
|
113
|
+
client = self
|
|
114
|
+
|
|
115
|
+
@ws.on :message do |msg|
|
|
116
|
+
client.send(:handle_message, msg.data)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
@ws.on :close do
|
|
120
|
+
client.send(:handle_disconnect)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
@ws.on :error do |e|
|
|
124
|
+
# Handle error silently
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
sleep 0.1 until @ws.open?
|
|
128
|
+
@reconnect_attempts = 0
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def handle_message(data)
|
|
132
|
+
msg = JSON.parse(data)
|
|
133
|
+
msg_type = msg["type"]
|
|
134
|
+
msg_id = msg["id"]
|
|
135
|
+
|
|
136
|
+
if msg_type == "change"
|
|
137
|
+
callback = @mutex.synchronize { @subscriptions[msg_id] }
|
|
138
|
+
callback&.call(ChangeEvent.from_hash(msg["change"]))
|
|
139
|
+
return
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
queue = @mutex.synchronize { @pending.delete(msg_id) }
|
|
143
|
+
queue&.push(msg)
|
|
144
|
+
rescue JSON::ParserError
|
|
145
|
+
# Ignore malformed messages
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def handle_disconnect
|
|
149
|
+
return if @closed
|
|
150
|
+
|
|
151
|
+
@mutex.synchronize do
|
|
152
|
+
@pending.each_value { |q| q.push({ "type" => "error", "error" => "Connection closed" }) }
|
|
153
|
+
@pending.clear
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
if @reconnect && @reconnect_attempts < @max_reconnect_attempts
|
|
157
|
+
@reconnect_attempts += 1
|
|
158
|
+
delay = @reconnect_delay * (2 ** (@reconnect_attempts - 1))
|
|
159
|
+
sleep delay
|
|
160
|
+
do_connect rescue nil
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def send_message(msg)
|
|
165
|
+
raise "Not connected" unless @ws&.open?
|
|
166
|
+
|
|
167
|
+
queue = Queue.new
|
|
168
|
+
@mutex.synchronize { @pending[msg[:id]] = queue }
|
|
169
|
+
|
|
170
|
+
@ws.send(msg.to_json)
|
|
171
|
+
queue.pop
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def generate_id
|
|
175
|
+
SecureRandom.uuid
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Subscription builder for fluent change subscriptions
|
|
180
|
+
class SubscriptionBuilder
|
|
181
|
+
def initialize(client, table_name)
|
|
182
|
+
@client = client
|
|
183
|
+
@table_name = table_name
|
|
184
|
+
@filter_condition = nil
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def find(condition = nil, &block)
|
|
188
|
+
@filter_condition = block_given? ? block.call(Query::DocProxy.new) : condition
|
|
189
|
+
self
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def changes(&callback)
|
|
193
|
+
query = { "table" => @table_name, "changes" => { "includeInitial" => false } }
|
|
194
|
+
query["filter"] = Query.filter_to_structured(@filter_condition) if @filter_condition
|
|
195
|
+
@client.subscribe_structured(query, &callback)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def self.connect(url, **options)
|
|
200
|
+
Client.connect(url, **options)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# SquirrelDB Ruby SDK - Storage (S3-compatible)
|
|
2
|
+
# Generated by sdk-generator v0.1.0
|
|
3
|
+
# DO NOT EDIT MANUALLY
|
|
4
|
+
|
|
5
|
+
# frozen_string_literal: true
|
|
6
|
+
|
|
7
|
+
require "net/http"
|
|
8
|
+
require "uri"
|
|
9
|
+
require "openssl"
|
|
10
|
+
require "time"
|
|
11
|
+
require "rexml/document"
|
|
12
|
+
|
|
13
|
+
module SquirrelDB
|
|
14
|
+
# S3-compatible storage client
|
|
15
|
+
class Storage
|
|
16
|
+
class Error < StandardError
|
|
17
|
+
attr_reader :status_code
|
|
18
|
+
|
|
19
|
+
def initialize(message, status_code = nil)
|
|
20
|
+
super(message)
|
|
21
|
+
@status_code = status_code
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Bucket = Struct.new(:name, :created_at, keyword_init: true)
|
|
26
|
+
StorageObject = Struct.new(:key, :size, :etag, :last_modified, keyword_init: true)
|
|
27
|
+
|
|
28
|
+
def initialize(endpoint:, access_key: nil, secret_key: nil, region: "us-east-1")
|
|
29
|
+
@endpoint = endpoint.chomp("/")
|
|
30
|
+
@access_key = access_key
|
|
31
|
+
@secret_key = secret_key
|
|
32
|
+
@region = region
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def list_buckets
|
|
36
|
+
response = request("GET", "/")
|
|
37
|
+
doc = REXML::Document.new(response.body)
|
|
38
|
+
doc.elements.collect("//Bucket/Name") do |e|
|
|
39
|
+
Bucket.new(name: e.text, created_at: Time.now)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def create_bucket(name)
|
|
44
|
+
request("PUT", "/#{name}")
|
|
45
|
+
true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def delete_bucket(name)
|
|
49
|
+
request("DELETE", "/#{name}")
|
|
50
|
+
true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def bucket_exists?(name)
|
|
54
|
+
request("HEAD", "/#{name}")
|
|
55
|
+
true
|
|
56
|
+
rescue Error
|
|
57
|
+
false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def list_objects(bucket, prefix: nil, max_keys: nil)
|
|
61
|
+
path = "/#{bucket}"
|
|
62
|
+
params = []
|
|
63
|
+
params << "prefix=#{URI.encode_www_form_component(prefix)}" if prefix
|
|
64
|
+
params << "max-keys=#{max_keys}" if max_keys
|
|
65
|
+
path += "?#{params.join("&")}" unless params.empty?
|
|
66
|
+
|
|
67
|
+
response = request("GET", path)
|
|
68
|
+
doc = REXML::Document.new(response.body)
|
|
69
|
+
|
|
70
|
+
doc.elements.collect("//Contents") do |e|
|
|
71
|
+
StorageObject.new(
|
|
72
|
+
key: e.elements["Key"]&.text,
|
|
73
|
+
size: e.elements["Size"]&.text&.to_i || 0,
|
|
74
|
+
etag: e.elements["ETag"]&.text&.gsub('"', ""),
|
|
75
|
+
last_modified: Time.now
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def get_object(bucket, key)
|
|
81
|
+
response = request("GET", "/#{bucket}/#{key}")
|
|
82
|
+
response.body
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def put_object(bucket, key, data, content_type: "application/octet-stream")
|
|
86
|
+
headers = { "Content-Type" => content_type }
|
|
87
|
+
response = request("PUT", "/#{bucket}/#{key}", body: data, headers: headers)
|
|
88
|
+
response["etag"]&.gsub('"', "") || ""
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def delete_object(bucket, key)
|
|
92
|
+
request("DELETE", "/#{bucket}/#{key}")
|
|
93
|
+
true
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def object_exists?(bucket, key)
|
|
97
|
+
request("HEAD", "/#{bucket}/#{key}")
|
|
98
|
+
true
|
|
99
|
+
rescue Error
|
|
100
|
+
false
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def request(method, path, body: nil, headers: {})
|
|
106
|
+
uri = URI.parse("#{@endpoint}#{path}")
|
|
107
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
108
|
+
http.use_ssl = uri.scheme == "https"
|
|
109
|
+
|
|
110
|
+
request_class = {
|
|
111
|
+
"GET" => Net::HTTP::Get,
|
|
112
|
+
"PUT" => Net::HTTP::Put,
|
|
113
|
+
"DELETE" => Net::HTTP::Delete,
|
|
114
|
+
"HEAD" => Net::HTTP::Head
|
|
115
|
+
}[method]
|
|
116
|
+
|
|
117
|
+
req = request_class.new(uri)
|
|
118
|
+
headers.each { |k, v| req[k] = v }
|
|
119
|
+
req.body = body if body
|
|
120
|
+
|
|
121
|
+
if @access_key && @secret_key
|
|
122
|
+
sign_request(req, uri, body)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
response = http.request(req)
|
|
126
|
+
|
|
127
|
+
unless response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPNoContent)
|
|
128
|
+
raise Error.new(response.body, response.code.to_i)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
response
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def sign_request(req, uri, body)
|
|
135
|
+
now = Time.now.utc
|
|
136
|
+
date_stamp = now.strftime("%Y%m%d")
|
|
137
|
+
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
|
138
|
+
|
|
139
|
+
payload_hash = OpenSSL::Digest::SHA256.hexdigest(body || "")
|
|
140
|
+
req["x-amz-date"] = amz_date
|
|
141
|
+
req["x-amz-content-sha256"] = payload_hash
|
|
142
|
+
|
|
143
|
+
signed_headers = (req.to_hash.keys + ["host"]).sort.join(";")
|
|
144
|
+
canonical_headers = (req.to_hash.merge("host" => [uri.host])).sort.map { |k, v| "#{k}:#{v.join(",")}\n" }.join
|
|
145
|
+
|
|
146
|
+
canonical_request = [
|
|
147
|
+
req.method,
|
|
148
|
+
uri.path,
|
|
149
|
+
uri.query || "",
|
|
150
|
+
canonical_headers,
|
|
151
|
+
signed_headers,
|
|
152
|
+
payload_hash
|
|
153
|
+
].join("\n")
|
|
154
|
+
|
|
155
|
+
algorithm = "AWS4-HMAC-SHA256"
|
|
156
|
+
credential_scope = "#{date_stamp}/#{@region}/s3/aws4_request"
|
|
157
|
+
string_to_sign = [
|
|
158
|
+
algorithm,
|
|
159
|
+
amz_date,
|
|
160
|
+
credential_scope,
|
|
161
|
+
OpenSSL::Digest::SHA256.hexdigest(canonical_request)
|
|
162
|
+
].join("\n")
|
|
163
|
+
|
|
164
|
+
k_date = hmac_sha256("AWS4#{@secret_key}", date_stamp)
|
|
165
|
+
k_region = hmac_sha256(k_date, @region)
|
|
166
|
+
k_service = hmac_sha256(k_region, "s3")
|
|
167
|
+
k_signing = hmac_sha256(k_service, "aws4_request")
|
|
168
|
+
signature = OpenSSL::HMAC.hexdigest("SHA256", k_signing, string_to_sign)
|
|
169
|
+
|
|
170
|
+
req["Authorization"] = "#{algorithm} Credential=#{@access_key}/#{credential_scope}, SignedHeaders=#{signed_headers}, Signature=#{signature}"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def hmac_sha256(key, data)
|
|
174
|
+
OpenSSL::HMAC.digest("SHA256", key, data)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
data/lib/squirreldb.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SquirrelDB Ruby SDK
|
|
2
|
+
# Generated by sdk-generator v0.1.0
|
|
3
|
+
# DO NOT EDIT MANUALLY
|
|
4
|
+
|
|
5
|
+
# frozen_string_literal: true
|
|
6
|
+
|
|
7
|
+
require_relative "squirreldb/types"
|
|
8
|
+
require_relative "squirreldb/client"
|
|
9
|
+
require_relative "squirreldb/storage"
|
|
10
|
+
require_relative "squirreldb/cache"
|
|
11
|
+
|
|
12
|
+
module SquirrelDB
|
|
13
|
+
VERSION = "0.1.0"
|
|
14
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: squirreldb-sdk
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- SquirrelDB Contributors
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-01-29 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: websocket-client-simple
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.9'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.9'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: json
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
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: minitest
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '5.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '5.0'
|
|
68
|
+
description: A Ruby client for connecting to SquirrelDB realtime database
|
|
69
|
+
executables: []
|
|
70
|
+
extensions: []
|
|
71
|
+
extra_rdoc_files: []
|
|
72
|
+
files:
|
|
73
|
+
- lib/squirreldb.rb
|
|
74
|
+
- lib/squirreldb/cache.rb
|
|
75
|
+
- lib/squirreldb/client.rb
|
|
76
|
+
- lib/squirreldb/storage.rb
|
|
77
|
+
- lib/squirreldb/types.rb
|
|
78
|
+
homepage: ''
|
|
79
|
+
licenses:
|
|
80
|
+
- MIT
|
|
81
|
+
metadata: {}
|
|
82
|
+
rdoc_options: []
|
|
83
|
+
require_paths:
|
|
84
|
+
- lib
|
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 3.0.0
|
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '0'
|
|
95
|
+
requirements: []
|
|
96
|
+
rubygems_version: 3.6.2
|
|
97
|
+
specification_version: 4
|
|
98
|
+
summary: Ruby client for SquirrelDB
|
|
99
|
+
test_files: []
|