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.
- checksums.yaml +7 -0
- data/lib/wirebridge.rb +284 -0
- 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: []
|