diff --git a/README.md b/README.md index d1ffccd..621732c 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ All examples below assume that the gem has been required. require 'nostr' ``` - ### Generating a keypair ```ruby @@ -221,6 +220,13 @@ rake yard:junk # Check the junk in your YARD Documentation rake yardstick_measure # Measure docs in lib/**/*.rb with yardstick ``` +### Type checking + +This gem leverages [RBS](https://github.com/ruby/rbs), a language to describe the structure of Ruby programs. It is +used to provide type checking and autocompletion in your editor. Run `bundle exec typeprof FILENAME` to generate +an RBS definition for the given Ruby file. And validate all definitions using [Steep](https://github.com/soutaro/steep) +with the command `bundle exec steep check`. + ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/wilsonsilva/nostr. diff --git a/Steepfile b/Steepfile new file mode 100644 index 0000000..b018581 --- /dev/null +++ b/Steepfile @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +target :lib do + signature 'sig' + + check 'lib' + + # Core libraries + library 'digest' + library 'securerandom' + + # Gems + library 'json' +end diff --git a/nostr.gemspec b/nostr.gemspec index 403f2fa..4e4ab2b 100644 --- a/nostr.gemspec +++ b/nostr.gemspec @@ -50,12 +50,15 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'puma', '~> 5.6' spec.add_development_dependency 'rack', '~> 3.0' spec.add_development_dependency 'rake', '~> 13.0' + spec.add_development_dependency 'rbs', '~> 2.8' spec.add_development_dependency 'rspec', '~> 3.12' spec.add_development_dependency 'rubocop', '~> 1.42' spec.add_development_dependency 'rubocop-rake', '~> 0.6' spec.add_development_dependency 'rubocop-rspec', '2.16' spec.add_development_dependency 'simplecov', '= 0.17' spec.add_development_dependency 'simplecov-console', '~> 0.9' + spec.add_development_dependency 'steep', '~> 1.3' + spec.add_development_dependency 'typeprof', '~> 0.21' spec.add_development_dependency 'yard', '~> 0.9' spec.add_development_dependency 'yard-junk', '~> 0.0.9' spec.add_development_dependency 'yardstick', '~> 0.9' diff --git a/sig/nostr/client.rbs b/sig/nostr/client.rbs new file mode 100644 index 0000000..1b3e9c4 --- /dev/null +++ b/sig/nostr/client.rbs @@ -0,0 +1,20 @@ +module Nostr + class Client + include EventEmitter + + def initialize: -> void + def connect: (Relay relay) -> Thread + def subscribe: (?subscription_id: String, ?filter: Filter) -> Subscription + def unsubscribe: (String subscription_id) -> untyped + def publish: (Event event) -> untyped + + private + + attr_reader subscriptions: Hash[String, Subscription] + attr_reader parent_to_child_channel: untyped + attr_reader child_to_parent_channel: untyped + + def execute_within_an_em_thread: { -> untyped } -> Thread + def initialize_channels: -> untyped + end +end diff --git a/sig/nostr/client_message_type.rbs b/sig/nostr/client_message_type.rbs new file mode 100644 index 0000000..0a391f9 --- /dev/null +++ b/sig/nostr/client_message_type.rbs @@ -0,0 +1,7 @@ +module Nostr + module ClientMessageType + EVENT: String + REQ: String + CLOSE: String + end +end diff --git a/sig/nostr/event.rbs b/sig/nostr/event.rbs new file mode 100644 index 0000000..b863df6 --- /dev/null +++ b/sig/nostr/event.rbs @@ -0,0 +1,24 @@ +module Nostr + class Event < EventFragment + attr_reader id: String + attr_reader sig: String + + def initialize: (id: String, sig: String, + created_at: Integer, + kind: Integer, + tags: Array[String], + content: String, + ) -> void + + def to_h: -> { + id: String, + pubkey: String, + created_at: Integer, + kind: Integer, + tags: Array[String], + content: String, + sig: String + } + def ==: (Event other) -> bool + end +end diff --git a/sig/nostr/event_fragment.rbs b/sig/nostr/event_fragment.rbs new file mode 100644 index 0000000..1f377d8 --- /dev/null +++ b/sig/nostr/event_fragment.rbs @@ -0,0 +1,12 @@ +module Nostr + class EventFragment + attr_reader pubkey: String + attr_reader created_at: Integer + attr_reader kind: Integer + attr_reader tags: Array[String] + attr_reader content: String + + def initialize: (pubkey: String, kind: Integer, content: String, ?created_at: Integer, ?tags: Array[String]) -> void + def serialize: -> [Integer, String, Integer, Integer, Array[String], String] + end +end diff --git a/sig/nostr/event_kind.rbs b/sig/nostr/event_kind.rbs new file mode 100644 index 0000000..ff12241 --- /dev/null +++ b/sig/nostr/event_kind.rbs @@ -0,0 +1,8 @@ +module Nostr + module EventKind + SET_METADATA: Integer + TEXT_NOTE: Integer + RECOMMEND_SERVER: Integer + CONTACTS: Integer + end +end diff --git a/sig/nostr/filter.rbs b/sig/nostr/filter.rbs new file mode 100644 index 0000000..78a75fd --- /dev/null +++ b/sig/nostr/filter.rbs @@ -0,0 +1,25 @@ +module Nostr + class Filter + attr_reader ids: Array[String] + attr_reader authors: Array[String] + attr_reader kinds: Array[Integer] + attr_reader e: String + attr_reader p: String + attr_reader since: Integer + attr_reader until: Integer + attr_reader limit: Integer + + def initialize: (**untyped) -> void + def to_h: -> { + ids: Array[String], + authors: Array[String], + kinds: Array[Integer], + e: String, + p: String, + since: Integer, + until: Integer, + limit: Integer + } + def ==: (Filter other) -> bool + end +end diff --git a/sig/nostr/key_pair.rbs b/sig/nostr/key_pair.rbs new file mode 100644 index 0000000..5ad86cf --- /dev/null +++ b/sig/nostr/key_pair.rbs @@ -0,0 +1,9 @@ +# Classes +module Nostr + class KeyPair + attr_reader private_key: String + attr_reader public_key: String + + def initialize: (private_key: String, public_key: String) -> void + end +end diff --git a/sig/nostr/keygen.rbs b/sig/nostr/keygen.rbs new file mode 100644 index 0000000..237567c --- /dev/null +++ b/sig/nostr/keygen.rbs @@ -0,0 +1,13 @@ +# Classes +module Nostr + class Keygen + def initialize: -> void + def generate_key_pair: -> KeyPair + def generate_private_key: -> String + def extract_public_key: (String private_key) -> String + + private + + attr_reader group: untyped + end +end diff --git a/sig/nostr/relay.rbs b/sig/nostr/relay.rbs new file mode 100644 index 0000000..29a6b1e --- /dev/null +++ b/sig/nostr/relay.rbs @@ -0,0 +1,9 @@ +# Classes +module Nostr + class Relay + attr_reader url: String + attr_reader name: String + + def initialize: (url: String, name: String) -> void + end +end diff --git a/sig/nostr/subscription.rbs b/sig/nostr/subscription.rbs new file mode 100644 index 0000000..6155c99 --- /dev/null +++ b/sig/nostr/subscription.rbs @@ -0,0 +1,9 @@ +module Nostr + class Subscription + attr_reader id: String + attr_reader filter: Filter + + def initialize: (filter: Filter, ?id: String) -> void + def ==: (Subscription other) -> bool + end +end diff --git a/sig/nostr/user.rbs b/sig/nostr/user.rbs new file mode 100644 index 0000000..7789de1 --- /dev/null +++ b/sig/nostr/user.rbs @@ -0,0 +1,24 @@ +# Classes +module Nostr + class User + attr_reader keypair: KeyPair + + def initialize: (?keypair: KeyPair | nil, ?keygen: Keygen) -> void + def create_event: ( + { + id: String, + pubkey: String, + created_at: Integer, + kind: Integer, + tags: Array[String], + content: String, + created_at: Integer, + sig: String + } + ) -> Event + + private + + def sign: (String event_sha256) -> String + end +end diff --git a/sig/vendor/ecsda/group/secp256k1.rbs b/sig/vendor/ecsda/group/secp256k1.rbs new file mode 100644 index 0000000..74ebc1d --- /dev/null +++ b/sig/vendor/ecsda/group/secp256k1.rbs @@ -0,0 +1,6 @@ +# Added only to satisfy the Steep requirements. Not 100% reliable. +module ECDSA + class Group + Secp256k1: Group + end +end diff --git a/sig/vendor/event_emitter.rbs b/sig/vendor/event_emitter.rbs new file mode 100644 index 0000000..a608a33 --- /dev/null +++ b/sig/vendor/event_emitter.rbs @@ -0,0 +1,9 @@ +# Added only to satisfy the Steep requirements. Not 100% reliable. +module EventEmitter + def add_listener: (untyped `type`, ?{once: true} params) -> Integer + alias on add_listener + + def remove_listener: (untyped id_or_type) -> Array[untyped]? + def emit: (untyped `type`, *untyped data) -> Array[untyped] + def once: (untyped `type`) -> Integer +end diff --git a/sig/vendor/event_machine.rbs b/sig/vendor/event_machine.rbs new file mode 100644 index 0000000..40fb263 --- /dev/null +++ b/sig/vendor/event_machine.rbs @@ -0,0 +1,69 @@ +# Added only to satisfy the Steep requirements. Not 100% reliable. +module EventMachine + ERRNOS: Hash[untyped, untyped] + P: untyped + self.@next_tick_mutex: Thread::Mutex + self.@reactor_running: bool + self.@next_tick_queue: Array[^-> untyped] + self.@tails: Array[nil] + self.@resultqueue: (Array[untyped] | Thread::Queue)? + self.@threadqueue: Thread::Queue? + self.@threadpool: Array[untyped]? + self.@all_threads_spawned: bool + self.@reactor_pid: Integer + self.@conns: Hash[untyped, untyped] + self.@acceptors: Hash[untyped, Array[(Array[untyped] | Integer)?]] + self.@timers: Hash[untyped, Integer | ^-> untyped | false] + self.@wrapped_exception: Exception? + self.@reactor_thread: Thread? + self.@threadpool_size: bot + self.@error_handler: bot + + def self.run: (?untyped blk, ?nil tail) ?{ -> untyped } -> nil + def self.run_block: -> nil + def self.reactor_thread?: -> bool + def self.schedule: (*untyped a) -> nil + def self.fork_reactor: -> Integer? + def self.cleanup_machine: -> Array[untyped] + def self.add_shutdown_hook: -> Array[nil] + def self.add_timer: (*Integer | ^-> untyped args) ?{ -> untyped } -> nil + def self.add_periodic_timer: (*untyped args) -> untyped + def self.cancel_timer: (untyped timer_or_sig) -> false? + def self.stop_event_loop: -> untyped + def self.start_server: (untyped server, ?nil port, ?nil handler, *untyped args) -> untyped + def self.attach_server: (untyped sock, ?nil handler, *untyped args) -> untyped + def self.stop_server: (untyped signature) -> untyped + def self.start_unix_domain_server: (untyped filename, *untyped args) -> untyped + def self.connect: (untyped server, ?nil port, ?nil handler, *untyped args) -> untyped + def self.bind_connect: (nil bind_addr, nil bind_port, untyped server, ?nil port, ?nil handler, *untyped args) -> untyped + def self.watch: (untyped io, ?nil handler, *untyped args) -> untyped + def self.attach: (untyped io, ?nil handler, *untyped args) -> untyped + def self.attach_io: (untyped io, bool watch_mode, ?nil handler, *untyped args) -> untyped + def self.reconnect: (untyped server, untyped port, untyped handler) -> untyped + def self.connect_unix_domain: (untyped socketname, *untyped args) -> untyped + def self.open_datagram_socket: (untyped address, untyped port, ?nil handler, *untyped args) -> untyped + def self.set_quantum: (untyped mills) -> untyped + def self.set_max_timers: (untyped ct) -> untyped + def self.get_max_timers: -> untyped + def self.connection_count: -> untyped + def self.run_deferred_callbacks: -> Integer + def self.defer: (?nil op, ?nil callback, ?nil errback) -> untyped + def self.spawn_threadpool: -> true + def self.defers_finished?: -> bool + def self.next_tick: (?nil pr) { -> nil } -> nil + def self.set_effective_user: (untyped username) -> untyped + def self.set_descriptor_table_size: (?nil n_descriptors) -> untyped + def self.popen: (untyped cmd, ?nil handler, *untyped args) -> untyped + def self.reactor_running?: -> bool + def self.open_keyboard: (?nil handler, *untyped args) -> untyped + def self.watch_file: (untyped filename, ?nil handler, *untyped args) -> untyped + def self.watch_process: (untyped pid, ?nil handler, *untyped args) -> untyped + def self.error_handler: (?nil cb) -> nil + def self.enable_proxy: (untyped from, untyped to, ?Integer bufsize, ?Integer length) -> untyped + def self.disable_proxy: (untyped from) -> untyped + def self.heartbeat_interval: -> untyped + def self.heartbeat_interval=: (untyped time) -> untyped + def self.event_callback: (untyped conn_binding, untyped opcode, untyped data) -> Integer? + def self._open_file_for_writing: (untyped filename, ?nil handler) -> untyped + def self.klass_from_handler: (?untyped klass, ?Integer? handler, *nil args) -> Integer +end diff --git a/sig/vendor/event_machine/channel.rbs b/sig/vendor/event_machine/channel.rbs new file mode 100644 index 0000000..0265325 --- /dev/null +++ b/sig/vendor/event_machine/channel.rbs @@ -0,0 +1,18 @@ +# Added only to satisfy the Steep requirements. Not 100% reliable. +module EventMachine + class Channel + @subs: Hash[untyped, untyped] + @uid: Integer + + def initialize: -> void + def num_subscribers: -> Integer + def subscribe: (*untyped a) ?{ -> untyped } -> Integer + def unsubscribe: (untyped name) -> untyped + def push: (*untyped items) -> untyped + alias << push + def pop: (*untyped a) -> untyped + + private + def gen_id: -> Integer + end +end diff --git a/sig/vendor/schnorr.rbs b/sig/vendor/schnorr.rbs new file mode 100644 index 0000000..fc542ce --- /dev/null +++ b/sig/vendor/schnorr.rbs @@ -0,0 +1,4 @@ +# Added only to satisfy the Steep requirements. Not 100% reliable. +module Schnorr + def self.sign: (String message, String private_key, ?String aux_rand) -> untyped +end