jobcelis 1.2.1 → 1.4.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 +4 -4
- data/LICENSE +67 -21
- data/README.md +23 -0
- data/lib/jobcelis/client.rb +130 -48
- data/lib/jobcelis.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6063822db94053ab346b13e3bd90b843f62bb0cff1c86554b8999c147d29d5cb
|
|
4
|
+
data.tar.gz: 3c873ce53d2304093e6c1632aa439d72765c1373a2d154a48b102733c4580b93
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4935d8e1b10c18224ffb04a5d9908455720ed6e27a6093d4428b2116d153cb9814a6765cc0464abfdb8f791fdbe92f6562918f5be3f53da1d2bd735514b1e696
|
|
7
|
+
data.tar.gz: 85b68964a08b4b91df15f684eba19e5ea694e35ef317188358dfead5bc59ba7458a142cafa303c373f89d28f0ae27b6d15e1a775f1070a444bfc822dacc5d4e7
|
data/LICENSE
CHANGED
|
@@ -1,21 +1,67 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
Business Source License 1.1
|
|
2
|
+
|
|
3
|
+
Parameters
|
|
4
|
+
|
|
5
|
+
Licensor: Vladimir Celi
|
|
6
|
+
Licensed Work: Jobcelis
|
|
7
|
+
The Licensed Work is (c) 2026 Vladimir Celi.
|
|
8
|
+
Additional Use Grant: You may make production use of the Licensed Work,
|
|
9
|
+
provided your use does not include offering the
|
|
10
|
+
Licensed Work to third parties as a hosted or
|
|
11
|
+
managed service, where the service provides users
|
|
12
|
+
with access to any substantial set of the features
|
|
13
|
+
or functionality of the Licensed Work.
|
|
14
|
+
Change Date: 2030-03-08
|
|
15
|
+
Change License: Apache License, Version 2.0
|
|
16
|
+
|
|
17
|
+
For information about alternative licensing arrangements for the Licensed Work,
|
|
18
|
+
please contact: support@jobcelis.com
|
|
19
|
+
|
|
20
|
+
Notice
|
|
21
|
+
|
|
22
|
+
Business Source License 1.1 (BUSL-1.1)
|
|
23
|
+
|
|
24
|
+
License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
|
|
25
|
+
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
|
26
|
+
|
|
27
|
+
Terms
|
|
28
|
+
|
|
29
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
30
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
31
|
+
Licensor may make an Additional Use Grant, above, permitting limited
|
|
32
|
+
production use.
|
|
33
|
+
|
|
34
|
+
Effective on the Change Date, or the fourth anniversary of the first publicly
|
|
35
|
+
available distribution of a specific version of the Licensed Work under this
|
|
36
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
37
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
38
|
+
above terminate.
|
|
39
|
+
|
|
40
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
41
|
+
currently in effect as described in this License, you must purchase a
|
|
42
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
43
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
44
|
+
|
|
45
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
46
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
47
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
48
|
+
for each version of the Licensed Work released by Licensor.
|
|
49
|
+
|
|
50
|
+
You must conspicuously display this License on each original or modified copy
|
|
51
|
+
of the Licensed Work. If you receive the Licensed Work in original or
|
|
52
|
+
modified form from a third party, the terms and conditions set forth in this
|
|
53
|
+
License apply to your use of that work.
|
|
54
|
+
|
|
55
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
56
|
+
terminate your rights under this License for the current and all other
|
|
57
|
+
versions of the Licensed Work.
|
|
58
|
+
|
|
59
|
+
This License does not grant you any right in any trademark or logo of
|
|
60
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
61
|
+
Licensor as expressly required by this License).
|
|
62
|
+
|
|
63
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
64
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
65
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
66
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
67
|
+
TITLE.
|
data/README.md
CHANGED
|
@@ -248,6 +248,9 @@ topics = client.list_topics
|
|
|
248
248
|
# API token
|
|
249
249
|
token = client.get_token
|
|
250
250
|
new_token = client.regenerate_token
|
|
251
|
+
|
|
252
|
+
# Limit the token to scopes and client IPs (empty allowed_ips = any IP)
|
|
253
|
+
client.update_token_restrictions(scopes: ["events:read", "events:write"], allowed_ips: ["203.0.113.10"])
|
|
251
254
|
```
|
|
252
255
|
|
|
253
256
|
## Multi-Project Management
|
|
@@ -346,6 +349,22 @@ rescue Jobcelis::Error => e
|
|
|
346
349
|
end
|
|
347
350
|
```
|
|
348
351
|
|
|
352
|
+
## Retries
|
|
353
|
+
|
|
354
|
+
The client retries a request automatically when the connection fails (refused or reset connection, DNS or TLS failure, timeout) or the API answers 429, 502, 503 or 504, for example while Jobcelis is being deployed. It waits 1 s, 2 s, 4 s, 8 s, 16 s and 30 s between attempts (with ±20 % jitter), or what the `Retry-After` header asks for, up to 60 s. By default it makes up to 6 retries, about 60 s in total. Other errors, such as 400 or 500, are raised immediately as `Jobcelis::Error`; after the last retry you get the same error you would get without retries.
|
|
355
|
+
|
|
356
|
+
GET, PUT, PATCH and DELETE are retried in all of these cases. POST is retried on 429/502/503/504, and after a connection failure only when it carries an idempotency key, so a request can never be applied twice:
|
|
357
|
+
|
|
358
|
+
- `send_event` sends an `X-Idempotency-Key` header, the same on every retry. Pass `idempotency_key:` to use your own key; otherwise a UUID is generated.
|
|
359
|
+
- `send_events` adds an `idempotency_key` to every event that does not have one.
|
|
360
|
+
|
|
361
|
+
Change or disable retries when you create the client:
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
client = Jobcelis::Client.new(api_key: "your_api_key", max_retries: 3)
|
|
365
|
+
client = Jobcelis::Client.new(api_key: "your_api_key", max_retries: 0) # no retries
|
|
366
|
+
```
|
|
367
|
+
|
|
349
368
|
## Webhook Signature Verification
|
|
350
369
|
|
|
351
370
|
```ruby
|
|
@@ -374,3 +393,7 @@ end
|
|
|
374
393
|
## License
|
|
375
394
|
|
|
376
395
|
BSL-1.1 (Business Source License)
|
|
396
|
+
|
|
397
|
+
## Releasing
|
|
398
|
+
|
|
399
|
+
Merge to `main`: the [Release workflow](.github/workflows/release.yml) publishes every change to the package as the next patch version (1.3.1 → 1.3.2) to RubyGems, writes the version into `jobcelis.gemspec` and `lib/jobcelis.rb` and tags the commit. Changes to docs, tests or CI publish nothing. For a minor or major version, raise it in `jobcelis.gemspec` and `lib/jobcelis.rb` in the same pull request. Every pull request runs [CI](.github/workflows/ci.yml), which runs the tests (`ruby -Ilib -Itest -e 'Dir["test/**/*_test.rb"].each { |f| require File.expand_path(f) }'`).
|
data/lib/jobcelis/client.rb
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
require "net/http"
|
|
4
4
|
require "json"
|
|
5
5
|
require "uri"
|
|
6
|
+
require "time"
|
|
7
|
+
require "openssl"
|
|
8
|
+
require "securerandom"
|
|
6
9
|
|
|
7
10
|
module Jobcelis
|
|
8
11
|
# Client for the Jobcelis Event Infrastructure Platform API.
|
|
@@ -12,13 +15,32 @@ module Jobcelis
|
|
|
12
15
|
# client = Jobcelis::Client.new(api_key: "your_api_key")
|
|
13
16
|
#
|
|
14
17
|
class Client
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
# Responses that mean the request was not processed and can be sent again.
|
|
19
|
+
RETRYABLE_STATUSES = [429, 502, 503, 504].freeze
|
|
20
|
+
|
|
21
|
+
# Connection, DNS, TLS and timeout failures.
|
|
22
|
+
NETWORK_ERRORS = [
|
|
23
|
+
SocketError, IOError, EOFError, Timeout::Error, OpenSSL::SSL::SSLError,
|
|
24
|
+
Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EHOSTUNREACH,
|
|
25
|
+
Errno::ENETUNREACH, Errno::ETIMEDOUT, Errno::EPIPE
|
|
26
|
+
].freeze
|
|
27
|
+
|
|
28
|
+
MAX_BACKOFF = 30.0
|
|
29
|
+
MAX_RETRY_AFTER = 60.0
|
|
30
|
+
|
|
31
|
+
# @param api_key [String] Your project API key.
|
|
32
|
+
# @param base_url [String] Base URL of the Jobcelis API (default: https://jobcelis.com).
|
|
33
|
+
# @param timeout [Integer] Request timeout in seconds (default: 30).
|
|
34
|
+
# @param max_retries [Integer] Retries for network errors and 429/502/503/504 responses
|
|
35
|
+
# (default: 6, about 60 s in total). 0 disables retries.
|
|
36
|
+
# @param retry_base_delay [Float] Delay in seconds before the first retry, doubled on each
|
|
37
|
+
# following one up to 30 s (default: 1.0).
|
|
38
|
+
def initialize(api_key:, base_url: "https://jobcelis.com", timeout: 30, max_retries: 6, retry_base_delay: 1.0)
|
|
19
39
|
@api_key = api_key
|
|
20
40
|
@base_url = base_url.chomp("/")
|
|
21
41
|
@timeout = timeout
|
|
42
|
+
@max_retries = [Integer(max_retries), 0].max
|
|
43
|
+
@retry_base_delay = Float(retry_base_delay)
|
|
22
44
|
@auth_token = nil
|
|
23
45
|
end
|
|
24
46
|
|
|
@@ -58,13 +80,19 @@ module Jobcelis
|
|
|
58
80
|
# ------------------------------------------------------------------
|
|
59
81
|
|
|
60
82
|
# Send a single event.
|
|
83
|
+
#
|
|
84
|
+
# The request carries an X-Idempotency-Key header so it can be retried
|
|
85
|
+
# safely: pass +idempotency_key:+ to choose it, otherwise a UUID is generated.
|
|
61
86
|
def send_event(topic, payload, **kwargs)
|
|
62
|
-
|
|
87
|
+
key = kwargs[:idempotency_key] || kwargs["idempotency_key"] || new_idempotency_key
|
|
88
|
+
request("POST", "/api/v1/events", body: { topic: topic, payload: payload, **kwargs }, idempotency_key: key.to_s)
|
|
63
89
|
end
|
|
64
90
|
|
|
65
91
|
# Send up to 1000 events in a batch.
|
|
92
|
+
#
|
|
93
|
+
# Events without an +idempotency_key+ get a generated one so the batch can be retried safely.
|
|
66
94
|
def send_events(events)
|
|
67
|
-
post("/api/v1/events/batch", { events: events })
|
|
95
|
+
post("/api/v1/events/batch", { events: with_idempotency_keys(events) })
|
|
68
96
|
end
|
|
69
97
|
|
|
70
98
|
# Get event details.
|
|
@@ -389,6 +417,12 @@ module Jobcelis
|
|
|
389
417
|
post("/api/v1/token/regenerate", {})
|
|
390
418
|
end
|
|
391
419
|
|
|
420
|
+
# Limit the API token to scopes and client IPs. Omitted arguments are left
|
|
421
|
+
# unchanged; an empty allowed_ips list means any IP.
|
|
422
|
+
def update_token_restrictions(scopes: nil, allowed_ips: nil)
|
|
423
|
+
patch("/api/v1/token", { scopes: scopes, allowed_ips: allowed_ips }.compact)
|
|
424
|
+
end
|
|
425
|
+
|
|
392
426
|
# ------------------------------------------------------------------
|
|
393
427
|
# Projects (multi)
|
|
394
428
|
# ------------------------------------------------------------------
|
|
@@ -646,53 +680,95 @@ module Jobcelis
|
|
|
646
680
|
end
|
|
647
681
|
|
|
648
682
|
def public_post(path, body)
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
req = Net::HTTP::Post.new(uri)
|
|
653
|
-
req["Content-Type"] = "application/json"
|
|
654
|
-
req.body = JSON.generate(body)
|
|
683
|
+
resp = send_with_retries("POST", path, body: body, auth: false)
|
|
684
|
+
handle_response(resp)
|
|
685
|
+
end
|
|
655
686
|
|
|
656
|
-
|
|
687
|
+
def request(method, path, params: nil, body: nil, idempotency_key: nil)
|
|
688
|
+
resp = send_with_retries(method, path, params: params, body: body, idempotency_key: idempotency_key)
|
|
657
689
|
handle_response(resp)
|
|
658
690
|
end
|
|
659
691
|
|
|
660
|
-
def
|
|
692
|
+
def request_raw(method, path, params: nil)
|
|
693
|
+
resp = send_with_retries(method, path, params: params)
|
|
694
|
+
raise_for_status(resp)
|
|
695
|
+
resp.body
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
# Sends the request, retrying network errors and 429/502/503/504 responses
|
|
699
|
+
# with exponential backoff. POST requests are only retried after a network
|
|
700
|
+
# error when they carry an idempotency key, so they can never be applied twice.
|
|
701
|
+
def send_with_retries(method, path, params: nil, body: nil, auth: true, idempotency_key: nil)
|
|
661
702
|
uri = build_uri(path, params)
|
|
662
|
-
|
|
703
|
+
payload = JSON.generate(body) if body
|
|
704
|
+
idempotent = method.upcase != "POST" || !idempotency_key.nil?
|
|
705
|
+
attempt = 0
|
|
663
706
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
707
|
+
loop do
|
|
708
|
+
begin
|
|
709
|
+
resp = perform_request(method, uri, payload, auth, idempotency_key)
|
|
710
|
+
rescue *NETWORK_ERRORS
|
|
711
|
+
raise if !idempotent || attempt >= @max_retries
|
|
669
712
|
|
|
670
|
-
|
|
671
|
-
|
|
713
|
+
attempt += 1
|
|
714
|
+
sleep(backoff_delay(attempt))
|
|
715
|
+
next
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
return resp unless RETRYABLE_STATUSES.include?(resp.code.to_i) && attempt < @max_retries
|
|
719
|
+
|
|
720
|
+
attempt += 1
|
|
721
|
+
sleep(retry_after(resp) || backoff_delay(attempt))
|
|
722
|
+
end
|
|
672
723
|
end
|
|
673
724
|
|
|
674
|
-
def
|
|
675
|
-
uri = build_uri(path, params)
|
|
725
|
+
def perform_request(method, uri, payload, auth, idempotency_key)
|
|
676
726
|
http = build_http(uri)
|
|
677
|
-
|
|
678
727
|
req = build_request(method, uri)
|
|
679
728
|
req["Content-Type"] = "application/json"
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
resp = http.request(req)
|
|
684
|
-
|
|
685
|
-
unless resp.is_a?(Net::HTTPSuccess)
|
|
686
|
-
detail = begin
|
|
687
|
-
parsed = JSON.parse(resp.body)
|
|
688
|
-
parsed.is_a?(Hash) ? (parsed["error"] || parsed) : parsed
|
|
689
|
-
rescue JSON::ParserError
|
|
690
|
-
resp.body
|
|
691
|
-
end
|
|
692
|
-
raise Jobcelis::Error.new(resp.code.to_i, detail)
|
|
729
|
+
if auth
|
|
730
|
+
req["X-Api-Key"] = @api_key
|
|
731
|
+
req["Authorization"] = "Bearer #{@auth_token}" if @auth_token
|
|
693
732
|
end
|
|
733
|
+
req["X-Idempotency-Key"] = idempotency_key if idempotency_key
|
|
734
|
+
req.body = payload if payload
|
|
735
|
+
http.request(req)
|
|
736
|
+
end
|
|
694
737
|
|
|
695
|
-
|
|
738
|
+
# Delay before retry number +attempt+ (1-based): base * 2^(attempt-1), capped, with +/-20% jitter.
|
|
739
|
+
def backoff_delay(attempt)
|
|
740
|
+
delay = [@retry_base_delay * (2**(attempt - 1)), MAX_BACKOFF].min
|
|
741
|
+
delay * (0.8 + (rand * 0.4))
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
# Seconds to wait from a Retry-After header (seconds or HTTP date), capped at 60 s.
|
|
745
|
+
def retry_after(resp)
|
|
746
|
+
value = resp["Retry-After"]
|
|
747
|
+
return nil if value.nil? || value.strip.empty?
|
|
748
|
+
|
|
749
|
+
seconds = if value.strip.match?(/\A\d+(\.\d+)?\z/)
|
|
750
|
+
value.to_f
|
|
751
|
+
else
|
|
752
|
+
Time.httpdate(value.strip) - Time.now
|
|
753
|
+
end
|
|
754
|
+
seconds.clamp(0, MAX_RETRY_AFTER)
|
|
755
|
+
rescue ArgumentError
|
|
756
|
+
nil
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
def new_idempotency_key
|
|
760
|
+
SecureRandom.uuid
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
# Gives every batch item without an idempotency_key its own key, without
|
|
764
|
+
# changing the caller's hashes, so a retried batch never duplicates events.
|
|
765
|
+
def with_idempotency_keys(events)
|
|
766
|
+
events.map do |event|
|
|
767
|
+
next event unless event.is_a?(Hash)
|
|
768
|
+
next event if event.key?(:idempotency_key) || event.key?("idempotency_key")
|
|
769
|
+
|
|
770
|
+
event.merge(idempotency_key: new_idempotency_key)
|
|
771
|
+
end
|
|
696
772
|
end
|
|
697
773
|
|
|
698
774
|
def build_uri(path, params = nil)
|
|
@@ -709,6 +785,8 @@ module Jobcelis
|
|
|
709
785
|
http.use_ssl = (uri.scheme == "https")
|
|
710
786
|
http.open_timeout = @timeout
|
|
711
787
|
http.read_timeout = @timeout
|
|
788
|
+
# Retries are handled by send_with_retries
|
|
789
|
+
http.max_retries = 0
|
|
712
790
|
http
|
|
713
791
|
end
|
|
714
792
|
|
|
@@ -725,19 +803,23 @@ module Jobcelis
|
|
|
725
803
|
end
|
|
726
804
|
|
|
727
805
|
def handle_response(resp)
|
|
728
|
-
|
|
729
|
-
detail = begin
|
|
730
|
-
parsed = JSON.parse(resp.body)
|
|
731
|
-
parsed.is_a?(Hash) ? (parsed["error"] || parsed) : parsed
|
|
732
|
-
rescue JSON::ParserError
|
|
733
|
-
resp.body
|
|
734
|
-
end
|
|
735
|
-
raise Jobcelis::Error.new(resp.code.to_i, detail)
|
|
736
|
-
end
|
|
806
|
+
raise_for_status(resp)
|
|
737
807
|
|
|
738
808
|
return nil if resp.code == "204"
|
|
739
809
|
|
|
740
810
|
JSON.parse(resp.body)
|
|
741
811
|
end
|
|
812
|
+
|
|
813
|
+
def raise_for_status(resp)
|
|
814
|
+
return if resp.is_a?(Net::HTTPSuccess)
|
|
815
|
+
|
|
816
|
+
detail = begin
|
|
817
|
+
parsed = JSON.parse(resp.body)
|
|
818
|
+
parsed.is_a?(Hash) ? (parsed["error"] || parsed) : parsed
|
|
819
|
+
rescue JSON::ParserError
|
|
820
|
+
resp.body
|
|
821
|
+
end
|
|
822
|
+
raise Jobcelis::Error.new(resp.code.to_i, detail)
|
|
823
|
+
end
|
|
742
824
|
end
|
|
743
825
|
end
|
data/lib/jobcelis.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jobcelis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jobcelis
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-11-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: net-http
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: base64
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.2'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.2'
|
|
27
41
|
description: Ruby client for the Jobcelis API — events, webhooks, jobs, pipelines,
|
|
28
42
|
and more. Connects to https://jobcelis.com by default.
|
|
29
43
|
email: support@jobcelis.com
|
|
@@ -42,7 +56,7 @@ licenses:
|
|
|
42
56
|
- BUSL-1.1
|
|
43
57
|
metadata:
|
|
44
58
|
homepage_uri: https://jobcelis.com
|
|
45
|
-
source_code_uri: https://github.com/
|
|
59
|
+
source_code_uri: https://github.com/jobcelis-team/jobcelis-ruby
|
|
46
60
|
documentation_uri: https://jobcelis.com/docs
|
|
47
61
|
post_install_message:
|
|
48
62
|
rdoc_options: []
|