wirebridge-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/wirebridge.rb +284 -0
  3. metadata +40 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2131b33eef048d7f81b828595a6f5aa7b4d199b6be87cca3dc915b4dc116fbb6
4
+ data.tar.gz: 63c016c3c22429b6e8d4d7c792400f14a9bac50e598d421a6defd24fbeac2101
5
+ SHA512:
6
+ metadata.gz: 9bbe3f3f9f17808356ae21746fa87d7764fe55bc64614a23abb1df1a2ac6e96b2d35ba58bdedb284da32a408276e6867b118350bc20ebf9472185bb082cf2c2c
7
+ data.tar.gz: 4c2d1fd74a6a68cf1073dceff8cc029d7ab06e950a25a67e5c7949361c0480c7cdeaa1139b250ec353d16a3748951e6a822fb7dcc5998a85d19bb23268ffad66
data/lib/wirebridge.rb ADDED
@@ -0,0 +1,284 @@
1
+ # frozen_string_literal: true
2
+
3
+ # WireBridge Ruby SDK
4
+ # Works with Rails, Sinatra, Rack, or plain Ruby.
5
+ #
6
+ # Usage:
7
+ # bridge = WireBridge::Client.new(
8
+ # service_name: "payment-service",
9
+ # base_url: "http://localhost:3000"
10
+ # )
11
+ #
12
+ # bridge.capability(
13
+ # name: "list payments",
14
+ # handler: "/api/payments",
15
+ # method: "GET",
16
+ # tags: ["payments", "read"],
17
+ # output: {
18
+ # payments: WireBridge.array_of(
19
+ # WireBridge.object_of(id: WireBridge.string, amount: WireBridge.number)
20
+ # )
21
+ # }
22
+ # )
23
+ #
24
+ # bridge.register
25
+
26
+ require "json"
27
+ require "net/http"
28
+ require "uri"
29
+ require "securerandom"
30
+ require "logger"
31
+
32
+ module WireBridge
33
+ VERSION = "0.1.0"
34
+
35
+ # ─── SCHEMA HELPERS ─────────────────────────────────────────────────────────
36
+
37
+ def self.string(required: true, description: nil, example: nil)
38
+ s = { type: "string", required: required }
39
+ s[:description] = description if description
40
+ s[:example] = example if example
41
+ s
42
+ end
43
+
44
+ def self.number(required: true, description: nil, example: nil)
45
+ s = { type: "number", required: required }
46
+ s[:description] = description if description
47
+ s
48
+ end
49
+
50
+ def self.boolean(required: true, description: nil)
51
+ s = { type: "boolean", required: required }
52
+ s[:description] = description if description
53
+ s
54
+ end
55
+
56
+ def self.object_of(properties, required: true, description: nil)
57
+ s = { type: "object", required: required, properties: properties }
58
+ s[:description] = description if description
59
+ s
60
+ end
61
+
62
+ def self.array_of(items, required: true, description: nil)
63
+ s = { type: "array", required: required, items: items }
64
+ s[:description] = description if description
65
+ s
66
+ end
67
+
68
+ def self.optional(schema)
69
+ schema.merge(required: false)
70
+ end
71
+
72
+ # ─── CLIENT ─────────────────────────────────────────────────────────────────
73
+
74
+ class Client
75
+ attr_reader :config, :capabilities
76
+
77
+ def initialize(
78
+ service_name:,
79
+ base_url:,
80
+ bridge_url: "http://localhost:7331",
81
+ service_id: nil,
82
+ version: "1.0.0",
83
+ api_key: nil,
84
+ heartbeat_interval: 30,
85
+ logger: Logger.new($stdout)
86
+ )
87
+ @config = {
88
+ service_name: service_name,
89
+ base_url: base_url,
90
+ bridge_url: bridge_url,
91
+ service_id: service_id || "svc-#{SecureRandom.hex(4)}",
92
+ version: version,
93
+ api_key: api_key || ENV["WIREBRIDGE_ANTHROPIC_KEY"] || ENV["ANTHROPIC_API_KEY"],
94
+ heartbeat_interval: heartbeat_interval
95
+ }
96
+ @capabilities = []
97
+ @registered = false
98
+ @logger = logger
99
+ @heartbeat_thread = nil
100
+ end
101
+
102
+ # Register a capability — chainable.
103
+ #
104
+ # @param name [String] Human-readable capability name
105
+ # @param handler [String] Route path, e.g. "/api/payments"
106
+ # @param method [String] HTTP method (default: "GET")
107
+ # @param output [Hash] Schema describing the response shape
108
+ # @param input [Hash] Schema describing accepted parameters
109
+ # @param tags [Array<String>] Semantic tags for matching
110
+ # @param description [String] Optional description
111
+ def capability(name:, handler:, output:, method: "GET", input: {}, tags: [], description: nil)
112
+ id = "#{@config[:service_id]}.#{handler.gsub(/[^a-z0-9]/, '-')}"
113
+ @capabilities << {
114
+ id: id,
115
+ name: name,
116
+ handler: handler,
117
+ method: method.upcase,
118
+ output: output,
119
+ input: input,
120
+ tags: tags,
121
+ description: description || "",
122
+ stack: "ruby"
123
+ }
124
+ self
125
+ end
126
+
127
+ # Push the manifest to WireBridge and start heartbeat.
128
+ def register(api_key: nil)
129
+ key = api_key || @config[:api_key]
130
+ manifest = build_manifest
131
+
132
+ payload = { manifest: manifest }
133
+ payload[:apiKey] = key if key
134
+
135
+ uri = URI("#{@config[:bridge_url]}/registry/backend")
136
+ http = Net::HTTP.new(uri.host, uri.port)
137
+ http.open_timeout = 5
138
+ http.read_timeout = 10
139
+
140
+ request = Net::HTTP::Post.new(uri.path, "Content-Type" => "application/json")
141
+ request.body = payload.to_json
142
+
143
+ response = http.request(request)
144
+
145
+ if response.code.to_i >= 400
146
+ @logger.error("[WireBridge] Registration failed: #{response.code} #{response.body}")
147
+ return false
148
+ end
149
+
150
+ @registered = true
151
+ @logger.info("[WireBridge] ✓ Registered #{@capabilities.size} capabilities for '#{@config[:service_name]}'")
152
+ start_heartbeat
153
+ true
154
+ rescue => e
155
+ @logger.error("[WireBridge] Registration error: #{e.message}")
156
+ false
157
+ end
158
+
159
+ # Stop the heartbeat thread.
160
+ def stop
161
+ @heartbeat_thread&.kill
162
+ end
163
+
164
+ private
165
+
166
+ def build_manifest
167
+ {
168
+ serviceId: @config[:service_id],
169
+ serviceName: @config[:service_name],
170
+ version: @config[:version],
171
+ baseUrl: @config[:base_url],
172
+ stack: "ruby",
173
+ capabilities: @capabilities,
174
+ registeredAt: Time.now.utc.iso8601
175
+ }
176
+ end
177
+
178
+ def start_heartbeat
179
+ interval = @config[:heartbeat_interval]
180
+ service_id = @config[:service_id]
181
+ bridge_url = @config[:bridge_url]
182
+
183
+ @heartbeat_thread = Thread.new do
184
+ loop do
185
+ sleep interval
186
+ begin
187
+ uri = URI("#{bridge_url}/registry/heartbeat")
188
+ Net::HTTP.post(uri, { serviceId: service_id }.to_json, "Content-Type" => "application/json")
189
+ rescue
190
+ # Heartbeat failures are silent
191
+ end
192
+ end
193
+ end
194
+ @heartbeat_thread.abort_on_exception = false
195
+ end
196
+ end
197
+
198
+ # ─── RAILS INTEGRATION ──────────────────────────────────────────────────────
199
+
200
+ module Rails
201
+ # Call in config/initializers/wirebridge.rb
202
+ #
203
+ # WireBridge::Rails.setup do |bridge|
204
+ # bridge.capability(name: "list users", handler: "/api/users", ...)
205
+ # end
206
+ def self.setup(bridge: nil, **opts, &block)
207
+ @bridge = bridge || Client.new(**opts)
208
+ yield @bridge if block_given?
209
+
210
+ # Register after Rails boots
211
+ if defined?(::Rails::Application)
212
+ ::Rails.application.config.after_initialize do
213
+ @bridge.register
214
+ end
215
+ else
216
+ @bridge.register
217
+ end
218
+
219
+ @bridge
220
+ end
221
+
222
+ def self.bridge
223
+ @bridge
224
+ end
225
+ end
226
+
227
+ # ─── SINATRA INTEGRATION ────────────────────────────────────────────────────
228
+
229
+ module Sinatra
230
+ # Mixin for Sinatra apps
231
+ #
232
+ # class MyApp < Sinatra::Base
233
+ # include WireBridge::Sinatra
234
+ # wirebridge service_name: "my-api", base_url: "http://localhost:4567"
235
+ # end
236
+ def self.included(base)
237
+ base.extend(ClassMethods)
238
+ end
239
+
240
+ module ClassMethods
241
+ def wirebridge(**opts, &block)
242
+ @_bridge = Client.new(**opts)
243
+ yield @_bridge if block_given?
244
+
245
+ configure do
246
+ @_bridge.register
247
+ end
248
+
249
+ @_bridge
250
+ end
251
+
252
+ def bridge
253
+ @_bridge
254
+ end
255
+ end
256
+ end
257
+
258
+ # ─── RACK MIDDLEWARE ────────────────────────────────────────────────────────
259
+
260
+ # Rack middleware — registers capabilities on first request.
261
+ #
262
+ # use WireBridge::Middleware, service_name: "my-api", base_url: "http://localhost:9292" do |b|
263
+ # b.capability(name: "health check", handler: "/health", output: { status: WireBridge.string })
264
+ # end
265
+ class Middleware
266
+ def initialize(app, **opts, &block)
267
+ @app = app
268
+ @bridge = Client.new(**opts)
269
+ yield @bridge if block_given?
270
+ @registered = false
271
+ @mutex = Mutex.new
272
+ end
273
+
274
+ def call(env)
275
+ @mutex.synchronize do
276
+ unless @registered
277
+ @bridge.register
278
+ @registered = true
279
+ end
280
+ end
281
+ @app.call(env)
282
+ end
283
+ end
284
+ end
metadata ADDED
@@ -0,0 +1,40 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wirebridge-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - WireBridge
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Runtime wiring layer SDK for Ruby. Works with Rails, Sinatra, Rack, and
13
+ plain Ruby.
14
+ executables: []
15
+ extensions: []
16
+ extra_rdoc_files: []
17
+ files:
18
+ - lib/wirebridge.rb
19
+ homepage: https://github.com/davistolu/sdk-ruby
20
+ licenses:
21
+ - MIT
22
+ metadata: {}
23
+ rdoc_options: []
24
+ require_paths:
25
+ - lib
26
+ required_ruby_version: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: '3.0'
31
+ required_rubygems_version: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ requirements: []
37
+ rubygems_version: 4.0.10
38
+ specification_version: 4
39
+ summary: WireBridge Ruby SDK — connect any Ruby backend to the WireBridge framework
40
+ test_files: []