qstash 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []