From 4e0f804839aac24c9744cdafdd0cfdf6ce2d5ddb Mon Sep 17 00:00:00 2001 From: Derek Collison Date: Mon, 9 Sep 2019 19:00:45 -0700 Subject: [PATCH] Initial commit Signed-off-by: Derek Collison --- .travis.yml | 6 + LICENSE | 222 +------- README.md | 44 +- TODO | 8 + bin/.gitignore | 6 + bin/basics.cr | 86 +++ bin/nats-pub.cr | 44 ++ bin/nats-req.cr | 41 ++ bin/nats-sub.cr | 56 ++ docs/NATS.html | 174 ++++++ docs/NATS/Connection.html | 569 +++++++++++++++++++ docs/NATS/Msg.html | 271 ++++++++++ docs/NATS/NUID.html | 289 ++++++++++ docs/NATS/Subscription.html | 250 +++++++++ docs/css/style.css | 629 +++++++++++++++++++++ docs/index.html | 120 +++++ docs/index.json | 1 + docs/js/doc.js | 1019 +++++++++++++++++++++++++++++++++++ docs/search-index.js | 1 + shard.yml | 19 + spec/nats/auth_spec.cr | 32 ++ spec/nats/basic_spec.cr | 189 +++++++ spec/nats/tls_spec.cr | 26 + spec/nats_spec.cr | 2 + spec/spec_helper.cr | 71 +++ src/nats.cr | 3 + src/nats/connection.cr | 517 ++++++++++++++++++ src/nats/msg.cr | 60 +++ src/nats/nuid.cr | 72 +++ src/nats/subscription.cr | 74 +++ 30 files changed, 4698 insertions(+), 203 deletions(-) create mode 100644 .travis.yml create mode 100644 TODO create mode 100644 bin/.gitignore create mode 100644 bin/basics.cr create mode 100644 bin/nats-pub.cr create mode 100644 bin/nats-req.cr create mode 100644 bin/nats-sub.cr create mode 100644 docs/NATS.html create mode 100644 docs/NATS/Connection.html create mode 100644 docs/NATS/Msg.html create mode 100644 docs/NATS/NUID.html create mode 100644 docs/NATS/Subscription.html create mode 100644 docs/css/style.css create mode 100644 docs/index.html create mode 100644 docs/index.json create mode 100644 docs/js/doc.js create mode 100644 docs/search-index.js create mode 100644 shard.yml create mode 100644 spec/nats/auth_spec.cr create mode 100644 spec/nats/basic_spec.cr create mode 100644 spec/nats/tls_spec.cr create mode 100644 spec/nats_spec.cr create mode 100644 spec/spec_helper.cr create mode 100644 src/nats.cr create mode 100644 src/nats/connection.cr create mode 100644 src/nats/msg.cr create mode 100644 src/nats/nuid.cr create mode 100644 src/nats/subscription.cr diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..19a8bbf --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: crystal +sudo: false + +script: + - crystal spec + - crystal tool format --check diff --git a/LICENSE b/LICENSE index 261eeb9..6e8101f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,21 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +The MIT License (MIT) + +Copyright (c) 2019 Derek Collison + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index b0b738f..1ad8e48 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,42 @@ -# nats.cr -Crystal client for NATS +# NATS + +Simple NATS client for the [Crystal](https://http://crystal-lang.org) programming language. + +## Installation + +1. Add the dependency to your `shard.yml`: + + ```yaml + dependencies: + NATS: + github: nats-io/nats.cr + ``` + +2. Run `shards install` + +## Usage + +```crystal +require "NATS" + +nc = NATS::Connection.new("demo.nats.io") +nc.subscribe("foo") { |msg| puts "Received '#{msg}'"} +nc.publish("foo", "Hello!") + +sub = nc.subscribe("req") do |msg| + msg.respond("ANSWER is 42") +end + +answer = nc.request("req", "Help!") +puts "Received a response '#{answer}'!" + +sub.close + +nc.flush +nc.close +``` + +## License + +Unless otherwise noted, the NATS source files are distributed under +the Apache Version 2.0 license found in the LICENSE file. diff --git a/TODO b/TODO new file mode 100644 index 0000000..d6e8e0a --- /dev/null +++ b/TODO @@ -0,0 +1,8 @@ +1. auto-unsub, max messages +2. Multiple URLs/Servers +3. Reconnect logic +4. Async INFO processing +5. Multiple servers, state logic CONNECTED/RECONNECTING/DISCONNECTED/CLOSED +6. msgs/bytes stats for connections and subscriptions +7. async request? +8. NATS 2.0 security/auth diff --git a/bin/.gitignore b/bin/.gitignore new file mode 100644 index 0000000..c397c67 --- /dev/null +++ b/bin/.gitignore @@ -0,0 +1,6 @@ +*.dwarf + +nats-pub +nats-sub +nats-req + diff --git a/bin/basics.cr b/bin/basics.cr new file mode 100644 index 0000000..3a86a5b --- /dev/null +++ b/bin/basics.cr @@ -0,0 +1,86 @@ +require "../src/nats/connection" + +nc = NATS::Connection.new(NATS::Connection::DEFAULT_URI) + +# nc = NATS::Connection.new(name: "Sample", user: "derek", pass: "foo") + +# nc = NATS::Connection.new(4222) + +# nc = NATS::Connection.new() + +# nc = NATS::Connection.new("demo.nats.io") + +# n5 = NATS::Connection.new("nats://demo.nats.io") + +puts "Connected" + +nc.publish("foo", "Hello!") +# nc.publish("foo") + +nc.subscribe("foo") { |msg| puts "Received '#{msg}'" } +nc.subscribe("foo", "bar") do |msg| + puts "Got a queue msg #{msg}" +end + +nc.publish("foo", "Hello World!") + +nc.subscribe("req") do |msg| + msg.respond("ANSWER is 42") +end + +puts "Sending a request!" + +puts "INBOX is #{nc.new_inbox}" + +answer = nc.request("req", "Help!") +puts "Received a response '#{answer}'!" + +# puts "Send a request with no response" +# answer = nc.request("no_req", "Help!") rescue "Timeout" +# puts "Received this response #{answer}" + +# answer = nc.request("no_req", "Help!", 10.millisecond) rescue "Quick Timeout" +# puts "Received this response #{answer}" + +# nc.flush +# puts "Returned from flush" + +puts "Simple bench" +data = "HELLO".to_slice + +to_send = 1_000_000 +elapsed = Time.measure do + (0...to_send).each { nc.publish("a") } + nc.flush +end +puts "elapsed for #{to_send} msgs is #{elapsed}" +puts "msgs/sec is #{(to_send/elapsed.to_f).ceil}" + +received = 0 +ch = Channel(Nil).new + +nc2 = NATS::Connection.new(4222) + +# to_send *= 5 + +nc2.subscribe("a") do + received += 1 + ch.send(nil) if received >= to_send +end +nc2.flush + +elapsed = Time.measure do + (0...to_send).each { nc.publish("a") } + ch.receive +end + +puts "elapsed for pub/sub of #{to_send} msgs is #{elapsed}" +puts "msgs/sec is #{(to_send/elapsed.to_f).ceil}" + +sleep 20 +nc2.close +puts "Closed connection" + +sleep + +nc.close diff --git a/bin/nats-pub.cr b/bin/nats-pub.cr new file mode 100644 index 0000000..3263d87 --- /dev/null +++ b/bin/nats-pub.cr @@ -0,0 +1,44 @@ +# Copyright 2019 The NATS Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "option_parser" +require "../src/nats/connection" + +USAGE = "Usage: nats-pub [-s server] " + +def usage + STDERR.puts USAGE; exit(1) +end + +server = NATS::Connection::DEFAULT_URI +args = ARGV.dup + +OptionParser.parse(args) do |parser| + parser.banner = USAGE + parser.on("-s SERVER", "--server=SERVER", "NATS Server to connect") { |url| server = url } + parser.on("-h", "--help", "Show this help") { usage } + parser.invalid_option do |flag| + STDERR.puts "ERROR: #{flag} is not a valid option." + usage + end +end + +usage unless args.size == 2 +subject, msg = args + +nc = NATS::Connection.new(server) +at_exit { nc.close } + +nc.publish(subject, msg) +puts "Published [#{subject}] : '#{msg}'" diff --git a/bin/nats-req.cr b/bin/nats-req.cr new file mode 100644 index 0000000..ddea19d --- /dev/null +++ b/bin/nats-req.cr @@ -0,0 +1,41 @@ +# Copyright 2019 The NATS Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "option_parser" +require "../src/nats/connection" + +USAGE = "Usage: nats-req [-s server] " + +def usage + STDERR.puts USAGE; exit(1) +end + +server = NATS::Connection::DEFAULT_URI +args = ARGV.dup + +OptionParser.parse(args) do |parser| + parser.banner = USAGE + parser.on("-s SERVER", "--server=SERVER", "NATS Server to connect") { |url| server = url } + parser.on("-h", "--help", "Show this help") { usage } + parser.invalid_option do |flag| + STDERR.puts "ERROR: #{flag} is not a valid option." + usage + end +end + +usage unless args.size >= 1 +subject, msg = args[0], args[1]? + +nc = NATS::Connection.new(server) +puts nc.request(subject, msg) diff --git a/bin/nats-sub.cr b/bin/nats-sub.cr new file mode 100644 index 0000000..e032f50 --- /dev/null +++ b/bin/nats-sub.cr @@ -0,0 +1,56 @@ +# Copyright 2019 The NATS Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "option_parser" +require "../src/nats/connection" + +USAGE = "Usage: nats-sub [-s server] [-r] [-t] [queue_group]" + +def usage + STDERR.puts USAGE; exit(1) +end + +server = NATS::Connection::DEFAULT_URI +show_time, show_raw = false, false + +args = ARGV.dup +OptionParser.parse(args) do |parser| + parser.banner = USAGE + parser.on("-s SERVER", "--server=SERVER", "NATS Server to connect") { |url| server = url } + parser.on("-t", "--time") { show_time = true } + parser.on("-r", "--raw") { show_raw = true } + parser.on("-h", "--help", "Show this help") { usage } + parser.invalid_option do |flag| + STDERR.puts "ERROR: #{flag} is not a valid option." + usage + end +end + +usage unless args.size >= 1 +subject, queue_group = args[0], args[1]? +index = 0 + +nc = NATS::Connection.new(server) + +nc.subscribe(subject, queue_group) do |msg| + if show_raw + puts msg + else + time_prefix = "[#{Time.now}] " if show_time + puts "#{time_prefix}[\##{index += 1}] Received on [#{msg.subject}] : '#{msg}'" + end +end + +puts "Listening on [#{subject}]" unless show_raw +sleep diff --git a/docs/NATS.html b/docs/NATS.html new file mode 100644 index 0000000..fc2e345 --- /dev/null +++ b/docs/NATS.html @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + NATS - github.com/nats-io/nats.cr + + + + + + +
+

+ + module NATS + +

+ + + + + + + + + + + + + + + + + + +

Defined in:

+ + + + nats/nuid.cr + + +
+ + + + nats/msg.cr + + +
+ + + + nats/subscription.cr + + +
+ + + + nats/connection.cr + + +
+ + + + + +

Constant Summary

+ +
+ +
+ LANG = "crystal" +
+ + +
+ VERSION = "0.0.1" +
+ + +
+ + + + + + + + + + + +
+ +
+ + + + + + + + + +
+ + + diff --git a/docs/NATS/Connection.html b/docs/NATS/Connection.html new file mode 100644 index 0000000..e29f0d8 --- /dev/null +++ b/docs/NATS/Connection.html @@ -0,0 +1,569 @@ + + + + + + + + + + + + + + + NATS::Connection - github.com/nats-io/nats.cr + + + + + + +
+

+ + class NATS::Connection + +

+ + + + + + + + + + + + + + + + + + + + +

Defined in:

+ + + + nats/connection.cr + + +
+ + + + + + +

Constructors

+ + + + + + +

Instance Method Summary

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + +

Constructor Detail

+ +
+
+ + def self.new(host, port, user : String? = nil, pass : String? = nil, name : String? = nil, echo = true, pedantic = false) + + # +
+ +

Creates a new connection to a NATS Server.

+ +
nc = NATS::Connection.new("demo.nats.io")
+nc = NATS::Connection.new("tls://demo.nats.io")
+nc = NATS::Connection.new("nats://#{user}:#{pass}@127.0.0.1:4222")
+nc = NATS::Connection.new(4222, name: "Sample App", user: "derek", pass: "s3cr3t")
+nc = NATS::Connection.new(4222)
+nc = NATS::Connection.new
+ +
+
+ + [View source] + +
+
+ + + + + + +

Instance Method Detail

+ +
+
+ + def close + + # +
+ +

Close a connection to the NATS server.

+ +
nc = NATS::Connection.new("demo.nats.io")
+nc.close
+ +
+
+ + [View source] + +
+
+ +
+
+ + def closed? : Bool + + # +
+ +
+
+ + [View source] + +
+
+ +
+
+ + def flush(timeout = 2.second) + + # +
+ +

Flush will flush the connection to the server. Can specify a timeout.

+ +
+
+ + [View source] + +
+
+ +
+
+ + def max_payload : Int32 + + # +
+ +
+
+ + [View source] + +
+
+ +
+
+ + def new_inbox + + # +
+ +
+
+ + [View source] + +
+
+ +
+
+ + def on_close(&callback) + + # +
+ +

Setup a callback for when the connection closes.

+ +
nc = NATS::Connection.new("demo.nats.io")
+nc.on_close { puts "Connection closed!" }
+nc.close
+ +
+
+ + [View source] + +
+
+ +
+
+ + def on_error(&callback : String -> ) + + # +
+ +

Setup a callback for an async errors that are received.

+ +
nc = NATS::Connection.new("demo.nats.io")
+nc.on_error { |e| puts "Received an error #{e}" }
+ +
+
+ + [View source] + +
+
+ +
+
+ + def publish(subject : String, msg) + + # +
+ +

Publishes a messages to a given subject.

+ +
nc = NATS::Connection.new("demo.nats.io")
+nc.publish("foo", "Hello!")
+ +
+
+ + [View source] + +
+
+ +
+
+ + def publish(subject : String) + + # +
+ +

Publishes an empty message to a given subject.

+ +
nc = NATS::Connection.new("demo.nats.io")
+nc.publish("foo")
+ +
+
+ + [View source] + +
+
+ +
+
+ + def publish_with_reply(subject, reply : String, msg) + + # +
+ +

Publishes a messages to a given subject with a reply subject.

+ +
nc = NATS::Connection.new("demo.nats.io")
+nc.publish_with_reply("foo", "reply", "Hello!")
+ +
+
+ + [View source] + +
+
+ +
+
+ + def request(subject : String, msg?, timeout = 2.second) + + # +
+ +

Request will send a request to the given subject and wait up to timeout for a response.

+ +
nc = NATS::Connection.new("demo.nats.io")
+answer = nc.request("req", "Help!")
+puts "Received a response '#{answer}'!"
+ +
+
+ + [View source] + +
+
+ +
+
+ + def subscribe(subject, queue : String, &callback : Msg -> ) + + # +
+ +

Subscribe to a given subject with the queue group. Will yield to the callback provided with the message received.

+ +
nc = NATS::Connection.new("demo.nats.io")
+nc.subscribe("foo", "group1") { |msg| puts "Received '#{msg}'" }
+ +
+
+ + [View source] + +
+
+ +
+
+ + def subscribe(subject : String, queue : String?, &callback : Msg -> ) + + # +
+ +
+
+ + [View source] + +
+
+ +
+
+ + def subscribe(subject : String, &callback : Msg -> ) + + # +
+ +

Subscribe to a given subject. Will yield to the callback provided with the message received.

+ +
nc = NATS::Connection.new("demo.nats.io")
+nc.subscribe("foo") { |msg| puts "Received '#{msg}'" }
+ +
+
+ + [View source] + +
+
+ + + + + +
+ + + diff --git a/docs/NATS/Msg.html b/docs/NATS/Msg.html new file mode 100644 index 0000000..b484b67 --- /dev/null +++ b/docs/NATS/Msg.html @@ -0,0 +1,271 @@ + + + + + + + + + + + + + + + NATS::Msg - github.com/nats-io/nats.cr + + + + + + +
+

+ + class NATS::Msg + +

+ + + + + + + +

Overview

+ +

A delivered message from a subscription.

+ +
nc = NATS::Connection.new("demo.nats.io")
+nc.subscribe("foo") do |msg|
+  puts "Received '#{msg}'"
+  puts "Subject is #{msg.subject}"
+  puts "Reply Subject is #{msg.reply}"
+  puts "Raw Data is #{msg.data}"
+end
+ + + + + + + + + + + + + + +

Defined in:

+ + + + nats/msg.cr + + +
+ + + + + + + + + + +

Instance Method Summary

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +

Instance Method Detail

+ +
+
+ + def data : Bytes + + # +
+ +
+
+ + [View source] + +
+
+ +
+
+ + def reply : String? + + # +
+ +
+
+ + [View source] + +
+
+ +
+
+ + def respond(msg) + + # +
+ +

Allows a response to a request message to be easily sent.

+ +
nc = NATS::Connection.new("demo.nats.io")
+nc.subscribe("req") do |msg|
+  msg.respond("ANSWER is 42")
+end
+ +
+
+ + [View source] + +
+
+ +
+
+ + def subject : String + + # +
+ +
+
+ + [View source] + +
+
+ + + + + +
+ + + diff --git a/docs/NATS/NUID.html b/docs/NATS/NUID.html new file mode 100644 index 0000000..c3f31f3 --- /dev/null +++ b/docs/NATS/NUID.html @@ -0,0 +1,289 @@ + + + + + + + + + + + + + + + NATS::NUID - github.com/nats-io/nats.cr + + + + + + +
+

+ + class NATS::NUID + +

+ + + + + + + + + + + + + + + + + + + + +

Defined in:

+ + + + nats/nuid.cr + + +
+ + + + + +

Constant Summary

+ +
+ +
+ BASE = 62 +
+ + +
+ DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
+ + +
+ INC = MAX_INC - MIN_INC +
+ + +
+ MAX_INC = 333_i64 +
+ + +
+ MAX_SEQ = 839299365868340224_i64 +
+ + +
+ MIN_INC = 33_i64 +
+ + +
+ PREFIX_LENGTH = 12 +
+ + +
+ SEQ_LENGTH = 10 +
+ + +
+ TOTAL_LENGTH = PREFIX_LENGTH + SEQ_LENGTH +
+ + +
+ + + +

Constructors

+
    + +
  • + .new + +
  • + +
+ + + + + +

Instance Method Summary

+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + +

Constructor Detail

+ +
+
+ + def self.new + + # +
+ +
+
+ + [View source] + +
+
+ + + + + + +

Instance Method Detail

+ +
+
+ + def next + + # +
+ +
+
+ + [View source] + +
+
+ +
+
+ + def randomize_prefix! + + # +
+ +
+
+ + [View source] + +
+
+ + + + + +
+ + + diff --git a/docs/NATS/Subscription.html b/docs/NATS/Subscription.html new file mode 100644 index 0000000..b7fc1f8 --- /dev/null +++ b/docs/NATS/Subscription.html @@ -0,0 +1,250 @@ + + + + + + + + + + + + + + + NATS::Subscription - github.com/nats-io/nats.cr + + + + + + +
+

+ + class NATS::Subscription + +

+ + + + + + + +

Overview

+ +

A subscription to a given subject and possible queue group. Used to unsubscribe/close.

+ +
nc = NATS::Connection.new("demo.nats.io")
+sub = nc.subscribe("foo") { |msg| }
+sub.close
+sub.unsubscribe
+ + + + + + + + + + + + + + +

Defined in:

+ + + + nats/subscription.cr + + +
+ + + + + + + + + + +

Instance Method Summary

+
    + +
  • + #close + +

    Will unsubscribe from a subscription.

    + +
  • + +
  • + #closed? + +

    Determines if the subscription is already closed.

    + +
  • + +
  • + #unsubscribe + +

    Will unsubscribe from a subscription.

    + +
  • + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + +

Instance Method Detail

+ +
+
+ + def close + + # +
+ +

Will unsubscribe from a subscription.

+ +
+
+ + [View source] + +
+
+ +
+
+ + def closed? + + # +
+ +

Determines if the subscription is already closed.

+ +
+
+ + [View source] + +
+
+ +
+
+ + def unsubscribe + + # +
+ +

Will unsubscribe from a subscription.

+ +
+
+ + [View source] + +
+
+ + + + + +
+ + + diff --git a/docs/css/style.css b/docs/css/style.css new file mode 100644 index 0000000..7295c3f --- /dev/null +++ b/docs/css/style.css @@ -0,0 +1,629 @@ +html, body { + background: #FFFFFF; + position: relative; + margin: 0; + padding: 0; + width: 100%; + height: 100%; + overflow: hidden; +} + +body { + font-family: "Avenir", "Tahoma", "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; + color: #333; + line-height: 1.5; +} + +a { + color: #263F6C; +} + +a:visited { + color: #112750; +} + +h1, h2, h3, h4, h5, h6 { + margin: 35px 0 25px; + color: #444444; +} + +h1.type-name { + color: #47266E; + margin: 20px 0 30px; + background-color: #F8F8F8; + padding: 10px 12px; + border: 1px solid #EBEBEB; + border-radius: 2px; +} + +h2 { + border-bottom: 1px solid #E6E6E6; + padding-bottom: 5px; +} + +body { + display: flex; +} + +.sidebar, .main-content { + overflow: auto; +} + +.sidebar { + width: 30em; + color: #F8F4FD; + background-color: #2E1052; + padding: 0 0 30px; + box-shadow: inset -3px 0 4px rgba(0,0,0,.35); + line-height: 1.2; +} + +.sidebar .search-box { + padding: 8px 9px; +} + +.sidebar input { + display: block; + box-sizing: border-box; + margin: 0; + padding: 5px; + font: inherit; + font-family: inherit; + line-height: 1.2; + width: 100%; + border: 0; + outline: 0; + border-radius: 2px; + box-shadow: 0px 3px 5px rgba(0,0,0,.25); + transition: box-shadow .12s; +} + +.sidebar input:focus { + box-shadow: 0px 5px 6px rgba(0,0,0,.5); +} + +.sidebar input::-webkit-input-placeholder { /* Chrome/Opera/Safari */ + color: #C8C8C8; + font-size: 14px; + text-indent: 2px; +} + +.sidebar input::-moz-placeholder { /* Firefox 19+ */ + color: #C8C8C8; + font-size: 14px; + text-indent: 2px; +} + +.sidebar input:-ms-input-placeholder { /* IE 10+ */ + color: #C8C8C8; + font-size: 14px; + text-indent: 2px; +} + +.sidebar input:-moz-placeholder { /* Firefox 18- */ + color: #C8C8C8; + font-size: 14px; + text-indent: 2px; +} + +.sidebar ul { + margin: 0; + padding: 0; + list-style: none outside; +} + +.sidebar li { + display: block; + position: relative; +} + +.types-list li.hide { + display: none; +} + +.sidebar a { + text-decoration: none; + color: inherit; + transition: color .14s; +} +.types-list a { + display: block; + padding: 5px 15px 5px 30px; +} + +.types-list { + display: block; +} + +.sidebar a:focus { + outline: 1px solid #D1B7F1; +} + +.types-list a { + padding: 5px 15px 5px 30px; +} + +.sidebar .current > a, +.sidebar a:hover { + color: #866BA6; +} + +.repository-links { + padding: 5px 15px 5px 30px; +} + +.types-list li ul { + overflow: hidden; + height: 0; + max-height: 0; + transition: 1s ease-in-out; +} + +.types-list li.parent { + padding-left: 30px; +} + +.types-list li.parent::before { + box-sizing: border-box; + content: "▼"; + display: block; + width: 30px; + height: 30px; + position: absolute; + top: 0; + left: 0; + text-align: center; + color: white; + font-size: 8px; + line-height: 30px; + transform: rotateZ(-90deg); + cursor: pointer; + transition: .2s linear; +} + + +.types-list li.parent > a { + padding-left: 0; +} + +.types-list li.parent.open::before { + transform: rotateZ(0); +} + +.types-list li.open > ul { + height: auto; + max-height: 1000em; +} + +.main-content { + padding: 0 30px 30px 30px; + width: 100%; +} + +.kind { + font-size: 60%; + color: #866BA6; +} + +.superclass-hierarchy { + margin: -15px 0 30px 0; + padding: 0; + list-style: none outside; + font-size: 80%; +} + +.superclass-hierarchy .superclass { + display: inline-block; + margin: 0 7px 0 0; + padding: 0; +} + +.superclass-hierarchy .superclass + .superclass::before { + content: "<"; + margin-right: 7px; +} + +.other-types-list li { + display: inline-block; +} + +.other-types-list, +.list-summary { + margin: 0 0 30px 0; + padding: 0; + list-style: none outside; +} + +.entry-const { + font-family: Menlo, Monaco, Consolas, 'Courier New', Courier, monospace; +} + +.entry-const code { + white-space: pre-wrap; +} + +.entry-summary { + padding-bottom: 4px; +} + +.superclass-hierarchy .superclass a, +.other-type a, +.entry-summary .signature { + padding: 4px 8px; + margin-bottom: 4px; + display: inline-block; + background-color: #f8f8f8; + color: #47266E; + border: 1px solid #f0f0f0; + text-decoration: none; + border-radius: 3px; + font-family: Menlo, Monaco, Consolas, 'Courier New', Courier, monospace; + transition: background .15s, border-color .15s; +} + +.superclass-hierarchy .superclass a:hover, +.other-type a:hover, +.entry-summary .signature:hover { + background: #D5CAE3; + border-color: #624288; +} + +.entry-summary .summary { + padding-left: 32px; +} + +.entry-summary .summary p { + margin: 12px 0 16px; +} + +.entry-summary a { + text-decoration: none; +} + +.entry-detail { + padding: 30px 0; +} + +.entry-detail .signature { + position: relative; + padding: 5px 15px; + margin-bottom: 10px; + display: block; + border-radius: 5px; + background-color: #f8f8f8; + color: #47266E; + border: 1px solid #f0f0f0; + font-family: Menlo, Monaco, Consolas, 'Courier New', Courier, monospace; + transition: .2s ease-in-out; +} + +.entry-detail:target .signature { + background-color: #D5CAE3; + border: 1px solid #624288; +} + +.entry-detail .signature .method-permalink { + position: absolute; + top: 0; + left: -35px; + padding: 5px 15px; + text-decoration: none; + font-weight: bold; + color: #624288; + opacity: .4; + transition: opacity .2s; +} + +.entry-detail .signature .method-permalink:hover { + opacity: 1; +} + +.entry-detail:target .signature .method-permalink { + opacity: 1; +} + +.methods-inherited { + padding-right: 10%; + line-height: 1.5em; +} + +.methods-inherited h3 { + margin-bottom: 4px; +} + +.methods-inherited a { + display: inline-block; + text-decoration: none; + color: #47266E; +} + +.methods-inherited a:hover { + text-decoration: underline; + color: #6C518B; +} + +.methods-inherited .tooltip>span { + background: #D5CAE3; + padding: 4px 8px; + border-radius: 3px; + margin: -4px -8px; +} + +.methods-inherited .tooltip * { + color: #47266E; +} + +pre { + padding: 10px 20px; + margin-top: 4px; + border-radius: 3px; + line-height: 1.45; + overflow: auto; + color: #333; + background: #fdfdfd; + font-size: 14px; + border: 1px solid #eee; +} + +code { + font-family: Menlo, Monaco, Consolas, 'Courier New', Courier, monospace; +} + +:not(pre) > code { + background-color: rgba(40,35,30,0.05); + padding: 0.2em 0.4em; + font-size: 85%; + border-radius: 3px; +} + +span.flag { + padding: 2px 4px 1px; + border-radius: 3px; + margin-right: 3px; + font-size: 11px; + border: 1px solid transparent; +} + +span.flag.orange { + background-color: #EE8737; + color: #FCEBDD; + border-color: #EB7317; +} + +span.flag.yellow { + background-color: #E4B91C; + color: #FCF8E8; + border-color: #B69115; +} + +span.flag.green { + background-color: #469C14; + color: #E2F9D3; + border-color: #34700E; +} + +span.flag.red { + background-color: #BF1919; + color: #F9ECEC; + border-color: #822C2C; +} + +span.flag.purple { + background-color: #2E1052; + color: #ECE1F9; + border-color: #1F0B37; +} + +.tooltip>span { + position: absolute; + opacity: 0; + display: none; + pointer-events: none; +} + +.tooltip:hover>span { + display: inline-block; + opacity: 1; +} + +.c { + color: #969896; +} + +.n { + color: #0086b3; +} + +.t { + color: #0086b3; +} + +.s { + color: #183691; +} + +.i { + color: #7f5030; +} + +.k { + color: #a71d5d; +} + +.o { + color: #a71d5d; +} + +.m { + color: #795da3; +} + +.hidden { + display: none; +} +.search-results { + font-size: 90%; + line-height: 1.3; +} + +.search-results mark { + color: inherit; + background: transparent; + font-weight: bold; +} +.search-result { + padding: 5px 8px 5px 5px; + cursor: pointer; + border-left: 5px solid transparent; + transform: translateX(-3px); + transition: all .2s, background-color 0s, border .02s; + min-height: 3.2em; +} +.search-result.current { + border-left-color: #ddd; + background-color: rgba(200,200,200,0.4); + transform: translateX(0); + transition: all .2s, background-color .5s, border 0s; +} +.search-result.current:hover, +.search-result.current:focus { + border-left-color: #866BA6; +} +.search-result:not(.current):nth-child(2n) { + background-color: rgba(255,255,255,.06); +} +.search-result__title { + font-size: 105%; + word-break: break-all; + line-height: 1.1; + padding: 3px 0; +} +.search-result__title strong { + font-weight: normal; +} +.search-results .search-result__title > a { + padding: 0; + display: block; +} +.search-result__title > a > .args { + color: #dddddd; + font-weight: 300; + transition: inherit; + font-size: 88%; + line-height: 1.2; + letter-spacing: -.02em; +} +.search-result__title > a > .args * { + color: inherit; +} + +.search-result a, +.search-result a:hover { + color: inherit; +} +.search-result:not(.current):hover .search-result__title > a, +.search-result:not(.current):focus .search-result__title > a, +.search-result__title > a:focus { + color: #866BA6; +} +.search-result:not(.current):hover .args, +.search-result:not(.current):focus .args { + color: #6a5a7d; +} + +.search-result__type { + color: #e8e8e8; + font-weight: 300; +} +.search-result__doc { + color: #bbbbbb; + font-size: 90%; +} +.search-result__doc p { + margin: 0; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + line-height: 1.2em; + max-height: 2.4em; +} + +.js-modal-visible .modal-background { + display: flex; +} +.main-content { + position: relative; +} +.modal-background { + position: absolute; + display: none; + height: 100%; + width: 100%; + background: rgba(120,120,120,.4); + z-index: 100; + align-items: center; + justify-content: center; +} +.usage-modal { + max-width: 90%; + background: #fff; + border: 2px solid #ccc; + border-radius: 9px; + padding: 5px 15px 20px; + min-width: 50%; + color: #555; + position: relative; + transform: scale(.5); + transition: transform 200ms; +} +.js-modal-visible .usage-modal { + transform: scale(1); +} +.usage-modal > .close-button { + position: absolute; + right: 15px; + top: 8px; + color: #aaa; + font-size: 27px; + cursor: pointer; +} +.usage-modal > .close-button:hover { + text-shadow: 2px 2px 2px #ccc; + color: #999; +} +.modal-title { + margin: 0; + text-align: center; + font-weight: normal; + color: #666; + border-bottom: 2px solid #ddd; + padding: 10px; +} +.usage-list { + padding: 0; + margin: 13px; +} +.usage-list > li { + padding: 5px 2px; + overflow: auto; + padding-left: 100px; + min-width: 12em; +} +.usage-modal kbd { + background: #eee; + border: 1px solid #ccc; + border-bottom-width: 2px; + border-radius: 3px; + padding: 3px 8px; + font-family: monospace; + margin-right: 2px; + display: inline-block; +} +.usage-key { + float: left; + clear: left; + margin-left: -100px; + margin-right: 12px; +} diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..c59960d --- /dev/null +++ b/docs/index.html @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + README - github.com/nats-io/nats.cr + + + + + + +
+

NATS

+ +

Simple NATS client for the Crystal programming language.

+ +

Installation

+ +
  1. Add the dependency to your shard.yml:
+ +

`yaml + dependencies:

+ +
 NATS:
+   github: nats-io/nats.cr
+ +

`

+ +
  1. Run shards install
+ +

Usage

+ +
require "NATS"
+
+nc = NATS::Connection.new("demo.nats.io")
+nc.subscribe("foo") { |msg| puts "Received '#{msg}'"}
+nc.publish("foo", "Hello!")
+
+sub = nc.subscribe("req") do |msg|
+  msg.respond("ANSWER is 42")
+end
+
+answer = nc.request("req", "Help!")
+puts "Received a response '#{answer}'!"
+
+sub.close
+
+nc.flush
+nc.close
+ +

License

+ +

Unless otherwise noted, the NATS source files are distributed under +the Apache Version 2.0 license found in the LICENSE file.

+
+ + diff --git a/docs/index.json b/docs/index.json new file mode 100644 index 0000000..9bbad7b --- /dev/null +++ b/docs/index.json @@ -0,0 +1 @@ +{"repository_name":"github.com/nats-io/nats.cr","body":"# NATS\n\nSimple NATS client for the [Crystal](https://http://crystal-lang.org) programming language.\n\n## Installation\n\n1. Add the dependency to your `shard.yml`:\n\n ```yaml\n dependencies:\n NATS:\n github: nats-io/nats.cr\n ```\n\n2. Run `shards install`\n\n## Usage\n\n```crystal\nrequire \"NATS\"\n\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.subscribe(\"foo\") { |msg| puts \"Received '#{msg}'\"}\nnc.publish(\"foo\", \"Hello!\")\n\nsub = nc.subscribe(\"req\") do |msg|\n msg.respond(\"ANSWER is 42\")\nend\n\nanswer = nc.request(\"req\", \"Help!\")\nputs \"Received a response '#{answer}'!\"\n\nsub.close\n\nnc.flush\nnc.close\n```\n\n## License\n\nUnless otherwise noted, the NATS source files are distributed under\nthe Apache Version 2.0 license found in the LICENSE file.\n","program":{"html_id":"github.com/nats-io/nats.cr/toplevel","path":"toplevel.html","kind":"module","full_name":"Top Level Namespace","name":"Top Level Namespace","abstract":false,"superclass":null,"ancestors":[],"locations":[],"repository_name":"github.com/nats-io/nats.cr","program":true,"enum":false,"alias":false,"aliased":"","const":false,"constants":[],"included_modules":[],"extended_modules":[],"subclasses":[],"including_types":[],"namespace":null,"doc":null,"summary":null,"class_methods":[],"constructors":[],"instance_methods":[],"macros":[],"types":[{"html_id":"github.com/nats-io/nats.cr/NATS","path":"NATS.html","kind":"module","full_name":"NATS","name":"NATS","abstract":false,"superclass":null,"ancestors":[],"locations":[{"filename":"nats/nuid.cr","line_number":20,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/nuid.cr"},{"filename":"nats/msg.cr","line_number":15,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/msg.cr"},{"filename":"nats/subscription.cr","line_number":15,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/subscription.cr"},{"filename":"nats/connection.cr","line_number":24,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr"}],"repository_name":"github.com/nats-io/nats.cr","program":false,"enum":false,"alias":false,"aliased":"","const":false,"constants":[{"id":"LANG","name":"LANG","value":"\"crystal\"","doc":null,"summary":null},{"id":"VERSION","name":"VERSION","value":"\"0.0.1\"","doc":null,"summary":null}],"included_modules":[],"extended_modules":[],"subclasses":[],"including_types":[],"namespace":null,"doc":null,"summary":null,"class_methods":[],"constructors":[],"instance_methods":[],"macros":[],"types":[{"html_id":"github.com/nats-io/nats.cr/NATS/Connection","path":"NATS/Connection.html","kind":"class","full_name":"NATS::Connection","name":"Connection","abstract":false,"superclass":{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},"ancestors":[{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},{"html_id":"github.com/nats-io/nats.cr/Object","kind":"class","full_name":"Object","name":"Object"}],"locations":[{"filename":"nats/connection.cr","line_number":28,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr"}],"repository_name":"github.com/nats-io/nats.cr","program":false,"enum":false,"alias":false,"aliased":"","const":false,"constants":[],"included_modules":[],"extended_modules":[],"subclasses":[],"including_types":[],"namespace":{"html_id":"github.com/nats-io/nats.cr/NATS","kind":"module","full_name":"NATS","name":"NATS"},"doc":null,"summary":null,"class_methods":[],"constructors":[{"id":"new(host,port,user:String?=nil,pass:String?=nil,name:String?=nil,echo=true,pedantic=false)-class-method","html_id":"new(host,port,user:String?=nil,pass:String?=nil,name:String?=nil,echo=true,pedantic=false)-class-method","name":"new","doc":"Creates a new connection to a NATS Server.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc = NATS::Connection.new(\"tls://demo.nats.io\")\nnc = NATS::Connection.new(\"nats://#{user}:#{pass}@127.0.0.1:4222\")\nnc = NATS::Connection.new(4222, name: \"Sample App\", user: \"derek\", pass: \"s3cr3t\")\nnc = NATS::Connection.new(4222)\nnc = NATS::Connection.new\n```","summary":"

Creates a new connection to a NATS Server.

","abstract":false,"args":[{"name":"host","doc":null,"default_value":"","external_name":"host","restriction":""},{"name":"port","doc":null,"default_value":"","external_name":"port","restriction":""},{"name":"user","doc":null,"default_value":"nil","external_name":"user","restriction":"String | ::Nil"},{"name":"pass","doc":null,"default_value":"nil","external_name":"pass","restriction":"String | ::Nil"},{"name":"name","doc":null,"default_value":"nil","external_name":"name","restriction":"String | ::Nil"},{"name":"echo","doc":null,"default_value":"true","external_name":"echo","restriction":""},{"name":"pedantic","doc":null,"default_value":"false","external_name":"pedantic","restriction":""}],"args_string":"(host, port, user : String? = nil, pass : String? = nil, name : String? = nil, echo = true, pedantic = false)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L55","def":{"name":"new","args":[{"name":"host","doc":null,"default_value":"","external_name":"host","restriction":""},{"name":"port","doc":null,"default_value":"","external_name":"port","restriction":""},{"name":"user","doc":null,"default_value":"nil","external_name":"user","restriction":"String | ::Nil"},{"name":"pass","doc":null,"default_value":"nil","external_name":"pass","restriction":"String | ::Nil"},{"name":"name","doc":null,"default_value":"nil","external_name":"name","restriction":"String | ::Nil"},{"name":"echo","doc":null,"default_value":"true","external_name":"echo","restriction":""},{"name":"pedantic","doc":null,"default_value":"false","external_name":"pedantic","restriction":""}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"_ = allocate\n_.initialize(host, port, user, pass, name, echo, pedantic)\nif _.responds_to?(:finalize)\n ::GC.add_finalizer(_)\nend\n_\n"}}],"instance_methods":[{"id":"close-instance-method","html_id":"close-instance-method","name":"close","doc":"Close a connection to the NATS server.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.close\n```","summary":"

Close a connection to the NATS server.

","abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L347","def":{"name":"close","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if @closed\n return\nend\n@closed = true\n@out.synchronize do\n flush_outbound\n @socket.flush\nend\n@socket.close\n@subs.each do |sid, sub|\n sub.unsubscribe\nend\n@close_cb.try do |cb|\n spawn(cb.call)\nend\n"}},{"id":"closed?:Bool-instance-method","html_id":"closed?:Bool-instance-method","name":"closed?","doc":null,"summary":null,"abstract":false,"args":[],"args_string":" : Bool","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L42","def":{"name":"closed?","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"@closed"}},{"id":"flush(timeout=2.second)-instance-method","html_id":"flush(timeout=2.second)-instance-method","name":"flush","doc":"Flush will flush the connection to the server. Can specify a *timeout*.","summary":"

Flush will flush the connection to the server.

","abstract":false,"args":[{"name":"timeout","doc":null,"default_value":"2.second","external_name":"timeout","restriction":""}],"args_string":"(timeout = 2.second)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L229","def":{"name":"flush","args":[{"name":"timeout","doc":null,"default_value":"2.second","external_name":"timeout","restriction":""}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"ch = Channel(Nil).new\n@pongs.push(ch)\n@out.synchronize do\n @buf.write(PING_SLICE)\nend\nflush_outbound\nspawn do\n sleep(timeout)\n ch.close\nend\nbegin\n ch.receive\nrescue\n {raise(\"Flush Timeout\")}\nend\n"}},{"id":"max_payload:Int32-instance-method","html_id":"max_payload:Int32-instance-method","name":"max_payload","doc":null,"summary":null,"abstract":false,"args":[],"args_string":" : Int32","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L43","def":{"name":"max_payload","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"@max_payload"}},{"id":"new_inbox-instance-method","html_id":"new_inbox-instance-method","name":"new_inbox","doc":null,"summary":null,"abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L238","def":{"name":"new_inbox","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"\"#{@resp_sub_prefix}.#{inbox_token}\""}},{"id":"on_close(&callback)-instance-method","html_id":"on_close(&callback)-instance-method","name":"on_close","doc":"Setup a callback for when the connection closes.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.on_close { puts \"Connection closed!\" }\nnc.close\n```","summary":"

Setup a callback for when the connection closes.

","abstract":false,"args":[],"args_string":"(&callback)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L369","def":{"name":"on_close","args":[],"double_splat":null,"splat_index":null,"yields":0,"block_arg":{"name":"callback","doc":null,"default_value":"","external_name":"callback","restriction":""},"return_type":"","visibility":"Public","body":"@close_cb = callback"}},{"id":"on_error(&callback:String->)-instance-method","html_id":"on_error(&callback:String->)-instance-method","name":"on_error","doc":"Setup a callback for an async errors that are received.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.on_error { |e| puts \"Received an error #{e}\" }\n```","summary":"

Setup a callback for an async errors that are received.

","abstract":false,"args":[],"args_string":"(&callback : String -> )","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L379","def":{"name":"on_error","args":[],"double_splat":null,"splat_index":null,"yields":1,"block_arg":{"name":"callback","doc":null,"default_value":"","external_name":"callback","restriction":"(String -> )"},"return_type":"","visibility":"Public","body":"@err_cb = callback"}},{"id":"publish(subject:String,msg)-instance-method","html_id":"publish(subject:String,msg)-instance-method","name":"publish","doc":"Publishes a messages to a given subject.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.publish(\"foo\", \"Hello!\")\n```","summary":"

Publishes a messages to a given subject.

","abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"},{"name":"msg","doc":null,"default_value":"","external_name":"msg","restriction":""}],"args_string":"(subject : String, msg)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L164","def":{"name":"publish","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"},{"name":"msg","doc":null,"default_value":"","external_name":"msg","restriction":""}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if subject.empty?\n raise(\"Bad Subject\")\nend\nif closed?\n raise(\"Connection Closed\")\nend\ndata = msg.to_slice\ncheck_size(data)\n@out.synchronize do\n @buf.write(PUB_SLICE)\n @buf.write(\"#{subject} #{data.size}\".to_slice)\n @buf.write(CR_LF_SLICE)\n @buf.write(data)\n @buf.write(CR_LF_SLICE)\nend\nif @flush.empty?\n @flush.send(true)\nend\n"}},{"id":"publish(subject:String)-instance-method","html_id":"publish(subject:String)-instance-method","name":"publish","doc":"Publishes an empty message to a given subject.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.publish(\"foo\")\n```","summary":"

Publishes an empty message to a given subject.

","abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"}],"args_string":"(subject : String)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L188","def":{"name":"publish","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if subject.empty?\n raise(\"Bad Subject\")\nend\nif closed?\n raise(\"Connection Closed\")\nend\n@out.synchronize do\n @buf.write(\"PUB #{subject} 0\\r\\n\\r\\n\".to_slice)\nend\nif @flush.empty?\n @flush.send(true)\nend\n"}},{"id":"publish_with_reply(subject,reply:String,msg)-instance-method","html_id":"publish_with_reply(subject,reply:String,msg)-instance-method","name":"publish_with_reply","doc":"Publishes a messages to a given subject with a reply subject.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.publish_with_reply(\"foo\", \"reply\", \"Hello!\")\n```","summary":"

Publishes a messages to a given subject with a reply subject.

","abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":""},{"name":"reply","doc":null,"default_value":"","external_name":"reply","restriction":"String"},{"name":"msg","doc":null,"default_value":"","external_name":"msg","restriction":""}],"args_string":"(subject, reply : String, msg)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L220","def":{"name":"publish_with_reply","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":""},{"name":"reply","doc":null,"default_value":"","external_name":"reply","restriction":"String"},{"name":"msg","doc":null,"default_value":"","external_name":"msg","restriction":""}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if subject.empty?\n raise(\"Bad Subject\")\nend\nif closed?\n raise(\"Connection Closed\")\nend\n@out.synchronize do\n @buf.write(\"PUB #{subject} #{reply} 0\\r\\n\\r\\n\".to_slice)\nend\nif @flush.empty?\n @flush.send(true)\nend\n"}},{"id":"request(subject:String,msg?,timeout=2.second)-instance-method","html_id":"request(subject:String,msg?,timeout=2.second)-instance-method","name":"request","doc":"Request will send a request to the given subject and wait up to *timeout* for a response.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nanswer = nc.request(\"req\", \"Help!\")\nputs \"Received a response '#{answer}'!\"\n```","summary":"

Request will send a request to the given subject and wait up to timeout for a response.

","abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"},{"name":"msg?","doc":null,"default_value":"","external_name":"msg?","restriction":""},{"name":"timeout","doc":null,"default_value":"2.second","external_name":"timeout","restriction":""}],"args_string":"(subject : String, msg?, timeout = 2.second)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L275","def":{"name":"request","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"},{"name":"msg?","doc":null,"default_value":"","external_name":"msg?","restriction":""},{"name":"timeout","doc":null,"default_value":"2.second","external_name":"timeout","restriction":""}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if @resp_sub_created\nelse\n create_resp_subscription\nend\ntoken = inbox_token\nreply = \"#{@resp_sub_prefix}.#{token}\"\nch = Channel(Msg | ::Nil).new\n@resp_map[token] = ch\npublish_with_reply(subject, reply, msg?)\nspawn do\n sleep(timeout)\n ch.close\nend\nbegin\n msg = ch.receive\nrescue\n @resp_map.delete(token)\n raise(\"Request Timeout\")\nend\n"}},{"id":"subscribe(subject,queue:String,&callback:Msg->)-instance-method","html_id":"subscribe(subject,queue:String,&callback:Msg->)-instance-method","name":"subscribe","doc":"Subscribe to a given subject with the queue group. Will yield to the callback provided with the message received.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.subscribe(\"foo\", \"group1\") { |msg| puts \"Received '#{msg}'\" }\n```","summary":"

Subscribe to a given subject with the queue group.

","abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":""},{"name":"queue","doc":null,"default_value":"","external_name":"queue","restriction":"String"}],"args_string":"(subject, queue : String, &callback : Msg -> )","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L321","def":{"name":"subscribe","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":""},{"name":"queue","doc":null,"default_value":"","external_name":"queue","restriction":"String"}],"double_splat":null,"splat_index":null,"yields":1,"block_arg":{"name":"callback","doc":null,"default_value":"","external_name":"callback","restriction":"(Msg -> )"},"return_type":"","visibility":"Public","body":"sid = @gsid = @gsid + 1\n@out.synchronize do\n ((((@buf << \"SUB \") << subject) << \" \") << queue) << \" #{sid}\\r\\n\"\nend\nif @flush.empty?\n @flush.send(true)\nend\n(Subscription.new(sid, self, callback)).tap do |sub|\n @subs[sid] = sub\nend\n"}},{"id":"subscribe(subject:String,queue:String?,&callback:Msg->)-instance-method","html_id":"subscribe(subject:String,queue:String?,&callback:Msg->)-instance-method","name":"subscribe","doc":null,"summary":null,"abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"},{"name":"queue","doc":null,"default_value":"","external_name":"queue","restriction":"String | Nil"}],"args_string":"(subject : String, queue : String?, &callback : Msg -> )","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L330","def":{"name":"subscribe","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"},{"name":"queue","doc":null,"default_value":"","external_name":"queue","restriction":"String | Nil"}],"double_splat":null,"splat_index":null,"yields":1,"block_arg":{"name":"callback","doc":null,"default_value":"","external_name":"callback","restriction":"(Msg -> )"},"return_type":"","visibility":"Public","body":"if queue.is_a?(String)\n return subscribe(subject, queue, &callback)\nend\nsubscribe(subject, &callback)\n"}},{"id":"subscribe(subject:String,&callback:Msg->)-instance-method","html_id":"subscribe(subject:String,&callback:Msg->)-instance-method","name":"subscribe","doc":"Subscribe to a given subject. Will yield to the callback provided with the message received.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.subscribe(\"foo\") { |msg| puts \"Received '#{msg}'\" }\n```","summary":"

Subscribe to a given subject.

","abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"}],"args_string":"(subject : String, &callback : Msg -> )","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L306","def":{"name":"subscribe","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"}],"double_splat":null,"splat_index":null,"yields":1,"block_arg":{"name":"callback","doc":null,"default_value":"","external_name":"callback","restriction":"(Msg -> )"},"return_type":"","visibility":"Public","body":"sid = @gsid = @gsid + 1\n@out.synchronize do\n ((@buf << \"SUB \") << subject) << \" #{sid}\\r\\n\"\nend\nif @flush.empty?\n @flush.send(true)\nend\n(Subscription.new(sid, self, callback)).tap do |sub|\n @subs[sid] = sub\nend\n"}}],"macros":[],"types":[]},{"html_id":"github.com/nats-io/nats.cr/NATS/Msg","path":"NATS/Msg.html","kind":"class","full_name":"NATS::Msg","name":"Msg","abstract":false,"superclass":{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},"ancestors":[{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},{"html_id":"github.com/nats-io/nats.cr/Object","kind":"class","full_name":"Object","name":"Object"}],"locations":[{"filename":"nats/msg.cr","line_number":27,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/msg.cr"}],"repository_name":"github.com/nats-io/nats.cr","program":false,"enum":false,"alias":false,"aliased":"","const":false,"constants":[],"included_modules":[],"extended_modules":[],"subclasses":[],"including_types":[],"namespace":{"html_id":"github.com/nats-io/nats.cr/NATS","kind":"module","full_name":"NATS","name":"NATS"},"doc":"A delivered message from a subscription.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.subscribe(\"foo\") do |msg|\n puts \"Received '#{msg}'\"\n puts \"Subject is #{msg.subject}\"\n puts \"Reply Subject is #{msg.reply}\"\n puts \"Raw Data is #{msg.data}\"\nend\n```","summary":"

A delivered message from a subscription.

","class_methods":[],"constructors":[],"instance_methods":[{"id":"data:Bytes-instance-method","html_id":"data:Bytes-instance-method","name":"data","doc":null,"summary":null,"abstract":false,"args":[],"args_string":" : Bytes","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/msg.cr#L32","def":{"name":"data","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"Bytes","visibility":"Public","body":"@data"}},{"id":"reply:String?-instance-method","html_id":"reply:String?-instance-method","name":"reply","doc":null,"summary":null,"abstract":false,"args":[],"args_string":" : String?","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/msg.cr#L31","def":{"name":"reply","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"String | ::Nil","visibility":"Public","body":"@reply"}},{"id":"respond(msg)-instance-method","html_id":"respond(msg)-instance-method","name":"respond","doc":"Allows a response to a request message to be easily sent.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.subscribe(\"req\") do |msg|\n msg.respond(\"ANSWER is 42\")\nend\n```","summary":"

Allows a response to a request message to be easily sent.

","abstract":false,"args":[{"name":"msg","doc":null,"default_value":"","external_name":"msg","restriction":""}],"args_string":"(msg)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/msg.cr#L54","def":{"name":"respond","args":[{"name":"msg","doc":null,"default_value":"","external_name":"msg","restriction":""}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if @reply.nil?\n raise(\"No reply subject\")\nend\nif @conn.nil?\n raise(\"Not a received message\")\nend\n@conn.try(&.publish(@reply.to_s, msg))\n"}},{"id":"subject:String-instance-method","html_id":"subject:String-instance-method","name":"subject","doc":null,"summary":null,"abstract":false,"args":[],"args_string":" : String","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/msg.cr#L30","def":{"name":"subject","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"String","visibility":"Public","body":"@subject"}}],"macros":[],"types":[]},{"html_id":"github.com/nats-io/nats.cr/NATS/NUID","path":"NATS/NUID.html","kind":"class","full_name":"NATS::NUID","name":"NUID","abstract":false,"superclass":{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},"ancestors":[{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},{"html_id":"github.com/nats-io/nats.cr/Object","kind":"class","full_name":"Object","name":"Object"}],"locations":[{"filename":"nats/nuid.cr","line_number":21,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/nuid.cr"}],"repository_name":"github.com/nats-io/nats.cr","program":false,"enum":false,"alias":false,"aliased":"","const":false,"constants":[{"id":"BASE","name":"BASE","value":"62","doc":null,"summary":null},{"id":"DIGITS","name":"DIGITS","value":"\"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"","doc":null,"summary":null},{"id":"INC","name":"INC","value":"MAX_INC - MIN_INC","doc":null,"summary":null},{"id":"MAX_INC","name":"MAX_INC","value":"333_i64","doc":null,"summary":null},{"id":"MAX_SEQ","name":"MAX_SEQ","value":"839299365868340224_i64","doc":null,"summary":null},{"id":"MIN_INC","name":"MIN_INC","value":"33_i64","doc":null,"summary":null},{"id":"PREFIX_LENGTH","name":"PREFIX_LENGTH","value":"12","doc":null,"summary":null},{"id":"SEQ_LENGTH","name":"SEQ_LENGTH","value":"10","doc":null,"summary":null},{"id":"TOTAL_LENGTH","name":"TOTAL_LENGTH","value":"PREFIX_LENGTH + SEQ_LENGTH","doc":null,"summary":null}],"included_modules":[],"extended_modules":[],"subclasses":[],"including_types":[],"namespace":{"html_id":"github.com/nats-io/nats.cr/NATS","kind":"module","full_name":"NATS","name":"NATS"},"doc":null,"summary":null,"class_methods":[],"constructors":[{"id":"new-class-method","html_id":"new-class-method","name":"new","doc":null,"summary":null,"abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/nuid.cr#L32","def":{"name":"new","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"_ = allocate\n_.initialize\nif _.responds_to?(:finalize)\n ::GC.add_finalizer(_)\nend\n_\n"}}],"instance_methods":[{"id":"next-instance-method","html_id":"next-instance-method","name":"next","doc":null,"summary":null,"abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/nuid.cr#L38","def":{"name":"next","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"@seq = @seq + @inc\nif @seq >= MAX_SEQ\n reset!\nend\nl = @seq\ns_10 = DIGITS[l % BASE]\ns_09, s_08, s_07, s_06, s_05, s_04, s_03, s_02, s_01 = (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE])\n\"#{@prefix}#{s_01}#{s_02}#{s_03}#{s_04}#{s_05}#{s_06}#{s_07}#{s_08}#{s_09}#{s_10}\"\n"}},{"id":"randomize_prefix!-instance-method","html_id":"randomize_prefix!-instance-method","name":"randomize_prefix!","doc":null,"summary":null,"abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/nuid.cr#L60","def":{"name":"randomize_prefix!","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"@prefix = String::Builder.build(PREFIX_LENGTH) do |io|\n Random::Secure.random_bytes(@pre_bytes)\n @pre_bytes.each do |n|\n io << \"#{DIGITS[n % BASE]}\"\n end\nend"}}],"macros":[],"types":[]},{"html_id":"github.com/nats-io/nats.cr/NATS/Subscription","path":"NATS/Subscription.html","kind":"class","full_name":"NATS::Subscription","name":"Subscription","abstract":false,"superclass":{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},"ancestors":[{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},{"html_id":"github.com/nats-io/nats.cr/Object","kind":"class","full_name":"Object","name":"Object"}],"locations":[{"filename":"nats/subscription.cr","line_number":24,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/subscription.cr"}],"repository_name":"github.com/nats-io/nats.cr","program":false,"enum":false,"alias":false,"aliased":"","const":false,"constants":[],"included_modules":[],"extended_modules":[],"subclasses":[],"including_types":[],"namespace":{"html_id":"github.com/nats-io/nats.cr/NATS","kind":"module","full_name":"NATS","name":"NATS"},"doc":"A subscription to a given subject and possible queue group. Used to unsubscribe/close.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nsub = nc.subscribe(\"foo\") { |msg| }\nsub.close\nsub.unsubscribe\n```","summary":"

A subscription to a given subject and possible queue group.

","class_methods":[],"constructors":[],"instance_methods":[{"id":"close-instance-method","html_id":"close-instance-method","name":"close","doc":"Will unsubscribe from a subscription.","summary":"

Will unsubscribe from a subscription.

","abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/subscription.cr#L39","def":{"name":"close","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"unsubscribe"}},{"id":"closed?-instance-method","html_id":"closed?-instance-method","name":"closed?","doc":"Determines if the subscription is already closed.","summary":"

Determines if the subscription is already closed.

","abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/subscription.cr#L44","def":{"name":"closed?","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"return @msgs.closed?"}},{"id":"unsubscribe-instance-method","html_id":"unsubscribe-instance-method","name":"unsubscribe","doc":"Will unsubscribe from a subscription.","summary":"

Will unsubscribe from a subscription.

","abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/subscription.cr#L31","def":{"name":"unsubscribe","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if closed?\nelse\n @conn.unsubscribe(@sid)\n @msgs.close\nend"}}],"macros":[],"types":[]}]}]}} \ No newline at end of file diff --git a/docs/js/doc.js b/docs/js/doc.js new file mode 100644 index 0000000..8796e74 --- /dev/null +++ b/docs/js/doc.js @@ -0,0 +1,1019 @@ +window.CrystalDoc = (window.CrystalDoc || {}); + +CrystalDoc.base_path = (CrystalDoc.base_path || ""); + +CrystalDoc.searchIndex = (CrystalDoc.searchIndex || false); +CrystalDoc.MAX_RESULTS_DISPLAY = 140; + +CrystalDoc.runQuery = function(query) { + function searchType(type, query, results) { + var matches = []; + var matchedFields = []; + var name = type.full_name; + var i = name.lastIndexOf("::"); + if (i > 0) { + name = name.substring(i + 2); + } + var nameMatches = query.matches(name); + if (nameMatches){ + matches = matches.concat(nameMatches); + matchedFields.push("name"); + } + + var namespaceMatches = query.matchesNamespace(type.full_name); + if(namespaceMatches){ + matches = matches.concat(namespaceMatches); + matchedFields.push("name"); + } + + var docMatches = query.matches(type.doc); + if(docMatches){ + matches = matches.concat(docMatches); + matchedFields.push("doc"); + } + if (matches.length > 0) { + results.push({ + id: type.id, + result_type: "type", + kind: type.kind, + name: name, + full_name: type.full_name, + href: type.path, + summary: type.summary, + matched_fields: matchedFields, + matched_terms: matches + }); + } + + type.instance_methods.forEach(function(method) { + searchMethod(method, type, "instance_method", query, results); + }) + type.class_methods.forEach(function(method) { + searchMethod(method, type, "class_method", query, results); + }) + type.constructors.forEach(function(constructor) { + searchMethod(constructor, type, "constructor", query, results); + }) + type.macros.forEach(function(macro) { + searchMethod(macro, type, "macro", query, results); + }) + type.constants.forEach(function(constant){ + searchConstant(constant, type, query, results); + }); + + type.types.forEach(function(subtype){ + searchType(subtype, query, results); + }); + }; + + function searchMethod(method, type, kind, query, results) { + var matches = []; + var matchedFields = []; + var nameMatches = query.matchesMethod(method.name, kind, type); + if (nameMatches){ + matches = matches.concat(nameMatches); + matchedFields.push("name"); + } + + method.args.forEach(function(arg){ + var argMatches = query.matches(arg.external_name); + if (argMatches) { + matches = matches.concat(argMatches); + matchedFields.push("args"); + } + }); + + var docMatches = query.matches(type.doc); + if(docMatches){ + matches = matches.concat(docMatches); + matchedFields.push("doc"); + } + + if (matches.length > 0) { + var typeMatches = query.matches(type.full_name); + if (typeMatches) { + matchedFields.push("type"); + matches = matches.concat(typeMatches); + } + results.push({ + id: method.id, + type: type.full_name, + result_type: kind, + name: method.name, + full_name: type.full_name + "#" + method.name, + args_string: method.args_string, + summary: method.summary, + href: type.path + "#" + method.id, + matched_fields: matchedFields, + matched_terms: matches + }); + } + } + + function searchConstant(constant, type, query, results) { + var matches = []; + var matchedFields = []; + var nameMatches = query.matches(constant.name); + if (nameMatches){ + matches = matches.concat(nameMatches); + matchedFields.push("name"); + } + var docMatches = query.matches(constant.doc); + if(docMatches){ + matches = matches.concat(docMatches); + matchedFields.push("doc"); + } + if (matches.length > 0) { + var typeMatches = query.matches(type.full_name); + if (typeMatches) { + matchedFields.push("type"); + matches = matches.concat(typeMatches); + } + results.push({ + id: constant.id, + type: type.full_name, + result_type: "constant", + name: constant.name, + full_name: type.full_name + "#" + constant.name, + value: constant.value, + summary: constant.summary, + href: type.path + "#" + constant.id, + matched_fields: matchedFields, + matched_terms: matches + }); + } + } + + var results = []; + searchType(CrystalDoc.searchIndex.program, query, results); + return results; +}; + +CrystalDoc.rankResults = function(results, query) { + function uniqueArray(ar) { + var j = {}; + + ar.forEach(function(v) { + j[v + "::" + typeof v] = v; + }); + + return Object.keys(j).map(function(v) { + return j[v]; + }); + } + + results = results.sort(function(a, b) { + var matchedTermsDiff = uniqueArray(b.matched_terms).length - uniqueArray(a.matched_terms).length; + var aHasDocs = b.matched_fields.includes("doc"); + var bHasDocs = b.matched_fields.includes("doc"); + + var aOnlyDocs = aHasDocs && a.matched_fields.length == 1; + var bOnlyDocs = bHasDocs && b.matched_fields.length == 1; + + if (a.result_type == "type" && b.result_type != "type" && !aOnlyDocs) { + if(CrystalDoc.DEBUG) { console.log("a is type b not"); } + return -1; + } else if (b.result_type == "type" && a.result_type != "type" && !bOnlyDocs) { + if(CrystalDoc.DEBUG) { console.log("b is type, a not"); } + return 1; + } + if (a.matched_fields.includes("name")) { + if (b.matched_fields.includes("name")) { + var a_name = (CrystalDoc.prefixForType(a.result_type) || "") + ((a.result_type == "type") ? a.full_name : a.name); + var b_name = (CrystalDoc.prefixForType(b.result_type) || "") + ((b.result_type == "type") ? b.full_name : b.name); + a_name = a_name.toLowerCase(); + b_name = b_name.toLowerCase(); + for(var i = 0; i < query.normalizedTerms.length; i++) { + var term = query.terms[i].replace(/^::?|::?$/, ""); + var a_orig_index = a_name.indexOf(term); + var b_orig_index = b_name.indexOf(term); + if(CrystalDoc.DEBUG) { console.log("term: " + term + " a: " + a_name + " b: " + b_name); } + if(CrystalDoc.DEBUG) { console.log(a_orig_index, b_orig_index, a_orig_index - b_orig_index); } + if (a_orig_index >= 0) { + if (b_orig_index >= 0) { + if(CrystalDoc.DEBUG) { console.log("both have exact match", a_orig_index > b_orig_index ? -1 : 1); } + if(a_orig_index != b_orig_index) { + if(CrystalDoc.DEBUG) { console.log("both have exact match at different positions", a_orig_index > b_orig_index ? 1 : -1); } + return a_orig_index > b_orig_index ? 1 : -1; + } + } else { + if(CrystalDoc.DEBUG) { console.log("a has exact match, b not"); } + return -1; + } + } else if (b_orig_index >= 0) { + if(CrystalDoc.DEBUG) { console.log("b has exact match, a not"); } + return 1; + } + } + } else { + if(CrystalDoc.DEBUG) { console.log("a has match in name, b not"); } + return -1; + } + } else if ( + !a.matched_fields.includes("name") && + b.matched_fields.includes("name") + ) { + return 1; + } + + if (matchedTermsDiff != 0 || (aHasDocs != bHasDocs)) { + if(CrystalDoc.DEBUG) { console.log("matchedTermsDiff: " + matchedTermsDiff, aHasDocs, bHasDocs); } + return matchedTermsDiff; + } + + var matchedFieldsDiff = b.matched_fields.length - a.matched_fields.length; + if (matchedFieldsDiff != 0) { + if(CrystalDoc.DEBUG) { console.log("matched to different number of fields: " + matchedFieldsDiff); } + return matchedFieldsDiff > 0 ? 1 : -1; + } + + var nameCompare = a.name.localeCompare(b.name); + if(nameCompare != 0){ + if(CrystalDoc.DEBUG) { console.log("nameCompare resulted in: " + a.name + "<=>" + b.name + ": " + nameCompare); } + return nameCompare > 0 ? 1 : -1; + } + + if(a.matched_fields.includes("args") && b.matched_fields.includes("args")) { + for(var i = 0; i < query.terms.length; i++) { + var term = query.terms[i]; + var aIndex = a.args_string.indexOf(term); + var bIndex = b.args_string.indexOf(term); + if(CrystalDoc.DEBUG) { console.log("index of " + term + " in args_string: " + aIndex + " - " + bIndex); } + if(aIndex >= 0){ + if(bIndex >= 0){ + if(aIndex != bIndex){ + return aIndex > bIndex ? 1 : -1; + } + }else{ + return -1; + } + }else if(bIndex >= 0) { + return 1; + } + } + } + + return 0; + }); + + if (results.length > 1) { + // if we have more than two search terms, only include results with the most matches + var bestMatchedTerms = uniqueArray(results[0].matched_terms).length; + + results = results.filter(function(result) { + return uniqueArray(result.matched_terms).length + 1 >= bestMatchedTerms; + }); + } + return results; +}; + +CrystalDoc.prefixForType = function(type) { + switch (type) { + case "instance_method": + return "#"; + + case "class_method": + case "macro": + case "constructor": + return "."; + + default: + return false; + } +}; + +CrystalDoc.displaySearchResults = function(results, query) { + function sanitize(html){ + return html.replace(/<(?!\/?code)[^>]+>/g, ""); + } + + // limit results + if (results.length > CrystalDoc.MAX_RESULTS_DISPLAY) { + results = results.slice(0, CrystalDoc.MAX_RESULTS_DISPLAY); + } + + var $frag = document.createDocumentFragment(); + var $resultsElem = document.querySelector(".search-list"); + $resultsElem.innerHTML = ""; + + results.forEach(function(result, i) { + var url = CrystalDoc.base_path + result.href; + var type = false; + + var title = query.highlight(result.result_type == "type" ? result.full_name : result.name); + + var prefix = CrystalDoc.prefixForType(result.result_type); + if (prefix) { + title = "" + prefix + "" + title; + } + + title = "" + title + ""; + + if (result.args_string) { + title += + "" + query.highlight(result.args_string) + ""; + } + + $elem = document.createElement("li"); + $elem.className = "search-result search-result--" + result.result_type; + $elem.dataset.href = url; + $elem.setAttribute("title", result.full_name + " docs page"); + + var $title = document.createElement("div"); + $title.setAttribute("class", "search-result__title"); + var $titleLink = document.createElement("a"); + $titleLink.setAttribute("href", url); + + $titleLink.innerHTML = title; + $title.appendChild($titleLink); + $elem.appendChild($title); + $elem.addEventListener("click", function() { + $titleLink.click(); + }); + + if (result.result_type !== "type") { + var $type = document.createElement("div"); + $type.setAttribute("class", "search-result__type"); + $type.innerHTML = query.highlight(result.type); + $elem.appendChild($type); + } + + if(result.summary){ + var $doc = document.createElement("div"); + $doc.setAttribute("class", "search-result__doc"); + $doc.innerHTML = query.highlight(sanitize(result.summary)); + $elem.appendChild($doc); + } + + $elem.appendChild(document.createComment(JSON.stringify(result))); + $frag.appendChild($elem); + }); + + $resultsElem.appendChild($frag); + + CrystalDoc.toggleResultsList(true); +}; + +CrystalDoc.toggleResultsList = function(visible) { + if (visible) { + document.querySelector(".types-list").classList.add("hidden"); + document.querySelector(".search-results").classList.remove("hidden"); + } else { + document.querySelector(".types-list").classList.remove("hidden"); + document.querySelector(".search-results").classList.add("hidden"); + } +}; + +CrystalDoc.Query = function(string) { + this.original = string; + this.terms = string.split(/\s+/).filter(function(word) { + return CrystalDoc.Query.stripModifiers(word).length > 0; + }); + + var normalized = this.terms.map(CrystalDoc.Query.normalizeTerm); + this.normalizedTerms = normalized; + + function runMatcher(field, matcher) { + if (!field) { + return false; + } + var normalizedValue = CrystalDoc.Query.normalizeTerm(field); + + var matches = []; + normalized.forEach(function(term) { + if (matcher(normalizedValue, term)) { + matches.push(term); + } + }); + return matches.length > 0 ? matches : false; + } + + this.matches = function(field) { + return runMatcher(field, function(normalized, term) { + if (term[0] == "#" || term[0] == ".") { + return false; + } + return normalized.indexOf(term) >= 0; + }); + }; + + function namespaceMatcher(normalized, term){ + var i = term.indexOf(":"); + if(i >= 0){ + term = term.replace(/^::?|::?$/, ""); + var index = normalized.indexOf(term); + if((index == 0) || (index > 0 && normalized[index-1] == ":")){ + return true; + } + } + return false; + } + this.matchesMethod = function(name, kind, type) { + return runMatcher(name, function(normalized, term) { + var i = term.indexOf("#"); + if(i >= 0){ + if (kind != "instance_method") { + return false; + } + }else{ + i = term.indexOf("."); + if(i >= 0){ + if (kind != "class_method" && kind != "macro" && kind != "constructor") { + return false; + } + }else{ + //neither # nor . + if(term.indexOf(":") && namespaceMatcher(normalized, term)){ + return true; + } + } + } + + var methodName = term; + if(i >= 0){ + var termType = term.substring(0, i); + methodName = term.substring(i+1); + + if(termType != "") { + if(CrystalDoc.Query.normalizeTerm(type.full_name).indexOf(termType) < 0){ + return false; + } + } + } + return normalized.indexOf(methodName) >= 0; + }); + }; + + this.matchesNamespace = function(namespace){ + return runMatcher(namespace, namespaceMatcher); + }; + + this.highlight = function(string) { + if (typeof string == "undefined") { + return ""; + } + function escapeRegExp(s) { + return s.replace(/[.*+?\^${}()|\[\]\\]/g, "\\$&").replace(/^[#\.:]+/, ""); + } + return string.replace( + new RegExp("(" + this.normalizedTerms.map(escapeRegExp).join("|") + ")", "gi"), + "$1" + ); + }; +}; +CrystalDoc.Query.normalizeTerm = function(term) { + return term.toLowerCase(); +}; +CrystalDoc.Query.stripModifiers = function(term) { + switch (term[0]) { + case "#": + case ".": + case ":": + return term.substr(1); + + default: + return term; + } +} + +CrystalDoc.search = function(string) { + if(!CrystalDoc.searchIndex) { + console.log("CrystalDoc search index not initialized, delaying search"); + + document.addEventListener("CrystalDoc:loaded", function listener(){ + document.removeEventListener("CrystalDoc:loaded", listener); + CrystalDoc.search(string); + }); + return; + } + + document.dispatchEvent(new Event("CrystalDoc:searchStarted")); + + var query = new CrystalDoc.Query(string); + var results = CrystalDoc.runQuery(query); + results = CrystalDoc.rankResults(results, query); + CrystalDoc.displaySearchResults(results, query); + + document.dispatchEvent(new Event("CrystalDoc:searchPerformed")); +}; + +CrystalDoc.initializeIndex = function(data) { + CrystalDoc.searchIndex = data; + + document.dispatchEvent(new Event("CrystalDoc:loaded")); +}; + +CrystalDoc.loadIndex = function() { + function loadJSON(file, callback) { + var xobj = new XMLHttpRequest(); + xobj.overrideMimeType("application/json"); + xobj.open("GET", file, true); + xobj.onreadystatechange = function() { + if (xobj.readyState == 4 && xobj.status == "200") { + callback(xobj.responseText); + } + }; + xobj.send(null); + } + + function loadScript(file) { + script = document.createElement("script"); + script.src = file; + document.body.appendChild(script); + } + + function parseJSON(json) { + CrystalDoc.initializeIndex(JSON.parse(json)); + } + + for(var i = 0; i < document.scripts.length; i++){ + var script = document.scripts[i]; + if (script.src && script.src.indexOf("js/doc.js") >= 0) { + if (script.src.indexOf("file://") == 0) { + // We need to support JSONP files for the search to work on local file system. + var jsonPath = script.src.replace("js/doc.js", "search-index.js"); + loadScript(jsonPath); + return; + } else { + var jsonPath = script.src.replace("js/doc.js", "index.json"); + loadJSON(jsonPath, parseJSON); + return; + } + } + } + console.error("Could not find location of js/doc.js"); +}; + +// Callback for jsonp +function crystal_doc_search_index_callback(data) { + CrystalDoc.initializeIndex(data); +} + +Navigator = function(sidebar, searchInput, list, leaveSearchScope){ + this.list = list; + var self = this; + + var performingSearch = false; + + document.addEventListener('CrystalDoc:searchStarted', function(){ + performingSearch = true; + }); + document.addEventListener('CrystalDoc:searchDebounceStarted', function(){ + performingSearch = true; + }); + document.addEventListener('CrystalDoc:searchPerformed', function(){ + performingSearch = false; + }); + document.addEventListener('CrystalDoc:searchDebounceStopped', function(event){ + performingSearch = false; + }); + + function delayWhileSearching(callback) { + if(performingSearch){ + document.addEventListener('CrystalDoc:searchPerformed', function listener(){ + document.removeEventListener('CrystalDoc:searchPerformed', listener); + + // add some delay to let search results display kick in + setTimeout(callback, 100); + }); + }else{ + callback(); + } + } + + function clearMoveTimeout() { + clearTimeout(self.moveTimeout); + self.moveTimeout = null; + } + + function startMoveTimeout(upwards){ + /*if(self.moveTimeout) { + clearMoveTimeout(); + } + + var go = function() { + if (!self.moveTimeout) return; + self.move(upwards); + self.moveTimout = setTimeout(go, 600); + }; + self.moveTimeout = setTimeout(go, 800);*/ + } + + function scrollCenter(element) { + var rect = element.getBoundingClientRect(); + var middle = sidebar.clientHeight / 2; + sidebar.scrollTop += rect.top + rect.height / 2 - middle; + } + + var move = this.move = function(upwards){ + if(!this.current){ + this.highlightFirst(); + return true; + } + var next = upwards ? this.current.previousElementSibling : this.current.nextElementSibling; + if(next && next.classList) { + this.highlight(next); + scrollCenter(next); + return true; + } + return false; + }; + + this.moveRight = function(){ + }; + this.moveLeft = function(){ + }; + + this.highlight = function(elem) { + if(!elem){ + return; + } + this.removeHighlight(); + + this.current = elem; + this.current.classList.add("current"); + }; + + this.highlightFirst = function(){ + this.highlight(this.list.querySelector('li:first-child')); + }; + + this.removeHighlight = function() { + if(this.current){ + this.current.classList.remove("current"); + } + this.current = null; + } + + this.openSelectedResult = function() { + if(this.current) { + this.current.click(); + } + } + + this.focus = function() { + searchInput.focus(); + searchInput.select(); + this.highlightFirst(); + } + + function handleKeyUp(event) { + switch(event.key) { + case "ArrowUp": + case "ArrowDown": + case "i": + case "j": + case "k": + case "l": + case "c": + case "h": + case "t": + case "n": + event.stopPropagation(); + clearMoveTimeout(); + } + } + + function handleKeyDown(event) { + switch(event.key) { + case "Enter": + event.stopPropagation(); + event.preventDefault(); + leaveSearchScope(); + self.openSelectedResult(); + break; + case "Escape": + event.stopPropagation(); + event.preventDefault(); + leaveSearchScope(); + break; + case "j": + case "c": + case "ArrowUp": + if(event.ctrlKey || event.key == "ArrowUp") { + event.stopPropagation(); + self.move(true); + startMoveTimeout(true); + } + break; + case "k": + case "h": + case "ArrowDown": + if(event.ctrlKey || event.key == "ArrowDown") { + event.stopPropagation(); + self.move(false); + startMoveTimeout(false); + } + break; + case "k": + case "t": + case "ArrowLeft": + if(event.ctrlKey || event.key == "ArrowLeft") { + event.stopPropagation(); + self.moveLeft(); + } + break; + case "l": + case "n": + case "ArrowRight": + if(event.ctrlKey || event.key == "ArrowRight") { + event.stopPropagation(); + self.moveRight(); + } + break; + } + } + + function handleInputKeyUp(event) { + switch(event.key) { + case "ArrowUp": + case "ArrowDown": + event.stopPropagation(); + event.preventDefault(); + clearMoveTimeout(); + } + } + + function handleInputKeyDown(event) { + switch(event.key) { + case "Enter": + event.stopPropagation(); + event.preventDefault(); + delayWhileSearching(function(){ + self.openSelectedResult(); + leaveSearchScope(); + }); + break; + case "Escape": + event.stopPropagation(); + event.preventDefault(); + // remove focus from search input + leaveSearchScope(); + sidebar.focus(); + break; + case "ArrowUp": + event.stopPropagation(); + event.preventDefault(); + self.move(true); + startMoveTimeout(true); + break; + + case "ArrowDown": + event.stopPropagation(); + event.preventDefault(); + self.move(false); + startMoveTimeout(false); + break; + } + } + + sidebar.tabIndex = 100; // set tabIndex to enable keylistener + sidebar.addEventListener('keyup', function(event) { + handleKeyUp(event); + }); + sidebar.addEventListener('keydown', function(event) { + handleKeyDown(event); + }); + searchInput.addEventListener('keydown', function(event) { + handleInputKeyDown(event); + }); + searchInput.addEventListener('keyup', function(event) { + handleInputKeyUp(event); + }); + this.move(); +}; + +var UsageModal = function(title, content) { + var $body = document.body; + var self = this; + var $modalBackground = document.createElement("div"); + $modalBackground.classList.add("modal-background"); + var $usageModal = document.createElement("div"); + $usageModal.classList.add("usage-modal"); + $modalBackground.appendChild($usageModal); + var $title = document.createElement("h3"); + $title.classList.add("modal-title"); + $title.innerHTML = title + $usageModal.appendChild($title); + var $closeButton = document.createElement("span"); + $closeButton.classList.add("close-button"); + $closeButton.setAttribute("title", "Close modal"); + $closeButton.innerText = '×'; + $usageModal.appendChild($closeButton); + $usageModal.insertAdjacentHTML("beforeend", content); + + $modalBackground.addEventListener('click', function(event) { + var element = event.target || event.srcElement; + + if(element == $modalBackground) { + self.hide(); + } + }); + $closeButton.addEventListener('click', function(event) { + self.hide(); + }); + + $body.insertAdjacentElement('beforeend', $modalBackground); + + this.show = function(){ + $body.classList.add("js-modal-visible"); + }; + this.hide = function(){ + $body.classList.remove("js-modal-visible"); + }; + this.isVisible = function(){ + return $body.classList.contains("js-modal-visible"); + } +} + + +document.addEventListener('DOMContentLoaded', function() { + var sessionStorage; + try { + sessionStorage = window.sessionStorage; + } catch (e) { } + if(!sessionStorage) { + sessionStorage = { + setItem: function() {}, + getItem: function() {}, + removeItem: function() {} + }; + } + + var repositoryName = document.querySelector('#repository-name').getAttribute('content'); + var typesList = document.querySelector('.types-list'); + var searchInput = document.querySelector('.search-input'); + var parents = document.querySelectorAll('.types-list li.parent'); + + var scrollSidebarToOpenType = function(){ + var openTypes = typesList.querySelectorAll('.current'); + if (openTypes.length > 0) { + var lastOpenType = openTypes[openTypes.length - 1]; + lastOpenType.scrollIntoView(); + } + } + + scrollSidebarToOpenType(); + + var setPersistentSearchQuery = function(value){ + sessionStorage.setItem(repositoryName + '::search-input:value', value); + } + + for(var i = 0; i < parents.length; i++) { + var _parent = parents[i]; + _parent.addEventListener('click', function(e) { + e.stopPropagation(); + + if(e.target.tagName.toLowerCase() == 'li') { + if(e.target.className.match(/open/)) { + sessionStorage.removeItem(e.target.getAttribute('data-id')); + e.target.className = e.target.className.replace(/ +open/g, ''); + } else { + sessionStorage.setItem(e.target.getAttribute('data-id'), '1'); + if(e.target.className.indexOf('open') == -1) { + e.target.className += ' open'; + } + } + } + }); + + if(sessionStorage.getItem(_parent.getAttribute('data-id')) == '1') { + _parent.className += ' open'; + } + } + + var leaveSearchScope = function(){ + CrystalDoc.toggleResultsList(false); + window.focus(); + } + + var navigator = new Navigator(document.querySelector('.types-list'), searchInput, document.querySelector(".search-results"), leaveSearchScope); + + CrystalDoc.loadIndex(); + var searchTimeout; + var lastSearchText = false; + var performSearch = function() { + document.dispatchEvent(new Event("CrystalDoc:searchDebounceStarted")); + + clearTimeout(searchTimeout); + searchTimeout = setTimeout(function() { + var text = searchInput.value; + + if(text == "") { + CrystalDoc.toggleResultsList(false); + }else if(text == lastSearchText){ + document.dispatchEvent(new Event("CrystalDoc:searchDebounceStopped")); + }else{ + CrystalDoc.search(text); + navigator.highlightFirst(); + searchInput.focus(); + } + lastSearchText = text; + setPersistentSearchQuery(text); + }, 200); + }; + + if(location.hash.length > 3 && location.hash.substring(0,3) == "#q="){ + // allows directly linking a search query which is then executed on the client + // this comes handy for establishing a custom browser search engine with https://crystal-lang.org/api/#q=%s as a search URL + // TODO: Add OpenSearch description + var searchQuery = location.hash.substring(3); + history.pushState({searchQuery: searchQuery}, "Search for " + searchQuery, location.href.replace(/#q=.*/, "")); + searchInput.value = searchQuery; + document.addEventListener('CrystalDoc:loaded', performSearch); + } + + if (searchInput.value.length == 0) { + var searchText = sessionStorage.getItem(repositoryName + '::search-input:value'); + if(searchText){ + searchInput.value = searchText; + } + } + searchInput.addEventListener('keyup', performSearch); + searchInput.addEventListener('input', performSearch); + + var usageModal = new UsageModal('Keyboard Shortcuts', '' + + '
    ' + + '
  • ' + + ' ' + + ' s,' + + ' /' + + ' ' + + ' Search' + + '
  • ' + + '
  • ' + + ' Esc' + + ' Abort search / Close modal' + + '
  • ' + + '
  • ' + + ' ' + + ' ,' + + ' Enter' + + ' ' + + ' Open highlighted result' + + '
  • ' + + '
  • ' + + ' ' + + ' ,' + + ' Ctrl+j' + + ' ' + + ' Select previous result' + + '
  • ' + + '
  • ' + + ' ' + + ' ,' + + ' Ctrl+k' + + ' ' + + ' Select next result' + + '
  • ' + + '
  • ' + + ' ?' + + ' Show usage info' + + '
  • ' + + '
' + ); + + function handleShortkeys(event) { + var element = event.target || event.srcElement; + + if(element.tagName == "INPUT" || element.tagName == "TEXTAREA" || element.parentElement.tagName == "TEXTAREA"){ + return; + } + + switch(event.key) { + case "?": + usageModal.show(); + break; + + case "Escape": + usageModal.hide(); + break; + + case "s": + case "/": + if(usageModal.isVisible()) { + return; + } + event.stopPropagation(); + navigator.focus(); + performSearch(); + break; + } + } + + document.addEventListener('keyup', handleShortkeys); + + var scrollToEntryFromLocationHash = function() { + var hash = window.location.hash; + if (hash) { + var targetAnchor = unescape(hash.substr(1)); + var targetEl = document.querySelectorAll('.entry-detail[id="' + targetAnchor + '"]'); + + if (targetEl && targetEl.length > 0) { + targetEl[0].offsetParent.scrollTop = targetEl[0].offsetTop; + } + } + }; + window.addEventListener("hashchange", scrollToEntryFromLocationHash, false); + scrollToEntryFromLocationHash(); +}); diff --git a/docs/search-index.js b/docs/search-index.js new file mode 100644 index 0000000..e0b7aaa --- /dev/null +++ b/docs/search-index.js @@ -0,0 +1 @@ +crystal_doc_search_index_callback({"repository_name":"github.com/nats-io/nats.cr","body":"# NATS\n\nSimple NATS client for the [Crystal](https://http://crystal-lang.org) programming language.\n\n## Installation\n\n1. Add the dependency to your `shard.yml`:\n\n ```yaml\n dependencies:\n NATS:\n github: nats-io/nats.cr\n ```\n\n2. Run `shards install`\n\n## Usage\n\n```crystal\nrequire \"NATS\"\n\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.subscribe(\"foo\") { |msg| puts \"Received '#{msg}'\"}\nnc.publish(\"foo\", \"Hello!\")\n\nsub = nc.subscribe(\"req\") do |msg|\n msg.respond(\"ANSWER is 42\")\nend\n\nanswer = nc.request(\"req\", \"Help!\")\nputs \"Received a response '#{answer}'!\"\n\nsub.close\n\nnc.flush\nnc.close\n```\n\n## License\n\nUnless otherwise noted, the NATS source files are distributed under\nthe Apache Version 2.0 license found in the LICENSE file.\n","program":{"html_id":"github.com/nats-io/nats.cr/toplevel","path":"toplevel.html","kind":"module","full_name":"Top Level Namespace","name":"Top Level Namespace","abstract":false,"superclass":null,"ancestors":[],"locations":[],"repository_name":"github.com/nats-io/nats.cr","program":true,"enum":false,"alias":false,"aliased":"","const":false,"constants":[],"included_modules":[],"extended_modules":[],"subclasses":[],"including_types":[],"namespace":null,"doc":null,"summary":null,"class_methods":[],"constructors":[],"instance_methods":[],"macros":[],"types":[{"html_id":"github.com/nats-io/nats.cr/NATS","path":"NATS.html","kind":"module","full_name":"NATS","name":"NATS","abstract":false,"superclass":null,"ancestors":[],"locations":[{"filename":"nats/nuid.cr","line_number":20,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/nuid.cr"},{"filename":"nats/msg.cr","line_number":15,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/msg.cr"},{"filename":"nats/subscription.cr","line_number":15,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/subscription.cr"},{"filename":"nats/connection.cr","line_number":24,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr"}],"repository_name":"github.com/nats-io/nats.cr","program":false,"enum":false,"alias":false,"aliased":"","const":false,"constants":[{"id":"LANG","name":"LANG","value":"\"crystal\"","doc":null,"summary":null},{"id":"VERSION","name":"VERSION","value":"\"0.0.1\"","doc":null,"summary":null}],"included_modules":[],"extended_modules":[],"subclasses":[],"including_types":[],"namespace":null,"doc":null,"summary":null,"class_methods":[],"constructors":[],"instance_methods":[],"macros":[],"types":[{"html_id":"github.com/nats-io/nats.cr/NATS/Connection","path":"NATS/Connection.html","kind":"class","full_name":"NATS::Connection","name":"Connection","abstract":false,"superclass":{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},"ancestors":[{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},{"html_id":"github.com/nats-io/nats.cr/Object","kind":"class","full_name":"Object","name":"Object"}],"locations":[{"filename":"nats/connection.cr","line_number":28,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr"}],"repository_name":"github.com/nats-io/nats.cr","program":false,"enum":false,"alias":false,"aliased":"","const":false,"constants":[],"included_modules":[],"extended_modules":[],"subclasses":[],"including_types":[],"namespace":{"html_id":"github.com/nats-io/nats.cr/NATS","kind":"module","full_name":"NATS","name":"NATS"},"doc":null,"summary":null,"class_methods":[],"constructors":[{"id":"new(host,port,user:String?=nil,pass:String?=nil,name:String?=nil,echo=true,pedantic=false)-class-method","html_id":"new(host,port,user:String?=nil,pass:String?=nil,name:String?=nil,echo=true,pedantic=false)-class-method","name":"new","doc":"Creates a new connection to a NATS Server.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc = NATS::Connection.new(\"tls://demo.nats.io\")\nnc = NATS::Connection.new(\"nats://#{user}:#{pass}@127.0.0.1:4222\")\nnc = NATS::Connection.new(4222, name: \"Sample App\", user: \"derek\", pass: \"s3cr3t\")\nnc = NATS::Connection.new(4222)\nnc = NATS::Connection.new\n```","summary":"

Creates a new connection to a NATS Server.

","abstract":false,"args":[{"name":"host","doc":null,"default_value":"","external_name":"host","restriction":""},{"name":"port","doc":null,"default_value":"","external_name":"port","restriction":""},{"name":"user","doc":null,"default_value":"nil","external_name":"user","restriction":"String | ::Nil"},{"name":"pass","doc":null,"default_value":"nil","external_name":"pass","restriction":"String | ::Nil"},{"name":"name","doc":null,"default_value":"nil","external_name":"name","restriction":"String | ::Nil"},{"name":"echo","doc":null,"default_value":"true","external_name":"echo","restriction":""},{"name":"pedantic","doc":null,"default_value":"false","external_name":"pedantic","restriction":""}],"args_string":"(host, port, user : String? = nil, pass : String? = nil, name : String? = nil, echo = true, pedantic = false)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L55","def":{"name":"new","args":[{"name":"host","doc":null,"default_value":"","external_name":"host","restriction":""},{"name":"port","doc":null,"default_value":"","external_name":"port","restriction":""},{"name":"user","doc":null,"default_value":"nil","external_name":"user","restriction":"String | ::Nil"},{"name":"pass","doc":null,"default_value":"nil","external_name":"pass","restriction":"String | ::Nil"},{"name":"name","doc":null,"default_value":"nil","external_name":"name","restriction":"String | ::Nil"},{"name":"echo","doc":null,"default_value":"true","external_name":"echo","restriction":""},{"name":"pedantic","doc":null,"default_value":"false","external_name":"pedantic","restriction":""}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"_ = allocate\n_.initialize(host, port, user, pass, name, echo, pedantic)\nif _.responds_to?(:finalize)\n ::GC.add_finalizer(_)\nend\n_\n"}}],"instance_methods":[{"id":"close-instance-method","html_id":"close-instance-method","name":"close","doc":"Close a connection to the NATS server.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.close\n```","summary":"

Close a connection to the NATS server.

","abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L347","def":{"name":"close","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if @closed\n return\nend\n@closed = true\n@out.synchronize do\n flush_outbound\n @socket.flush\nend\n@socket.close\n@subs.each do |sid, sub|\n sub.unsubscribe\nend\n@close_cb.try do |cb|\n spawn(cb.call)\nend\n"}},{"id":"closed?:Bool-instance-method","html_id":"closed?:Bool-instance-method","name":"closed?","doc":null,"summary":null,"abstract":false,"args":[],"args_string":" : Bool","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L42","def":{"name":"closed?","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"@closed"}},{"id":"flush(timeout=2.second)-instance-method","html_id":"flush(timeout=2.second)-instance-method","name":"flush","doc":"Flush will flush the connection to the server. Can specify a *timeout*.","summary":"

Flush will flush the connection to the server.

","abstract":false,"args":[{"name":"timeout","doc":null,"default_value":"2.second","external_name":"timeout","restriction":""}],"args_string":"(timeout = 2.second)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L229","def":{"name":"flush","args":[{"name":"timeout","doc":null,"default_value":"2.second","external_name":"timeout","restriction":""}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"ch = Channel(Nil).new\n@pongs.push(ch)\n@out.synchronize do\n @buf.write(PING_SLICE)\nend\nflush_outbound\nspawn do\n sleep(timeout)\n ch.close\nend\nbegin\n ch.receive\nrescue\n {raise(\"Flush Timeout\")}\nend\n"}},{"id":"max_payload:Int32-instance-method","html_id":"max_payload:Int32-instance-method","name":"max_payload","doc":null,"summary":null,"abstract":false,"args":[],"args_string":" : Int32","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L43","def":{"name":"max_payload","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"@max_payload"}},{"id":"new_inbox-instance-method","html_id":"new_inbox-instance-method","name":"new_inbox","doc":null,"summary":null,"abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L238","def":{"name":"new_inbox","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"\"#{@resp_sub_prefix}.#{inbox_token}\""}},{"id":"on_close(&callback)-instance-method","html_id":"on_close(&callback)-instance-method","name":"on_close","doc":"Setup a callback for when the connection closes.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.on_close { puts \"Connection closed!\" }\nnc.close\n```","summary":"

Setup a callback for when the connection closes.

","abstract":false,"args":[],"args_string":"(&callback)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L369","def":{"name":"on_close","args":[],"double_splat":null,"splat_index":null,"yields":0,"block_arg":{"name":"callback","doc":null,"default_value":"","external_name":"callback","restriction":""},"return_type":"","visibility":"Public","body":"@close_cb = callback"}},{"id":"on_error(&callback:String->)-instance-method","html_id":"on_error(&callback:String->)-instance-method","name":"on_error","doc":"Setup a callback for an async errors that are received.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.on_error { |e| puts \"Received an error #{e}\" }\n```","summary":"

Setup a callback for an async errors that are received.

","abstract":false,"args":[],"args_string":"(&callback : String -> )","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L379","def":{"name":"on_error","args":[],"double_splat":null,"splat_index":null,"yields":1,"block_arg":{"name":"callback","doc":null,"default_value":"","external_name":"callback","restriction":"(String -> )"},"return_type":"","visibility":"Public","body":"@err_cb = callback"}},{"id":"publish(subject:String,msg)-instance-method","html_id":"publish(subject:String,msg)-instance-method","name":"publish","doc":"Publishes a messages to a given subject.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.publish(\"foo\", \"Hello!\")\n```","summary":"

Publishes a messages to a given subject.

","abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"},{"name":"msg","doc":null,"default_value":"","external_name":"msg","restriction":""}],"args_string":"(subject : String, msg)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L164","def":{"name":"publish","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"},{"name":"msg","doc":null,"default_value":"","external_name":"msg","restriction":""}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if subject.empty?\n raise(\"Bad Subject\")\nend\nif closed?\n raise(\"Connection Closed\")\nend\ndata = msg.to_slice\ncheck_size(data)\n@out.synchronize do\n @buf.write(PUB_SLICE)\n @buf.write(\"#{subject} #{data.size}\".to_slice)\n @buf.write(CR_LF_SLICE)\n @buf.write(data)\n @buf.write(CR_LF_SLICE)\nend\nif @flush.empty?\n @flush.send(true)\nend\n"}},{"id":"publish(subject:String)-instance-method","html_id":"publish(subject:String)-instance-method","name":"publish","doc":"Publishes an empty message to a given subject.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.publish(\"foo\")\n```","summary":"

Publishes an empty message to a given subject.

","abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"}],"args_string":"(subject : String)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L188","def":{"name":"publish","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if subject.empty?\n raise(\"Bad Subject\")\nend\nif closed?\n raise(\"Connection Closed\")\nend\n@out.synchronize do\n @buf.write(\"PUB #{subject} 0\\r\\n\\r\\n\".to_slice)\nend\nif @flush.empty?\n @flush.send(true)\nend\n"}},{"id":"publish_with_reply(subject,reply:String,msg)-instance-method","html_id":"publish_with_reply(subject,reply:String,msg)-instance-method","name":"publish_with_reply","doc":"Publishes a messages to a given subject with a reply subject.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.publish_with_reply(\"foo\", \"reply\", \"Hello!\")\n```","summary":"

Publishes a messages to a given subject with a reply subject.

","abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":""},{"name":"reply","doc":null,"default_value":"","external_name":"reply","restriction":"String"},{"name":"msg","doc":null,"default_value":"","external_name":"msg","restriction":""}],"args_string":"(subject, reply : String, msg)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L220","def":{"name":"publish_with_reply","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":""},{"name":"reply","doc":null,"default_value":"","external_name":"reply","restriction":"String"},{"name":"msg","doc":null,"default_value":"","external_name":"msg","restriction":""}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if subject.empty?\n raise(\"Bad Subject\")\nend\nif closed?\n raise(\"Connection Closed\")\nend\n@out.synchronize do\n @buf.write(\"PUB #{subject} #{reply} 0\\r\\n\\r\\n\".to_slice)\nend\nif @flush.empty?\n @flush.send(true)\nend\n"}},{"id":"request(subject:String,msg?,timeout=2.second)-instance-method","html_id":"request(subject:String,msg?,timeout=2.second)-instance-method","name":"request","doc":"Request will send a request to the given subject and wait up to *timeout* for a response.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nanswer = nc.request(\"req\", \"Help!\")\nputs \"Received a response '#{answer}'!\"\n```","summary":"

Request will send a request to the given subject and wait up to timeout for a response.

","abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"},{"name":"msg?","doc":null,"default_value":"","external_name":"msg?","restriction":""},{"name":"timeout","doc":null,"default_value":"2.second","external_name":"timeout","restriction":""}],"args_string":"(subject : String, msg?, timeout = 2.second)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L275","def":{"name":"request","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"},{"name":"msg?","doc":null,"default_value":"","external_name":"msg?","restriction":""},{"name":"timeout","doc":null,"default_value":"2.second","external_name":"timeout","restriction":""}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if @resp_sub_created\nelse\n create_resp_subscription\nend\ntoken = inbox_token\nreply = \"#{@resp_sub_prefix}.#{token}\"\nch = Channel(Msg | ::Nil).new\n@resp_map[token] = ch\npublish_with_reply(subject, reply, msg?)\nspawn do\n sleep(timeout)\n ch.close\nend\nbegin\n msg = ch.receive\nrescue\n @resp_map.delete(token)\n raise(\"Request Timeout\")\nend\n"}},{"id":"subscribe(subject,queue:String,&callback:Msg->)-instance-method","html_id":"subscribe(subject,queue:String,&callback:Msg->)-instance-method","name":"subscribe","doc":"Subscribe to a given subject with the queue group. Will yield to the callback provided with the message received.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.subscribe(\"foo\", \"group1\") { |msg| puts \"Received '#{msg}'\" }\n```","summary":"

Subscribe to a given subject with the queue group.

","abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":""},{"name":"queue","doc":null,"default_value":"","external_name":"queue","restriction":"String"}],"args_string":"(subject, queue : String, &callback : Msg -> )","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L321","def":{"name":"subscribe","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":""},{"name":"queue","doc":null,"default_value":"","external_name":"queue","restriction":"String"}],"double_splat":null,"splat_index":null,"yields":1,"block_arg":{"name":"callback","doc":null,"default_value":"","external_name":"callback","restriction":"(Msg -> )"},"return_type":"","visibility":"Public","body":"sid = @gsid = @gsid + 1\n@out.synchronize do\n ((((@buf << \"SUB \") << subject) << \" \") << queue) << \" #{sid}\\r\\n\"\nend\nif @flush.empty?\n @flush.send(true)\nend\n(Subscription.new(sid, self, callback)).tap do |sub|\n @subs[sid] = sub\nend\n"}},{"id":"subscribe(subject:String,queue:String?,&callback:Msg->)-instance-method","html_id":"subscribe(subject:String,queue:String?,&callback:Msg->)-instance-method","name":"subscribe","doc":null,"summary":null,"abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"},{"name":"queue","doc":null,"default_value":"","external_name":"queue","restriction":"String | Nil"}],"args_string":"(subject : String, queue : String?, &callback : Msg -> )","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L330","def":{"name":"subscribe","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"},{"name":"queue","doc":null,"default_value":"","external_name":"queue","restriction":"String | Nil"}],"double_splat":null,"splat_index":null,"yields":1,"block_arg":{"name":"callback","doc":null,"default_value":"","external_name":"callback","restriction":"(Msg -> )"},"return_type":"","visibility":"Public","body":"if queue.is_a?(String)\n return subscribe(subject, queue, &callback)\nend\nsubscribe(subject, &callback)\n"}},{"id":"subscribe(subject:String,&callback:Msg->)-instance-method","html_id":"subscribe(subject:String,&callback:Msg->)-instance-method","name":"subscribe","doc":"Subscribe to a given subject. Will yield to the callback provided with the message received.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.subscribe(\"foo\") { |msg| puts \"Received '#{msg}'\" }\n```","summary":"

Subscribe to a given subject.

","abstract":false,"args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"}],"args_string":"(subject : String, &callback : Msg -> )","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/connection.cr#L306","def":{"name":"subscribe","args":[{"name":"subject","doc":null,"default_value":"","external_name":"subject","restriction":"String"}],"double_splat":null,"splat_index":null,"yields":1,"block_arg":{"name":"callback","doc":null,"default_value":"","external_name":"callback","restriction":"(Msg -> )"},"return_type":"","visibility":"Public","body":"sid = @gsid = @gsid + 1\n@out.synchronize do\n ((@buf << \"SUB \") << subject) << \" #{sid}\\r\\n\"\nend\nif @flush.empty?\n @flush.send(true)\nend\n(Subscription.new(sid, self, callback)).tap do |sub|\n @subs[sid] = sub\nend\n"}}],"macros":[],"types":[]},{"html_id":"github.com/nats-io/nats.cr/NATS/Msg","path":"NATS/Msg.html","kind":"class","full_name":"NATS::Msg","name":"Msg","abstract":false,"superclass":{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},"ancestors":[{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},{"html_id":"github.com/nats-io/nats.cr/Object","kind":"class","full_name":"Object","name":"Object"}],"locations":[{"filename":"nats/msg.cr","line_number":27,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/msg.cr"}],"repository_name":"github.com/nats-io/nats.cr","program":false,"enum":false,"alias":false,"aliased":"","const":false,"constants":[],"included_modules":[],"extended_modules":[],"subclasses":[],"including_types":[],"namespace":{"html_id":"github.com/nats-io/nats.cr/NATS","kind":"module","full_name":"NATS","name":"NATS"},"doc":"A delivered message from a subscription.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.subscribe(\"foo\") do |msg|\n puts \"Received '#{msg}'\"\n puts \"Subject is #{msg.subject}\"\n puts \"Reply Subject is #{msg.reply}\"\n puts \"Raw Data is #{msg.data}\"\nend\n```","summary":"

A delivered message from a subscription.

","class_methods":[],"constructors":[],"instance_methods":[{"id":"data:Bytes-instance-method","html_id":"data:Bytes-instance-method","name":"data","doc":null,"summary":null,"abstract":false,"args":[],"args_string":" : Bytes","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/msg.cr#L32","def":{"name":"data","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"Bytes","visibility":"Public","body":"@data"}},{"id":"reply:String?-instance-method","html_id":"reply:String?-instance-method","name":"reply","doc":null,"summary":null,"abstract":false,"args":[],"args_string":" : String?","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/msg.cr#L31","def":{"name":"reply","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"String | ::Nil","visibility":"Public","body":"@reply"}},{"id":"respond(msg)-instance-method","html_id":"respond(msg)-instance-method","name":"respond","doc":"Allows a response to a request message to be easily sent.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nnc.subscribe(\"req\") do |msg|\n msg.respond(\"ANSWER is 42\")\nend\n```","summary":"

Allows a response to a request message to be easily sent.

","abstract":false,"args":[{"name":"msg","doc":null,"default_value":"","external_name":"msg","restriction":""}],"args_string":"(msg)","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/msg.cr#L54","def":{"name":"respond","args":[{"name":"msg","doc":null,"default_value":"","external_name":"msg","restriction":""}],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if @reply.nil?\n raise(\"No reply subject\")\nend\nif @conn.nil?\n raise(\"Not a received message\")\nend\n@conn.try(&.publish(@reply.to_s, msg))\n"}},{"id":"subject:String-instance-method","html_id":"subject:String-instance-method","name":"subject","doc":null,"summary":null,"abstract":false,"args":[],"args_string":" : String","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/msg.cr#L30","def":{"name":"subject","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"String","visibility":"Public","body":"@subject"}}],"macros":[],"types":[]},{"html_id":"github.com/nats-io/nats.cr/NATS/NUID","path":"NATS/NUID.html","kind":"class","full_name":"NATS::NUID","name":"NUID","abstract":false,"superclass":{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},"ancestors":[{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},{"html_id":"github.com/nats-io/nats.cr/Object","kind":"class","full_name":"Object","name":"Object"}],"locations":[{"filename":"nats/nuid.cr","line_number":21,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/nuid.cr"}],"repository_name":"github.com/nats-io/nats.cr","program":false,"enum":false,"alias":false,"aliased":"","const":false,"constants":[{"id":"BASE","name":"BASE","value":"62","doc":null,"summary":null},{"id":"DIGITS","name":"DIGITS","value":"\"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\"","doc":null,"summary":null},{"id":"INC","name":"INC","value":"MAX_INC - MIN_INC","doc":null,"summary":null},{"id":"MAX_INC","name":"MAX_INC","value":"333_i64","doc":null,"summary":null},{"id":"MAX_SEQ","name":"MAX_SEQ","value":"839299365868340224_i64","doc":null,"summary":null},{"id":"MIN_INC","name":"MIN_INC","value":"33_i64","doc":null,"summary":null},{"id":"PREFIX_LENGTH","name":"PREFIX_LENGTH","value":"12","doc":null,"summary":null},{"id":"SEQ_LENGTH","name":"SEQ_LENGTH","value":"10","doc":null,"summary":null},{"id":"TOTAL_LENGTH","name":"TOTAL_LENGTH","value":"PREFIX_LENGTH + SEQ_LENGTH","doc":null,"summary":null}],"included_modules":[],"extended_modules":[],"subclasses":[],"including_types":[],"namespace":{"html_id":"github.com/nats-io/nats.cr/NATS","kind":"module","full_name":"NATS","name":"NATS"},"doc":null,"summary":null,"class_methods":[],"constructors":[{"id":"new-class-method","html_id":"new-class-method","name":"new","doc":null,"summary":null,"abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/nuid.cr#L32","def":{"name":"new","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"_ = allocate\n_.initialize\nif _.responds_to?(:finalize)\n ::GC.add_finalizer(_)\nend\n_\n"}}],"instance_methods":[{"id":"next-instance-method","html_id":"next-instance-method","name":"next","doc":null,"summary":null,"abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/nuid.cr#L38","def":{"name":"next","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"@seq = @seq + @inc\nif @seq >= MAX_SEQ\n reset!\nend\nl = @seq\ns_10 = DIGITS[l % BASE]\ns_09, s_08, s_07, s_06, s_05, s_04, s_03, s_02, s_01 = (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE]), (l = l / BASE\nDIGITS[l % BASE])\n\"#{@prefix}#{s_01}#{s_02}#{s_03}#{s_04}#{s_05}#{s_06}#{s_07}#{s_08}#{s_09}#{s_10}\"\n"}},{"id":"randomize_prefix!-instance-method","html_id":"randomize_prefix!-instance-method","name":"randomize_prefix!","doc":null,"summary":null,"abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/nuid.cr#L60","def":{"name":"randomize_prefix!","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"@prefix = String::Builder.build(PREFIX_LENGTH) do |io|\n Random::Secure.random_bytes(@pre_bytes)\n @pre_bytes.each do |n|\n io << \"#{DIGITS[n % BASE]}\"\n end\nend"}}],"macros":[],"types":[]},{"html_id":"github.com/nats-io/nats.cr/NATS/Subscription","path":"NATS/Subscription.html","kind":"class","full_name":"NATS::Subscription","name":"Subscription","abstract":false,"superclass":{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},"ancestors":[{"html_id":"github.com/nats-io/nats.cr/Reference","kind":"class","full_name":"Reference","name":"Reference"},{"html_id":"github.com/nats-io/nats.cr/Object","kind":"class","full_name":"Object","name":"Object"}],"locations":[{"filename":"nats/subscription.cr","line_number":24,"url":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/subscription.cr"}],"repository_name":"github.com/nats-io/nats.cr","program":false,"enum":false,"alias":false,"aliased":"","const":false,"constants":[],"included_modules":[],"extended_modules":[],"subclasses":[],"including_types":[],"namespace":{"html_id":"github.com/nats-io/nats.cr/NATS","kind":"module","full_name":"NATS","name":"NATS"},"doc":"A subscription to a given subject and possible queue group. Used to unsubscribe/close.\n\n```\nnc = NATS::Connection.new(\"demo.nats.io\")\nsub = nc.subscribe(\"foo\") { |msg| }\nsub.close\nsub.unsubscribe\n```","summary":"

A subscription to a given subject and possible queue group.

","class_methods":[],"constructors":[],"instance_methods":[{"id":"close-instance-method","html_id":"close-instance-method","name":"close","doc":"Will unsubscribe from a subscription.","summary":"

Will unsubscribe from a subscription.

","abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/subscription.cr#L39","def":{"name":"close","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"unsubscribe"}},{"id":"closed?-instance-method","html_id":"closed?-instance-method","name":"closed?","doc":"Determines if the subscription is already closed.","summary":"

Determines if the subscription is already closed.

","abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/subscription.cr#L44","def":{"name":"closed?","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"return @msgs.closed?"}},{"id":"unsubscribe-instance-method","html_id":"unsubscribe-instance-method","name":"unsubscribe","doc":"Will unsubscribe from a subscription.","summary":"

Will unsubscribe from a subscription.

","abstract":false,"args":[],"args_string":"","source_link":"https://github.com/nats-io/nats.cr/blob/5949408bd9050474736462ab7e71efbe2cc6f0e5/src/nats/subscription.cr#L31","def":{"name":"unsubscribe","args":[],"double_splat":null,"splat_index":null,"yields":null,"block_arg":null,"return_type":"","visibility":"Public","body":"if closed?\nelse\n @conn.unsubscribe(@sid)\n @msgs.close\nend"}}],"macros":[],"types":[]}]}]}}) \ No newline at end of file diff --git a/shard.yml b/shard.yml new file mode 100644 index 0000000..292202c --- /dev/null +++ b/shard.yml @@ -0,0 +1,19 @@ +name: nats +version: 0.0.1 + +authors: + - Derek Collison + +github: nats-io/nats.cr + +targets: + nats-pub: + main: bin/nats-pub.cr + nats-sub: + main: bin/nats-sub.cr + nats-req: + main: bin/nats-req.cr + +crystal: 0.30.1 + +license: Apache 2 diff --git a/spec/nats/auth_spec.cr b/spec/nats/auth_spec.cr new file mode 100644 index 0000000..ba832f7 --- /dev/null +++ b/spec/nats/auth_spec.cr @@ -0,0 +1,32 @@ +require "../spec_helper" + +describe "NATS::Connection#auth" do + user = "user" + pass = "s3cr3t" + + auth_uri = URI.parse("nats://#{user}:#{pass}@127.0.0.1:4322") + noauth_uri = URI.parse("nats://127.0.0.1:4322") + + auth_server : NATSServer | Nil = nil + + Spec.after_each do + auth_server.try(&.shutdown) + auth_server = nil + end + + describe "#new" do + it "should connect with correct user/pass" do + auth_server = NATSServer.start(auth_uri) + nc = NATS::Connection.new(auth_uri) + nc.should_not be_nil + nc.close + end + + it "should fail with no user/pass" do + auth_server = NATSServer.start(auth_uri) + expect_raises(Exception) do + NATS::Connection.new(noauth_uri) + end + end + end +end diff --git a/spec/nats/basic_spec.cr b/spec/nats/basic_spec.cr new file mode 100644 index 0000000..348d32b --- /dev/null +++ b/spec/nats/basic_spec.cr @@ -0,0 +1,189 @@ +require "../spec_helper" + +describe NATS::Connection do + uri = URI.parse("nats://127.0.0.1:4322") + server : NATSServer | Nil = nil + + Spec.before_each do + server = NATSServer.start(uri) + end + + Spec.after_each do + server.try(&.shutdown) + server = nil + end + + describe "#new" do + it "Should raise when failing to connect" do + expect_raises(Exception) do + NATS::Connection.new("nats://127.0.0.1:4333") + end + end + + it "Should do basic connect with proper uri" do + nc = NATS::Connection.new(uri) + nc.should_not be_nil + nc.close + end + end + + describe "#close" do + it "Should report closed when closed" do + nc = NATS::Connection.new(uri) + nc.closed?.should be_false + nc.close + nc.closed?.should be_true + end + end + + describe "#on_close" do + it "Should call on_close callback when closing" do + nc = NATS::Connection.new(uri) + ch = Channel(Bool).new + nc.on_close { ch.send(true) } + nc.close + wait(ch) + end + + it "Should call on_close when connection closed underneath" do + nc = NATS::Connection.new(uri) + ch = Channel(Bool).new + nc.on_close { ch.send(true) } + server.try(&.shutdown) + wait(ch) + end + end + + describe "#new_inbox" do + it "Should properly create _INBOX subjects" do + nc = NATS::Connection.new(uri) + inbox = nc.new_inbox + inbox.should start_with("_INBOX") + inbox.split(".").size.should eq(3) + end + end + + describe "#publish" do + it "Should not raise when doing a publish" do + nc = NATS::Connection.new(uri) + nc.publish("foo", "Hello!") + nc.close + end + + it "Should raise on empty subject" do + nc = NATS::Connection.new(uri) + expect_raises(Exception) { nc.publish("", "Hello!") } + end + + it "Should raise on closed connection" do + nc = NATS::Connection.new(uri) + nc.close + expect_raises(Exception) { nc.publish("foo", "Hello!") } + end + + it "Should raise on too big of a payload" do + nc = NATS::Connection.new(uri) + expect_raises(Exception) { nc.publish("foo", "abc"*10_000_000) } + end + + it "Should deliver an error on bad subject" do + nc = NATS::Connection.new(uri, pedantic: true) + ch = Channel(Bool).new + nc.on_error do |e| + e.should contain("Invalid Publish Subject") + ch.send(true) + end + nc.publish("foo..", "abc") + wait(ch) + nc.close + end + end + + describe "#publish_with_reply" do + it "Should attach a reply subject" do + nc = NATS::Connection.new(uri) + ch = Channel(Bool).new + nc.subscribe("foo") do |msg| + msg.should_not be_nil + msg.reply.should_not be_nil + msg.reply.to_s.should eq("respond") + ch.send(true) + end + nc.publish_with_reply("foo", "respond", "Hello!") + wait(ch) + nc.close + end + end + + describe "#subscribe" do + it "Should receive messages it subscribed to" do + nc = NATS::Connection.new(uri) + ch = Channel(Bool).new + nc.subscribe("foo") { ch.send(true) } + nc.publish("foo", "Hello!") + wait(ch) + nc.close + end + + it "Should receive messages it subscribed to as a group" do + nc = NATS::Connection.new(uri) + ch = Channel(Bool).new + nc.subscribe("foo", "bar") { ch.send(true) } + nc.subscribe("foo", "bar") { ch.send(true) } + nc.publish("foo", "Hello!") + nc.publish("foo", "Hello!") + wait(ch) + wait(ch) + end + + it "Should not receive messages after unssubscribe" do + nc = NATS::Connection.new(uri) + ch = Channel(Bool).new + sub = nc.subscribe("foo") { ch.send(true) } + nc.publish("foo", "Hello!") + wait(ch) + sub.unsubscribe + nc.publish("foo", "Hello!") + spawn { sleep 20.millisecond; ch.close } + expect_raises(Exception) { ch.receive } + sub.closed?.should be_true + # Unsubscribing/closing twice should be ok. + sub.close + end + end + + describe "#request" do + it "Should receive requests with reply subjects" do + nc = NATS::Connection.new(uri) + nc.subscribe("request") do |msg| + msg.should_not be_nil + msg.reply.should_not be_nil + msg.reply.to_s.should start_with("_INBOX") + nc.publish(msg.reply.to_s, "42") + end + answer = nc.request("request", "Help!") + answer.to_s.should eq("42") + nc.close + end + + it "Should timeout correctly" do + nc = NATS::Connection.new(uri) + elapsed = Time.measure do + answer = nc.request("request", "Help!", 10.millisecond) + rescue + end + elapsed.should be_close(10.millisecond, 5.millisecond) + nc.close + end + + it "Should be able to respond to request message" do + nc = NATS::Connection.new(uri) + nc.subscribe("request") do |req| + req.respond("42") + end + answer = nc.request("request", "Help!") + answer.to_s.should eq("42") + nc.close + end + end +end diff --git a/spec/nats/tls_spec.cr b/spec/nats/tls_spec.cr new file mode 100644 index 0000000..8d89be6 --- /dev/null +++ b/spec/nats/tls_spec.cr @@ -0,0 +1,26 @@ +require "../spec_helper" + +describe "NATS::Connection#TLS" do + tls_uri = "tls://demo.nats.io" + + describe "#new" do + it "should connect to a TLS based server" do + nc = NATS::Connection.new(tls_uri) + nc.should_not be_nil + nc.close + end + end + + describe "#subscribe" do + it "Should receive messages it subscribed to" do + nc = NATS::Connection.new(tls_uri) + inbox = nc.new_inbox + ch = Channel(Bool).new + ch = Channel(Bool).new + nc.subscribe(inbox) { ch.send(true) } + nc.publish(inbox, "Hello!") + wait(ch) + nc.close + end + end +end diff --git a/spec/nats_spec.cr b/spec/nats_spec.cr new file mode 100644 index 0000000..99a24e5 --- /dev/null +++ b/spec/nats_spec.cr @@ -0,0 +1,2 @@ +require "./spec_helper" +require "./nats/*" diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr new file mode 100644 index 0000000..0e04113 --- /dev/null +++ b/spec/spec_helper.cr @@ -0,0 +1,71 @@ +require "spec" +require "socket" +require "../src/nats" +require "../src/nats/connection" + +class NATSServer + getter? was_running + + def self.start(uri = NATS::CONNECTION::DEFAULT_URI) + server = NATSServer.new(uri) + server.start(true) + return server + end + + getter uri + + def initialize(uri = NATS::CONNECTION::DEFAULT_URI, flags : String? = nil, config_file : String? = nil) + @uri = uri.is_a?(URI) ? uri : URI.parse(uri) + @flags = flags + @config_file = config_file + end + + def start(wait_for_server = true) + args = ["-p", "#{@uri.port}", "-a", "#{@uri.host}"] + + if @uri.user && !@uri.password + args += " --auth #{@uri.user}".split + else + args += " --user #{@uri.user}".split if @uri.user + args += " --pass #{@uri.password}".split if @uri.password + end + args += " #{@flags}".split if @flags + + args << "-DV" if ENV["DEBUG_NATS_TEST"]? == "true" + + @p = Process.new("nats-server", args) + raise "Server could not start, already running?" if @p.nil? || @p.try(&.terminated?) + wait_for_server(@uri, 10) if wait_for_server + end + + def shutdown + @p.try(&.kill) + @p = nil + rescue + end + + def wait_for_server(uri, max_wait = 5) + start = Time.now + wait = Time::Span.new(0, 0, max_wait) + while (Time.now - start < wait) # Wait max_wait seconds max + return if server_running?(uri) + sleep(0.1) + end + raise "Server not started, can not connect" + end + + def server_running?(uri) + s = TCPSocket.new(uri.host.to_s, uri.port) + s.close + return true + rescue + return false + end +end + +def wait(ch : Channel, timeout = 2.second) + spawn { sleep timeout; ch.close } + return ch.receive +rescue + fail("Operation timed-out") +end diff --git a/src/nats.cr b/src/nats.cr new file mode 100644 index 0000000..5730c86 --- /dev/null +++ b/src/nats.cr @@ -0,0 +1,3 @@ +require "./nats/*" + +# TODO: Write documentation for `Nats` diff --git a/src/nats/connection.cr b/src/nats/connection.cr new file mode 100644 index 0000000..b101723 --- /dev/null +++ b/src/nats/connection.cr @@ -0,0 +1,517 @@ +# Copyright 2019 The NATS Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "socket" +require "uri" +require "json" +require "openssl" + +require "./nuid" +require "./msg" +require "./subscription" + +module NATS + VERSION = "0.0.1" + LANG = "crystal" + + class Connection + # :nodoc: + DEFAULT_PORT = 4222 + # :nodoc: + DEFAULT_AUTH_PORT = 4443 + # :nodoc: + DEFAULT_PRE = "nats://127.0.0.1:" + # :nodoc: + DEFAULT_URI = URI.parse("#{DEFAULT_PRE}#{DEFAULT_PORT}") + # :nodoc: + BUFFER_SIZE = 32768 + # :nodoc: + MAX_PAYLOAD = 1_000_000 + + getter? closed + getter max_payload + + # Creates a new connection to a NATS Server. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # nc = NATS::Connection.new("tls://demo.nats.io") + # nc = NATS::Connection.new("nats://#{user}:#{pass}@127.0.0.1:4222") + # nc = NATS::Connection.new(4222, name: "Sample App", user: "derek", pass: "s3cr3t") + # nc = NATS::Connection.new(4222) + # nc = NATS::Connection.new + # ``` + def initialize( + host, port, + @user : String? = nil, + @pass : String? = nil, + @name : String? = nil, + @echo = true, + @pedantic = false + ) + # For new style INBOX behavior. + @nuid = NUID.new + @resp_sub_prefix = "_INBOX.#{@nuid.next}" + @rand = Random.new + + # This will be updated when we receive an INFO from the server. + @max_payload = MAX_PAYLOAD + + # For flush + @pongs = Deque(Channel(Nil)).new + + # FIXME(dlc) - timeouts on connect. + # dns_timeout = nil, connect_timeout = nil + @socket = TCPSocket.new(host, port) + if (s = @socket).is_a?(TCPSocket) + s.tcp_nodelay = true + s.sync = true + s.read_buffering = true + s.buffer_size = BUFFER_SIZE + end + + # For efficient batched writes + @buf = IO::Memory.new(BUFFER_SIZE) + @out = Mutex.new + + @closed = false + @gsid = 0 + @subs = {} of Int32 => (Subscription | InternalSubscription) + @resp_map = {} of String => Channel(Msg?) + @resp_sub_created = false + + # read in the INFO block here inline before we start up + # the inbound fiber. + # TODO(dlc) put read timeout here. + @server_info = uninitialized Hash(String, JSON::Any) + process_first_info + + # send connect + send_connect + + # FIXME(dlc) - make sure we do not have auth error etc. + # send ping and expect pong. + connect_ok? + + # spawn our inbound processing fiber + spawn inbound + + # spawn for our outbound + @flush = Channel(Bool?).new(8) + spawn outbound + end + + # :nodoc: + def self.new(**args) + new(DEFAULT_URI, **args) + end + + # :nodoc: + def self.new(port : Int, **args) + new(URI.parse("#{DEFAULT_PRE}#{port}"), **args) + end + + # :nodoc: + def self.new(url : String, **args) + # We want to allow simple strings, e.g. demo.nats.io + # so make sure to add on scheme and port if needed. + url = "nats://#{url}" unless url.includes? "://" + unless url.index(/:\d/) != nil + url = "#{url}:#{DEFAULT_PORT}" if url.starts_with? "nats" + url = "#{url}:#{DEFAULT_AUTH_PORT}" if url.starts_with? "tls" + end + new(URI.parse(url), **args) + end + + # :nodoc: + def self.new(uri : URI, **args) + host = uri.host + port = uri.port + raise "Invalid URI" if host.nil? || port.nil? + new(host, port, uri.user, uri.password, **args) + end + + private def out_sync + @out.synchronize do + yield + end + @flush.send(true) if @flush.empty? + end + + private def check_size(data) + if data.size > @max_payload + raise "Payload too big" + end + end + + # Publishes a messages to a given subject. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # nc.publish("foo", "Hello!") + # ``` + def publish(subject : String, msg) + # raise ArgumentError.new("Connection closed") if closed? + raise "Bad Subject" if subject.empty? + raise "Connection Closed" if closed? + + data = msg.to_slice + check_size(data) + + @out.synchronize do + @buf.write(PUB_SLICE) + @buf.write("#{subject} #{data.size}".to_slice) + @buf.write(CR_LF_SLICE) + @buf.write(data) + @buf.write(CR_LF_SLICE) + end + @flush.send(true) if @flush.empty? + end + + # Publishes an empty message to a given subject. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # nc.publish("foo") + # ``` + def publish(subject : String) + raise "Bad Subject" if subject.empty? + raise "Connection Closed" if closed? + + @out.synchronize { @buf.write("PUB #{subject} 0\r\n\r\n".to_slice) } + @flush.send(true) if @flush.empty? + end + + # Publishes a messages to a given subject with a reply subject. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # nc.publish_with_reply("foo", "reply", "Hello!") + # ``` + def publish_with_reply(subject, reply : String, msg) + raise "Bad Subject" if subject.empty? + raise "Connection Closed" if closed? + + data = msg.to_slice + check_size(data) + + @out.synchronize do + @buf.write(PUB_SLICE) + @buf.write("#{subject} #{reply} #{data.size}".to_slice) + @buf.write(CR_LF_SLICE) + @buf.write(data) + @buf.write(CR_LF_SLICE) + end + @flush.send(true) if @flush.empty? + end + + # ditto + def publish_with_reply(subject, reply : String, msg) + raise "Bad Subject" if subject.empty? + raise "Connection Closed" if closed? + + @out.synchronize { @buf.write("PUB #{subject} #{reply} 0\r\n\r\n".to_slice) } + @flush.send(true) if @flush.empty? + end + + # Flush will flush the connection to the server. Can specify a *timeout*. + def flush(timeout = 2.second) + ch = Channel(Nil).new + @pongs.push(ch) + @out.synchronize { @buf.write(PING_SLICE) } + flush_outbound + spawn { sleep timeout; ch.close } + ch.receive rescue {raise "Flush Timeout"} + end + + def new_inbox + "#{@resp_sub_prefix}.#{inbox_token}" + end + + # :nodoc: + TOKEN_LENGTH = 8 # Matches Go implementation + + private def inbox_token + rn = @rand.rand(Int64::MAX) + String::Builder.build(TOKEN_LENGTH) do |io| + (0...TOKEN_LENGTH).each do + io << "#{NUID::DIGITS[rn % NUID::BASE]}" + rn /= NUID::BASE + end + end + end + + private def create_resp_subscription + return if @resp_sub_created + @resp_sub_created = true + internal_subscribe("#{@resp_sub_prefix}.*") + end + + protected def handle_resp(msg) + token = msg.subject[@resp_sub_prefix.size + 1..-1] + ch = @resp_map[token] + ch.send(msg) + @resp_map.delete(token) + end + + # Request will send a request to the given subject and wait up to *timeout* for a response. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # answer = nc.request("req", "Help!") + # puts "Received a response '#{answer}'!" + # ``` + def request(subject : String, msg?, timeout = 2.second) + create_resp_subscription unless @resp_sub_created + token = inbox_token + reply = "#{@resp_sub_prefix}.#{token}" + ch = Channel(Msg?).new + @resp_map[token] = ch + publish_with_reply(subject, reply, msg?) + spawn { sleep timeout; ch.close } + begin + msg = ch.receive + rescue + @resp_map.delete(token) + raise "Request Timeout" + end + end + + private def internal_subscribe(subject : String) + sid = @gsid += 1 + @out.synchronize { @buf << "SUB " << subject << " #{sid}\r\n" } + @flush.send(true) if @flush.empty? + InternalSubscription.new(sid, self).tap do |sub| + @subs[sid] = sub + end + end + + # Subscribe to a given subject. Will yield to the callback provided with the message received. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # nc.subscribe("foo") { |msg| puts "Received '#{msg}'" } + # ``` + def subscribe(subject : String, &callback : Msg ->) + sid = @gsid += 1 + @out.synchronize { @buf << "SUB " << subject << " #{sid}\r\n" } + @flush.send(true) if @flush.empty? + Subscription.new(sid, self, callback).tap do |sub| + @subs[sid] = sub + end + end + + # Subscribe to a given subject with the queue group. Will yield to the callback provided with the message received. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # nc.subscribe("foo", "group1") { |msg| puts "Received '#{msg}'" } + # ``` + def subscribe(subject, queue : String, &callback : Msg ->) + sid = @gsid += 1 + @out.synchronize { @buf << "SUB " << subject << " " << queue << " #{sid}\r\n" } + @flush.send(true) if @flush.empty? + Subscription.new(sid, self, callback).tap do |sub| + @subs[sid] = sub + end + end + + def subscribe(subject : String, queue : String | Nil, &callback : Msg ->) + return subscribe(subject, queue, &callback) if queue.is_a?(String) + subscribe(subject, &callback) + end + + protected def unsubscribe(sid) + return if closed? + @out.synchronize { @buf << "UNSUB #{sid}\r\n" } + @flush.send(true) if @flush.empty? + end + + # Close a connection to the NATS server. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # nc.close + # ``` + def close + return if @closed + @closed = true + @out.synchronize do + flush_outbound + @socket.flush + end + @socket.close + @subs.each { |sid, sub| sub.unsubscribe } + # TODO(dlc) - pop any calls in flush. + @close_cb.try do |cb| + spawn cb.call + end + end + + # Setup a callback for when the connection closes. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # nc.on_close { puts "Connection closed!" } + # nc.close + # ``` + def on_close(&callback) + @close_cb = callback + end + + # Setup a callback for an async errors that are received. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # nc.on_error { |e| puts "Received an error #{e}" } + # ``` + def on_error(&callback : String ->) + @err_cb = callback + end + + # :nodoc: + def finalize + close unless closed? + end + + # :nodoc: + INFO = /\AINFO\s+([^\r\n]+)/i + # :nodoc: + MSG = /\AMSG\s+([^\s]+)\s+([^\s]+)\s+(([^\s]+)[^\S\r\n]+)?(\d+)/i + # :nodoc: + PING = /\APING\s*/i + # :nodoc: + PONG = /\APONG\s*/i + # :nodoc: + ERR = /\A-ERR\s+('.+')?/i + # :nodoc: + OK = /\A\+OK\s*/i + + # :nodoc: + CR_LF = "\r\n" + # :nodoc: + CR_LF_SLICE = CR_LF.to_slice + # :nodoc: + PUB_SLICE = "PUB ".to_slice + # :nodoc: + PING_SLICE = "PING\r\n".to_slice + # :nodoc: + PONG_SLICE = "PONG\r\n".to_slice + + # TODO(dlc) - Performance is off from what I expected. Blind read *should* be handled by IO::Buffered. + # Should try manual blind read and hand rolled parser similar to Golang. Also make sure Channels is not slowdown. + private def inbound + until closed? + case data = @socket.gets(CR_LF) + when MSG + bytesize = $5.to_i + sid = $2.to_i + payload = Bytes.new(bytesize) + @socket.read_fully?(payload) || raise "Unexpected EOF" + @socket.gets(CR_LF) + sub = @subs[sid] + sub.send(Msg.new($1, payload, $4?, self)) unless sub.nil? + when PING + @out.synchronize { @buf.write(PONG_SLICE) } + flush_outbound + when PONG + ch = @pongs.pop? + ch.send(nil) unless ch.nil? + when OK + when ERR + # TODO(dlc) - Not default to puts? + puts "NATS: Received an ERR #{$1}" if @err_cb.nil? + @err_cb.try do |cb| + cb.call($1) + end + else + raise "Protocol Error" unless data.to_s.empty? + close + end + end + rescue ex + close + end + + private def flush_outbound + @out.synchronize do + @socket.write(@buf.to_slice) + @socket.flush + @buf.clear + end + end + + private def outbound + until closed? + fs = @flush.receive + break if fs.nil? + flush_outbound + end + end + + private def connect_ok? + auth_required = @server_info["auth_required"].as_bool rescue false + tls_required = @server_info["tls_required"].as_bool rescue false + return unless auth_required || tls_required + # Don't need synchronize yet. + + @socket.write(PING_SLICE) + @socket.flush + case data = @socket.gets(CR_LF) + when PONG + return + when ERR + raise $1 + else + raise "Connection Terminated" + end + end + + private def process_first_info + # TODO(dlc) - timeouts + line = @socket.read_line + + # TODO(dlc) - Could use JSON.mapping here with Info class. + if match = line.match(INFO) + info_json = match.captures.first + @server_info = JSON.parse(info_json.to_s).as_h + unless @server_info[:max_payload]?.nil? + @max_payload = @server_info[:max_payload].as_i + end + else + raise "INFO not valid" + end + + # FIXME(dlc) - client side certs, etc. + tls_required = @server_info["tls_required"].as_bool rescue false + @socket = OpenSSL::SSL::Socket::Client.new(@socket) if tls_required + end + + private def send_connect + cs = { + :verbose => false, + :pedantic => @pedantic, + :lang => LANG, + :version => VERSION, + :protocol => 1, + } + cs[:name] = @name.to_s if @name + + cs[:user] = @user.to_s if @user + cs[:pass] = @pass.to_s if @pass + + @socket << "CONNECT #{cs.to_json}\r\n" + end + end +end diff --git a/src/nats/msg.cr b/src/nats/msg.cr new file mode 100644 index 0000000..22b469e --- /dev/null +++ b/src/nats/msg.cr @@ -0,0 +1,60 @@ +# Copyright 2019 The NATS Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module NATS + # A delivered message from a subscription. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # nc.subscribe("foo") do |msg| + # puts "Received '#{msg}'" + # puts "Subject is #{msg.subject}" + # puts "Reply Subject is #{msg.reply}" + # puts "Raw Data is #{msg.data}" + # end + # ``` + class Msg + getter subject : String + getter reply : String? + getter data : Bytes + + @conn : Connection | Nil + + # :nodoc: + def to_s(io : IO) + io << String.new(data) + end + + # :nodoc: + def initialize(@subject, @data, @reply = nil) + end + + protected def initialize(@subject, @data, @reply = nil, @conn = nil) + end + + # Allows a response to a request message to be easily sent. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # nc.subscribe("req") do |msg| + # msg.respond("ANSWER is 42") + # end + # ``` + def respond(msg) + raise "No reply subject" if @reply.nil? + raise "Not a received message" if @conn.nil? + @conn.try(&.publish(@reply.to_s, msg)) + end + end +end diff --git a/src/nats/nuid.cr b/src/nats/nuid.cr new file mode 100644 index 0000000..8fb383a --- /dev/null +++ b/src/nats/nuid.cr @@ -0,0 +1,72 @@ +# Copyright 2019 The NATS Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "random/secure" + +# Copied and modified from nats-pure repo. +# https://github.com/nats-io/nats-pure.rb/blob/master/lib/nats/nuid.rb + +module NATS + class NUID + DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + BASE = 62 + PREFIX_LENGTH = 12 + SEQ_LENGTH = 10 + TOTAL_LENGTH = PREFIX_LENGTH + SEQ_LENGTH + MAX_SEQ = 839299365868340224_i64 + MIN_INC = 33_i64 + MAX_INC = 333_i64 + INC = MAX_INC - MIN_INC + + def initialize + @pre_bytes = Bytes.new(PREFIX_LENGTH) + @seq = @inc = 0_i64 + reset! + end + + def next + @seq += @inc + if @seq >= MAX_SEQ + reset! + end + l = @seq + + # Do this inline 10 times to avoid even more extra allocs, + # then use string interpolation of everything which works + # faster for doing concat. + s_10 = DIGITS[l % BASE] + # Ugly, but parallel assignment is slightly faster here... + s_09, s_08, s_07, s_06, s_05, s_04, s_03, s_02, s_01 = \ + (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]), (l /= BASE; DIGITS[l % BASE]) + "#{@prefix}#{s_01}#{s_02}#{s_03}#{s_04}#{s_05}#{s_06}#{s_07}#{s_08}#{s_09}#{s_10}" + end + + private def reset! + randomize_prefix! + reset_sequential! + end + + def randomize_prefix! + @prefix = String::Builder.build(PREFIX_LENGTH) do |io| + Random::Secure.random_bytes(@pre_bytes) + @pre_bytes.each { |n| io << "#{DIGITS[n % BASE]}" } + end + end + + private def reset_sequential! + @seq = Random::Secure.rand(MAX_SEQ) + @inc = MIN_INC + Random::Secure.rand(INC) + end + end +end diff --git a/src/nats/subscription.cr b/src/nats/subscription.cr new file mode 100644 index 0000000..c0f15a1 --- /dev/null +++ b/src/nats/subscription.cr @@ -0,0 +1,74 @@ +# Copyright 2019 The NATS Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +module NATS + # A subscription to a given subject and possible queue group. Used to unsubscribe/close. + # + # ``` + # nc = NATS::Connection.new("demo.nats.io") + # sub = nc.subscribe("foo") { |msg| } + # sub.close + # sub.unsubscribe + # ``` + class Subscription + protected def initialize(@sid : Int32, @conn : Connection, @cb : Msg ->) + @msgs = Channel(Msg?).new(128) + spawn process_msgs + end + + # Will unsubscribe from a subscription. + def unsubscribe + unless closed? + @conn.unsubscribe(@sid) + @msgs.close + end + end + + # ditto + def close + unsubscribe + end + + # Determines if the subscription is already closed. + def closed? + return @msgs.closed? + end + + protected def send(msg : Msg) + @msgs.send(msg) unless @msgs.closed? + end + + private def process_msgs + until @conn.closed? + msg = @msgs.receive + break if msg.nil? # FIXME(dlc) - I think nil ok. + @cb.call(msg) + end + rescue # for when msgs channel gets closed. + end + end + + private class InternalSubscription + def initialize(@sid : Int32, @conn : Connection) + end + + def unsubscribe + @conn.unsubscribe(@sid) + end + + def send(msg : Msg) + @conn.handle_resp(msg) + end + end +end