qstash 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cd39eb04db27197abf82b76f458ac5c7142d6ddb976efd81e4b87c3319c91245
4
+ data.tar.gz: 8f2d438cdf82010113c1298bb0de7b844f53ef6cff656eb796b450334eed2856
5
+ SHA512:
6
+ metadata.gz: d6d60b5e86997cbb8a3e477646b1eb3008ee7fe30979b8d59d282d213b20631f2f1c07d480f0b3f4df980bcad9f6d12611ee1657321a1ac017694dd1ab841d91
7
+ data.tar.gz: d162c3b3e312921ed357d46c619904f3d6896e8979dcb5d032a8ad2363b9dc034113dd2888ed07a86919ff0c09e90b1fe25a37e44df551984363e029e38681ad
data/.dockerignore ADDED
@@ -0,0 +1,9 @@
1
+ ./.bundle
2
+ /vendor/
3
+ Gemfile.lock
4
+
5
+ /log/*
6
+ /tmp/*
7
+ !/log/.keep
8
+ !/tmp/.keep
9
+
@@ -0,0 +1,28 @@
1
+ name: ci
2
+ on: push
3
+ jobs:
4
+ test:
5
+ runs-on: ubuntu-latest
6
+ strategy:
7
+ matrix:
8
+ ruby: [3.3]
9
+ steps:
10
+ - uses: actions/checkout@v2
11
+ - uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: ${{ matrix.ruby }}
14
+ bundler-cache: true
15
+ - run: bundle exec rake test
16
+
17
+ lint:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ matrix:
21
+ ruby: [3.3]
22
+ steps:
23
+ - uses: actions/checkout@v2
24
+ - uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: ${{ matrix.ruby }}
27
+ bundler-cache: true
28
+ - run: bundle exec rake lint
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ Gemfile.lock
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ parallel: true
2
+ format: progress
3
+
data/Dockerfile-3.3 ADDED
@@ -0,0 +1,30 @@
1
+ FROM ruby:3.3-alpine as base
2
+
3
+ RUN apk add --update --no-cache \
4
+ build-base \
5
+ cmake \
6
+ tzdata \
7
+ bash \
8
+ git
9
+
10
+ ENV APP_PATH /var/www/qstash-rb
11
+ RUN mkdir -p $APP_PATH
12
+
13
+ # Build intermediate
14
+ FROM base as intermediate
15
+
16
+ WORKDIR $APP_PATH
17
+
18
+ RUN rm -rf /var/cache/apk/*
19
+
20
+ FROM base as development
21
+
22
+ COPY --from=intermediate $APP_PATH $APP_PATH
23
+
24
+ WORKDIR $APP_PATH
25
+
26
+ ENV GEM_HOME $APP_PATH/vendor/bundle
27
+ ENV BUNDLE_PATH vendor/bundle
28
+
29
+ COPY . ./
30
+ RUN bundle check || bundle install
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in qstash-rb.gemspec
4
+ gemspec
5
+
6
+ gem "rake", ">= 12.0"
7
+ gem "minitest", "~> 5.0"
8
+ gem "minitest-reporters", ">= 1.4"
9
+ gem "mocha", ">= 1.12"
10
+ gem "pry"
11
+
12
+ gem "guard"
13
+ gem "guard-minitest"
14
+ gem "yard"
15
+
16
+ gem "standard"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Drew Monroe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # QStash
2
+
3
+ [![ci](https://github.com/dvmonroe/qstash-rb/actions/workflows/ci.yml/badge.svg)](https://github.com/dvmonroe/qstash-rb/actions/workflows/ci.yml)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/56ffdaabb1df70536c88/maintainability)](https://codeclimate.com/github/dvmonroe/qstash-rb/maintainability)
5
+
6
+ A Ruby client for the [QStash](https://upstash.com/qstash) service from Upstash.
7
+
8
+ QStash is an HTTP-based messaging and scheduling solution, designed for serverless and edge computing environments, guaranteeing at-least-once delivery.
9
+
10
+ If you're coming from a Rails background, you'll be familiar with ActiveJob, Sidekiq, and/or DelayedJob for background job processing. QStash can run background jobs which may/may not be a use case if you're using Ruby in a serverless environment but it's also much more than that. It can:
11
+
12
+ - Schedule messages to be delivered at a later time.
13
+ - Serve as a FIFO message queue.
14
+ - Serve as a push based fanout exchange (think RabbitMQ).
15
+
16
+ ## Contents
17
+
18
+ - [QStash](#qstash)
19
+ - [Contents](#contents)
20
+ - [Getting Started](#getting-started)
21
+ - [Messages](#messages)
22
+ - [Events](#events)
23
+ - [Development](#development)
24
+ - [Contributing](#contributing)
25
+ - [License](#license)
26
+
27
+
28
+ ## Getting Started
29
+
30
+ Add this line to your application's Gemfile:
31
+
32
+ ```ruby
33
+ gem 'qstash'
34
+ ```
35
+
36
+ Setup your thread safe configuration:
37
+
38
+ ```ruby
39
+ QStash.configure do |q|
40
+ q.token = "your-qstash-token"
41
+ q.url = "https://qstash.upstash.io"
42
+ end
43
+ ```
44
+
45
+ or you can set the following ENV variables at runtime:
46
+
47
+ ```bash
48
+ QSTASH_TOKEN=your-qstash-token
49
+ QSTASH_URL=https://qstash.upstash.io
50
+ ```
51
+
52
+ ## Messages
53
+
54
+ #### Publish
55
+ To [publish a message](https://upstash.com/docs/qstash/api/publish) to a queue:
56
+
57
+ ```ruby
58
+ QStash::Message.publish(destination: "https://example.com/api/message-receiver", body: "Hello, World!")
59
+ ```
60
+
61
+ From Upstash's docs:
62
+
63
+ > Destination can either be a topic name or id that you configured in the Upstash console, a valid url where the message gets sent to, or a valid QStash API name like api/llm. If the destination is a URL, make sure the URL is prefixed with a valid protocol (http:// or https://)
64
+
65
+ You can also pass in headers. We help format Upstash's headers for you if you pass them in:
66
+
67
+ ```
68
+ - "Upstash-Method",
69
+ - "Upstash-Timeout",
70
+ - "Upstash-Retries",
71
+ - "Upstash-Callback",
72
+ - "Upstash-Failure-Callback",
73
+ - "Upstash-Forward-*
74
+ - "Upstash-Delay",
75
+ - "Upstash-Not-Before",
76
+ - "Upstash-Deduplication-Id",
77
+ - "Upstash-Content-Based-Deduplication"
78
+ ```
79
+
80
+ ```ruby
81
+ QStash::Message.publish(
82
+ destination: "https://example.com/api/message-receiver",
83
+ body: "Hello, World!",
84
+ headers: { upstash_retries: 2 }
85
+ )
86
+ ```
87
+
88
+ #### Enqueue
89
+
90
+ To [enqueue a message](https://upstash.com/docs/qstash/api/enqueue) to a queue:
91
+
92
+ ```ruby
93
+ QStash::Message.enqueue(
94
+ queue_name: "my-queue",
95
+ destination: "https://example.com/api/message-receiver",
96
+ body: "Hello, World!"
97
+ )
98
+ ```
99
+
100
+ #### Batch
101
+
102
+ To [batch publish messages](https://upstash.com/docs/qstash/api/messages/batch):
103
+
104
+ ```ruby
105
+ QStash::Message.batch_publish(messages: [
106
+ {
107
+ destination: "https://example.com/api/message-receiver",
108
+ body: "Hello, World!"
109
+ },
110
+ {
111
+ destination: "https://example.com/api/message-receiver",
112
+ body: "Hello, World Again!",
113
+ headers: { upstash_retries: 2 }
114
+ }
115
+ ])
116
+ ```
117
+
118
+ #### Get
119
+
120
+ To [get a message](https://upstash.com/docs/qstash/api/messages/get) from a queue:
121
+
122
+ ```ruby
123
+ QStash::Message.get("1234") # 1234 is the message id
124
+ ```
125
+
126
+ #### Cancel
127
+
128
+ To [cancel a message](https://upstash.com/docs/qstash/api/messages/cancel) from a queue:
129
+
130
+ ```ruby
131
+ QStash::Message.cancel("1234") # 1234 is the message id
132
+ ```
133
+
134
+ #### Bulk Cancel
135
+
136
+ To [bulk cancel messages](https://upstash.com/docs/qstash/api/messages/bulk-cancel) from a queue:
137
+
138
+ ```ruby
139
+ QStash::Message.cancel(["1234", "5678"])
140
+ ```
141
+
142
+ ## Events
143
+
144
+ #### List
145
+
146
+ To [list all events](https://upstash.com/docs/qstash/api/events/list):
147
+
148
+ ```ruby
149
+ QStash::Event.list
150
+ ```
151
+
152
+ You can pass in any filters available from the [Upstash docs](https://upstash.com/docs/qstash/api/events/list).
153
+
154
+ ```ruby
155
+ QStash::Event.list(filters: {
156
+ queueName: "my-queue",
157
+ fromDate: Time.now - 1.day,
158
+ toDate: Time.now
159
+ })
160
+ ```
161
+
162
+ ## Signing Keys
163
+
164
+ #### Get
165
+
166
+ To get your current [signing keys](https://upstash.com/docs/qstash/api/signingKeys/get):
167
+
168
+ ```ruby
169
+ QStash::SigningKeys.get
170
+ ```
171
+
172
+ #### Rotate
173
+
174
+ To [rotate signing keys](https://upstash.com/docs/qstash/api/signingKeys/rotate):
175
+
176
+ ```ruby
177
+ QStash::SigningKeys.rotate
178
+ ```
179
+
180
+ ## Dead Letter Queue
181
+
182
+ #### Get
183
+
184
+ To [get dead letter messages](https://upstash.com/docs/qstash/api/dlq/getMessage):
185
+
186
+ ```ruby
187
+ QStash::DLQ.get("1234") # 1234 is the dlq id of the message
188
+ ```
189
+
190
+ #### List
191
+
192
+ To [list dead letter messages](https://upstash.com/docs/qstash/api/dlq/listMessages):
193
+
194
+ ```ruby
195
+ QStash::DLQ.list
196
+ ```
197
+
198
+ To [list dead letter messages with filters](https://upstash.com/docs/qstash/api/dlq/listMessages):
199
+
200
+ ```ruby
201
+ QStash::DLQ.list(filters: {
202
+ queueName: "my-queue",
203
+ fromDate: Time.now - 1.day,
204
+ toDate: Time.now
205
+ })
206
+ ```
207
+
208
+ #### Delete
209
+
210
+ To [delete dead letter messages](https://upstash.com/docs/qstash/api/dlq/deleteMessage):
211
+
212
+ ```ruby
213
+ QStash::DLQ.delete("1234") # 1234 is the dlq id of the message
214
+ ```
215
+
216
+ #### Delete Multiple
217
+
218
+ To [delete multiple dead letter messages](https://upstash.com/docs/qstash/api/dlq/deleteMessages):
219
+
220
+ ```ruby
221
+ QStash::DLQ.delete(["1234", "5678"])
222
+ ```
223
+
224
+ ## Development
225
+
226
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
227
+
228
+ To install this gem onto your local machine, run `bundle exec rake install`.
229
+
230
+ ### Testing across all supported versions:
231
+
232
+ To run tests across all gem supported ruby versions (requires Docker):
233
+ ```sh
234
+ bin/dev-test
235
+ ```
236
+ To run lint across all gem supported ruby versions (requires Docker):
237
+ ```sh
238
+ bin/dev-lint
239
+ ```
240
+
241
+ ## Contributing
242
+
243
+ Bug reports and pull requests are welcome on GitHub at https://github.com/dvmonroe/qstash-rb.
244
+
245
+ ## License
246
+
247
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task default: :test
11
+
12
+ task :lint do
13
+ if ENV["CI"]
14
+ sh "bin/standardrb"
15
+ else
16
+ sh "bin/standardrb --fix"
17
+ end
18
+ end
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "qstash"
5
+
6
+ require "pry"
7
+ Pry.start
data/bin/dev-lint ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ system "docker-compose run --rm 3.3 bin/standardrb"
data/bin/dev-test ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ system "docker-compose run --rm 3.3 bundle exec rake test"
data/bin/setup ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
data/bin/standardrb ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'standardrb' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("standard", "standardrb")
@@ -0,0 +1,20 @@
1
+ version: '3.9'
2
+
3
+ services:
4
+ '3.3':
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile-3.3
8
+ tty: true
9
+ stdin_open: true
10
+ volumes:
11
+ - ./bin:/var/www/qstash-rb/bin/
12
+ - ./lib:/var/www/qstash-rb/lib/
13
+ - ./test:/var/www/qstash-rb/test/
14
+ container_name: qstah-rb-3.3
15
+ command: bash
16
+
17
+ volumes:
18
+ bin:
19
+ lib:
20
+ test:
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QStash
4
+ module Callable
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def call(*args, **)
11
+ if args.empty?
12
+ new(**).call
13
+ else
14
+ new(*args, **).call
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ module QStash
2
+ module Configuration
3
+ extend self
4
+
5
+ def token
6
+ Thread.current[:qstash_token] || ENV.fetch("QSTASH_TOKEN", "")
7
+ end
8
+
9
+ def token=(value)
10
+ Thread.current[:qstash_token] = value
11
+ end
12
+
13
+ def url
14
+ Thread.current[:qstash_url] || ENV.fetch("QSTASH_URL", "https://qstash.upstash.io")
15
+ end
16
+
17
+ def url=(value)
18
+ Thread.current[:qstash_url] = value
19
+ end
20
+
21
+ def configure
22
+ yield self
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://upstash.com/docs/qstash/api/dlq/deleteMessage
4
+ module QStash
5
+ module DLQ
6
+ class Delete
7
+ include QStash::Callable
8
+ attr_reader :dlq_ids, :headers
9
+
10
+ def initialize(dlq_ids, headers: {})
11
+ @dlq_ids = Array(dlq_ids)
12
+ @headers = headers
13
+ end
14
+
15
+ def call
16
+ uri = URI(endpoint)
17
+ client = QStash::HttpClient.new(uri)
18
+ client.delete(body, headers)
19
+ end
20
+
21
+ private
22
+
23
+ def base_url
24
+ QStash.config.url.sub(/\/$/, "")
25
+ end
26
+
27
+ def body
28
+ (dlq_ids.length > 1) ? {dlqIds: dlq_ids} : {}
29
+ end
30
+
31
+ def endpoint
32
+ [
33
+ base_url,
34
+ (dlq_ids.length > 1) ? Endpoints::DLQ_ENDPOINT : "#{Endpoints::DLQ_ENDPOINT}/#{dlq_ids.first}"
35
+ ].join("/")
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://upstash.com/docs/qstash/api/dlq/getMessage
4
+ module QStash
5
+ module DLQ
6
+ class Get
7
+ include QStash::Callable
8
+ attr_reader :headers, :dlq_id
9
+
10
+ def initialize(dlq_id, headers: {})
11
+ @dlq_id = dlq_id
12
+ @headers = headers
13
+ end
14
+
15
+ def call
16
+ uri = URI(endpoint)
17
+ client = QStash::HttpClient.new(uri)
18
+ client.get(headers)
19
+ end
20
+
21
+ private
22
+
23
+ def endpoint
24
+ [
25
+ QStash.config.url.sub(/\/$/, ""),
26
+ Endpoints::DLQ_ENDPOINT,
27
+ dlq_id
28
+ ].join("/")
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://upstash.com/docs/qstash/api/dlq/listMessages
4
+ module QStash
5
+ module DLQ
6
+ class List
7
+ include QStash::Callable
8
+ attr_reader :filters, :headers
9
+
10
+ def initialize(filters: {}, headers: {})
11
+ @filters = filters
12
+ @headers = headers
13
+ end
14
+
15
+ def call
16
+ uri = URI(endpoint)
17
+ client = QStash::HttpClient.new(uri)
18
+ client.get(headers)
19
+ end
20
+
21
+ private
22
+
23
+ def endpoint
24
+ url = [
25
+ QStash.config.url.sub(/\/$/, ""),
26
+ Endpoints::DLQ_ENDPOINT
27
+ ].join("/")
28
+
29
+ # Add query params from filters hash
30
+ url += "?#{URI.encode_www_form(filters)}" unless filters.empty?
31
+
32
+ url
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/qstash/dlq.rb ADDED
@@ -0,0 +1,23 @@
1
+ require "qstash/dlq/get"
2
+ require "qstash/dlq/list"
3
+ require "qstash/dlq/delete"
4
+
5
+ module QStash
6
+ module DLQ
7
+ class << self
8
+ def get(dlq_id, headers: {})
9
+ Get.call(dlq_id, headers: headers)
10
+ end
11
+
12
+ def list(filters: {}, headers: {})
13
+ List.call(filters: filters, headers: headers)
14
+ end
15
+
16
+ def delete(dlq_id, headers: {})
17
+ Delete.call(dlq_id, headers: headers)
18
+ end
19
+ end
20
+
21
+ private_constant :Get, :List, :Delete
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QStash
4
+ module Endpoints
5
+ # Messages
6
+ ENQUEUE_ENDPOINT = "v2/enqueue"
7
+ PUBLISH_ENDPOINT = "v2/publish"
8
+ BATCH_ENDPOINT = "v2/batch"
9
+ GET_ENDPOINT = "v2/messages"
10
+ CANCEL_ENDPOINT = "v2/messages"
11
+ BULK_CANCEL_ENDPOINT = "v2/messages"
12
+
13
+ # Events
14
+ LIST_EVENTS_ENDPOINT = "v2/events"
15
+
16
+ # Signing Keys
17
+ ROTATE_SIGNING_KEY_ENDPOINT = "v2/keys/rotate"
18
+ SIGNING_KEYS_ENDPOINT = "v2/keys"
19
+
20
+ # Dead Letter Queue
21
+ DLQ_ENDPOINT = "v2/dlq"
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://upstash.com/docs/qstash/api/events/list
4
+ module QStash
5
+ module Events
6
+ class List
7
+ include QStash::Callable
8
+ attr_reader :filters, :headers
9
+
10
+ def initialize(filters: {}, headers: {})
11
+ @filters = filters
12
+ @headers = headers
13
+ end
14
+
15
+ def call
16
+ uri = URI(endpoint)
17
+ client = QStash::HttpClient.new(uri)
18
+ client.get(headers)
19
+ end
20
+
21
+ private
22
+
23
+ def endpoint
24
+ url = [
25
+ QStash.config.url.sub(/\/$/, ""),
26
+ Endpoints::LIST_EVENTS_ENDPOINT
27
+ ].join("/")
28
+
29
+ # Add query params from filters hash
30
+ url += "?#{URI.encode_www_form(filters)}" unless filters.empty?
31
+
32
+ url
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "qstash/events/list"
4
+
5
+ module QStash
6
+ module Events
7
+ class << self
8
+ def list(filters: {}, headers: {})
9
+ List.call(filters: filters, headers: headers)
10
+ end
11
+ end
12
+
13
+ private_constant :List
14
+ end
15
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QStash
4
+ module Headers
5
+ extend self
6
+
7
+ def qstash_header_key(key)
8
+ case key.to_sym
9
+ when :content_type
10
+ "Content-Type"
11
+ when :upstash_method
12
+ "Upstash-Method"
13
+ when :upstash_timeout
14
+ "Upstash-Timeout"
15
+ when :upstash_retries
16
+ "Upstash-Retries"
17
+ when :upstash_callback
18
+ "Upstash-Callback"
19
+ when :upstash_failure_callback
20
+ "Upstash-Failure-Callback"
21
+ when /^upstash_forward_/
22
+ "Upstash-Forward-#{key.to_s.gsub(/^upstash_forward_/, "").camelize(:lower)}"
23
+ when :upstash_delay
24
+ "Upstash-Delay"
25
+ when :upstash_not_before
26
+ "Upstash-Not-Before"
27
+ when :upstash_deduplication_id
28
+ "Upstash-Deduplication-Id"
29
+ when :upstash_content_based_deduplication
30
+ "Upstash-Content-Based-Deduplication"
31
+ else
32
+ key.to_s
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QStash
4
+ class HttpClient
5
+ attr_reader :uri
6
+
7
+ def initialize(uri)
8
+ @uri = uri
9
+ end
10
+
11
+ def delete(body, headers = {})
12
+ request = Net::HTTP::Delete.new(uri)
13
+ request.body = body.to_json
14
+ set_headers(request, headers)
15
+
16
+ make_request(request)
17
+ end
18
+
19
+ def get(headers = {})
20
+ request = Net::HTTP::Get.new(uri)
21
+ set_headers(request, headers)
22
+
23
+ make_request(request)
24
+ end
25
+
26
+ def post(body, headers = {})
27
+ request = Net::HTTP::Post.new(uri)
28
+ request.body = body.to_json
29
+ set_headers(request, headers)
30
+
31
+ make_request(request)
32
+ end
33
+
34
+ private
35
+
36
+ def make_request(request)
37
+ request["Authorization"] = "Bearer #{QStash.config.token}"
38
+ request["User-Agent"] = "qstash-rb/#{QStash::VERSION}"
39
+
40
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
41
+ http.request(request).then do |response|
42
+ Response.new(response)
43
+ end
44
+ end
45
+ end
46
+
47
+ def set_headers(request, headers)
48
+ headers.each do |key, value|
49
+ request[QStash::Headers.qstash_header_key(key)] = value
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://upstash.com/docs/qstash/api/messages/batch
4
+ module QStash
5
+ module Message
6
+ class Batch
7
+ include QStash::Callable
8
+ attr_reader :messages, :headers
9
+
10
+ def initialize(messages:, headers: {})
11
+ @messages = messages
12
+ @headers = headers
13
+ end
14
+
15
+ def call
16
+ uri = URI(endpoint)
17
+ client = QStash::HttpClient.new(uri)
18
+ client.post(messages, headers)
19
+ end
20
+
21
+ private
22
+
23
+ def endpoint
24
+ [
25
+ QStash.config.url.sub(/\/$/, ""),
26
+ Endpoints::BATCH_ENDPOINT
27
+ ].join("/")
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://upstash.com/docs/qstash/api/messages/cancel
4
+ # https://upstash.com/docs/qstash/api/messages/bulk-cancel
5
+ module QStash
6
+ module Message
7
+ class Cancel
8
+ include QStash::Callable
9
+ attr_reader :message_ids, :headers
10
+
11
+ def initialize(message_ids, headers: {})
12
+ @message_ids = Array(message_ids)
13
+ @headers = headers
14
+ end
15
+
16
+ def call
17
+ uri = URI(endpoint)
18
+ client = QStash::HttpClient.new(uri)
19
+ client.delete(body, headers)
20
+ end
21
+
22
+ private
23
+
24
+ def base_url
25
+ QStash.config.url.sub(/\/$/, "")
26
+ end
27
+
28
+ def body
29
+ (message_ids.length > 1) ? {messageIds: message_ids} : {}
30
+ end
31
+
32
+ def endpoint
33
+ [
34
+ base_url,
35
+ (message_ids.length > 1) ? Endpoints::BULK_CANCEL_ENDPOINT : "#{Endpoints::CANCEL_ENDPOINT}/#{message_ids.first}"
36
+ ].join("/")
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://upstash.com/docs/qstash/api/enqueue
4
+ module QStash
5
+ module Message
6
+ class Enqueue
7
+ include QStash::Callable
8
+ attr_reader :queue_name, :destination, :body, :headers
9
+
10
+ def initialize(queue_name:, destination:, body:, headers: {})
11
+ @queue_name = queue_name
12
+ @destination = destination
13
+ @body = body
14
+ @headers = headers
15
+ end
16
+
17
+ def call
18
+ uri = URI(endpoint)
19
+ client = QStash::HttpClient.new(uri)
20
+ client.post(body, headers)
21
+ end
22
+
23
+ private
24
+
25
+ def endpoint
26
+ [
27
+ QStash.config.url.sub(/\/$/, ""),
28
+ Endpoints::ENQUEUE_ENDPOINT,
29
+ queue_name,
30
+ destination
31
+ ].join("/")
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://upstash.com/docs/qstash/api/messages/get
4
+ module QStash
5
+ module Message
6
+ class Get
7
+ include QStash::Callable
8
+ attr_reader :message_id, :headers
9
+
10
+ def initialize(message_id, headers: {})
11
+ @message_id = message_id
12
+ @headers = headers
13
+ end
14
+
15
+ def call
16
+ uri = URI(endpoint)
17
+ client = QStash::HttpClient.new(uri)
18
+ client.get(headers)
19
+ end
20
+
21
+ private
22
+
23
+ def endpoint
24
+ [
25
+ QStash.config.url.sub(/\/$/, ""),
26
+ Endpoints::GET_ENDPOINT,
27
+ message_id
28
+ ].join("/")
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://upstash.com/docs/qstash/api/publish
4
+ module QStash
5
+ module Message
6
+ class Publish
7
+ include QStash::Callable
8
+ attr_reader :destination, :body, :headers
9
+
10
+ def initialize(destination:, body:, headers: {})
11
+ @destination = destination
12
+ @body = body
13
+ @headers = headers
14
+ end
15
+
16
+ def call
17
+ uri = URI(endpoint)
18
+ client = QStash::HttpClient.new(uri)
19
+ client.post(body, headers)
20
+ end
21
+
22
+ private
23
+
24
+ def endpoint
25
+ [
26
+ QStash.config.url.sub(/\/$/, ""),
27
+ Endpoints::PUBLISH_ENDPOINT,
28
+ destination
29
+ ].join("/")
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "qstash/message/publish"
4
+ require "qstash/message/enqueue"
5
+ require "qstash/message/batch"
6
+ require "qstash/message/get"
7
+ require "qstash/message/cancel"
8
+
9
+ module QStash
10
+ module Message
11
+ class << self
12
+ def batch(messages:, headers: {})
13
+ Batch.call(messages: messages, headers: headers)
14
+ end
15
+
16
+ def cancel(message_ids, headers: {})
17
+ Cancel.call(message_ids, headers: headers)
18
+ end
19
+
20
+ def enqueue(queue_name:, destination:, body:, headers: {})
21
+ Enqueue.call(queue_name: queue_name, destination: destination, body: body, headers: headers)
22
+ end
23
+
24
+ def get(message_id, headers: {})
25
+ Get.call(message_id, headers: headers)
26
+ end
27
+
28
+ def publish(destination:, body:, headers: {})
29
+ Publish.call(destination: destination, body: body, headers: headers)
30
+ end
31
+ end
32
+
33
+ private_constant :Publish, :Enqueue, :Batch, :Get, :Cancel
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QStash
4
+ class Response
5
+ attr_reader :status, :headers, :body, :message
6
+
7
+ def initialize(response)
8
+ @status = response.code.to_i
9
+ @headers = response.to_hash
10
+ @message = response.message
11
+ @body = begin
12
+ JSON.parse(response.body)
13
+ rescue JSON::ParserError
14
+ response.body
15
+ end
16
+ end
17
+
18
+ def inspect
19
+ "#<#{self.class}:0x#{object_id.to_s(16)} #{status} #{message} readbody=true>"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://upstash.com/docs/qstash/api/signingKeys/get
4
+ module QStash
5
+ module SigningKeys
6
+ class Get
7
+ include QStash::Callable
8
+ attr_reader :headers
9
+
10
+ def initialize(headers: {})
11
+ @headers = headers
12
+ end
13
+
14
+ def call
15
+ uri = URI(endpoint)
16
+ client = QStash::HttpClient.new(uri)
17
+ client.get(headers)
18
+ end
19
+
20
+ private
21
+
22
+ def endpoint
23
+ [
24
+ QStash.config.url.sub(/\/$/, ""),
25
+ Endpoints::SIGNING_KEYS_ENDPOINT
26
+ ].join("/")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://upstash.com/docs/qstash/api/signingKeys/rotate
4
+ module QStash
5
+ module SigningKeys
6
+ class Rotate
7
+ include QStash::Callable
8
+ attr_reader :headers
9
+
10
+ def initialize(headers: {})
11
+ @headers = headers
12
+ end
13
+
14
+ def call
15
+ uri = URI(endpoint)
16
+ client = QStash::HttpClient.new(uri)
17
+ client.post({}, headers)
18
+ end
19
+
20
+ private
21
+
22
+ def endpoint
23
+ [
24
+ QStash.config.url.sub(/\/$/, ""),
25
+ Endpoints::ROTATE_SIGNING_KEY_ENDPOINT
26
+ ].join("/")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "qstash/signing_keys/get"
4
+ require "qstash/signing_keys/rotate"
5
+
6
+ module QStash
7
+ module SigningKeys
8
+ class << self
9
+ def get(headers: {})
10
+ Get.call(headers: headers)
11
+ end
12
+
13
+ def rotate(headers: {})
14
+ Rotate.call(headers: headers)
15
+ end
16
+ end
17
+
18
+ private_constant :Get, :Rotate
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module QStash
2
+ VERSION = "0.1.0"
3
+ end
data/lib/qstash.rb ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/http"
5
+ require "uri"
6
+
7
+ require "qstash/version"
8
+ require "qstash/configuration"
9
+ require "qstash/headers"
10
+ require "qstash/http_client"
11
+ require "qstash/response"
12
+ require "qstash/callable"
13
+ require "qstash/endpoints"
14
+
15
+ require "qstash/message"
16
+ require "qstash/events"
17
+ require "qstash/signing_keys"
18
+ require "qstash/dlq"
19
+
20
+ module QStash
21
+ class Error < StandardError; end
22
+
23
+ class << self
24
+ def configure
25
+ yield Configuration
26
+ end
27
+
28
+ def configuration
29
+ Configuration
30
+ end
31
+
32
+ alias_method :config, :configuration
33
+ end
34
+ end
data/qstash.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "qstash/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "qstash"
7
+ spec.version = QStash::VERSION
8
+ spec.authors = ["Drew Monroe"]
9
+ spec.email = ["drew@pinecreeklabs.com"]
10
+
11
+ spec.summary = "A Ruby client for the QStash API"
12
+ spec.description = "A Ruby client for the QStash API"
13
+ spec.homepage = "https://github.com/dvmonroe/qstash-ruby"
14
+ spec.license = "MIT"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = spec.homepage
18
+ spec.metadata["changelog_uri"] = spec.homepage
19
+
20
+ # Specify which files should be added to the gem when it is released.
21
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: qstash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Drew Monroe
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-08-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A Ruby client for the QStash API
14
+ email:
15
+ - drew@pinecreeklabs.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".dockerignore"
21
+ - ".github/workflows/ci.yml"
22
+ - ".gitignore"
23
+ - ".standard.yml"
24
+ - Dockerfile-3.3
25
+ - Gemfile
26
+ - LICENSE.txt
27
+ - README.md
28
+ - Rakefile
29
+ - bin/console
30
+ - bin/dev-lint
31
+ - bin/dev-test
32
+ - bin/setup
33
+ - bin/standardrb
34
+ - docker-compose.yml
35
+ - lib/qstash.rb
36
+ - lib/qstash/callable.rb
37
+ - lib/qstash/configuration.rb
38
+ - lib/qstash/dlq.rb
39
+ - lib/qstash/dlq/delete.rb
40
+ - lib/qstash/dlq/get.rb
41
+ - lib/qstash/dlq/list.rb
42
+ - lib/qstash/endpoints.rb
43
+ - lib/qstash/events.rb
44
+ - lib/qstash/events/list.rb
45
+ - lib/qstash/headers.rb
46
+ - lib/qstash/http_client.rb
47
+ - lib/qstash/message.rb
48
+ - lib/qstash/message/batch.rb
49
+ - lib/qstash/message/cancel.rb
50
+ - lib/qstash/message/enqueue.rb
51
+ - lib/qstash/message/get.rb
52
+ - lib/qstash/message/publish.rb
53
+ - lib/qstash/response.rb
54
+ - lib/qstash/signing_keys.rb
55
+ - lib/qstash/signing_keys/get.rb
56
+ - lib/qstash/signing_keys/rotate.rb
57
+ - lib/qstash/version.rb
58
+ - qstash.gemspec
59
+ homepage: https://github.com/dvmonroe/qstash-ruby
60
+ licenses:
61
+ - MIT
62
+ metadata:
63
+ homepage_uri: https://github.com/dvmonroe/qstash-ruby
64
+ source_code_uri: https://github.com/dvmonroe/qstash-ruby
65
+ changelog_uri: https://github.com/dvmonroe/qstash-ruby
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 3.4.17
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: A Ruby client for the QStash API
85
+ test_files: []