Class: Backlex::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/backlex/client.rb

Overview

The official Ruby client for the backlex API — a thin, typed wrapper over the same REST + SSE surface the TypeScript SDK (@backlex/client) speaks. Three auth modes: server key, workspace app mode (token capture), or cookie session.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(url, api_key: nil, workspace: nil, token: nil, tenant: nil, org: nil, tracing: true) ⇒ Client

Returns a new instance of Client.



20
21
22
23
24
25
26
27
28
# File 'lib/backlex/client.rb', line 20

def initialize(url, api_key: nil, workspace: nil, token: nil, tenant: nil, org: nil, tracing: true)
  @url = url.chomp("/")
  @api_key = api_key
  @workspace = workspace
  @app_token = token
  @tenant = tenant
  @org = org
  @tracing = tracing
end

Instance Attribute Details

#app_tokenObject

Returns the value of attribute app_token.



14
15
16
# File 'lib/backlex/client.rb', line 14

def app_token
  @app_token
end

#orgObject

Act inside a specific organization (slug or id) from here on, so $org.id in permission rules resolves without threading it through every call.



18
19
20
# File 'lib/backlex/client.rb', line 18

def org
  @org
end

#workspaceObject (readonly)

Returns the value of attribute workspace.



13
14
15
# File 'lib/backlex/client.rb', line 13

def workspace
  @workspace
end

Class Method Details

.build_search(query) ⇒ Object

Serialize a ListQuery hash into a URL query string (mirrors buildSearch in index.ts). The filter is compact JSON, percent-encoded exactly once.



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/backlex/client.rb', line 114

def self.build_search(query)
  return "" if query.nil?

  parts = []
  if query[:filter] && !query[:filter].empty?
    parts << "filter=#{URI.encode_www_form_component(JSON.generate(query[:filter]))}"
  end
  # sort/fields may be absent when a hand-built query (e.g. one(id, expand:)) is
  # passed rather than a full builder-produced ListQuery — default to [].
  parts << "sort=#{URI.encode_www_form_component(query[:sort].join(','))}" unless (query[:sort] || []).empty?
  parts << "fields=#{URI.encode_www_form_component(query[:fields].join(','))}" unless (query[:fields] || []).empty?
  unless (query[:expand] || []).empty?
    parts << "expand=#{URI.encode_www_form_component(query[:expand].join(','))}"
  end
  parts << "limit=#{query[:limit]}" unless query[:limit].nil?
  parts << "offset=#{query[:offset]}" unless query[:offset].nil?
  parts << "meta=#{URI.encode_www_form_component(query[:meta])}" if query[:meta]
  parts << "locale=#{URI.encode_www_form_component(query[:locale])}" if query[:locale]
  parts << "q=#{URI.encode_www_form_component(query[:q])}" if query[:q]
  parts.empty? ? "" : "?#{parts.join('&')}"
end

.make_traceparentObject

A W3C traceparent: 00-<32-hex trace id>-<16-hex span id>-01. Mirrors packages/client/src/trace.ts, which is what the API parses. Fresh per request — a span id reused across calls collapses them into one span.



33
34
35
# File 'lib/backlex/client.rb', line 33

def self.make_traceparent
  "00-#{SecureRandom.hex(16)}-#{SecureRandom.hex(8)}-01"
end

Instance Method Details

#authObject



42
43
44
# File 'lib/backlex/client.rb', line 42

def auth
  @auth ||= Auth.new(self)
end

#auth_header(req) ⇒ Object

The one chokepoint every request path goes through (data, storage, realtime) — a header added here reaches every call.



99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/backlex/client.rb', line 99

def auth_header(req)
  if @api_key
    req["Authorization"] = "Bearer #{@api_key}"
  elsif @app_token
    req["Authorization"] = "Bearer #{@app_token}"
  end
  req["X-Backlex-Tenant"] = @tenant if @tenant
  req["X-Backlex-Org"] = @org if @org
  # Without this a call never appears in the admin Traces panel and cannot
  # be stitched to the server spans it triggers.
  req["traceparent"] = self.class.make_traceparent if @tracing
end

#from(slug) ⇒ Object

CRUD handle for a collection.



38
39
40
# File 'lib/backlex/client.rb', line 38

def from(slug)
  Collection.new(self, slug)
end

#get_raw(path) ⇒ Object

Raw byte download (storage). Returns the response body string.

Raises:



86
87
88
89
90
91
92
93
94
95
# File 'lib/backlex/client.rb', line 86

def get_raw(path)
  uri = URI(@url + path)
  req = Net::HTTP::Get.new(uri)
  auth_header(req)
  res = send_request(uri, req)
  code = res.code.to_i
  raise Backlex::Error.new(code, "UNKNOWN", "HTTP #{code}") if code < 200 || code >= 300

  res.body
end

#put_raw(path, body, content_type) ⇒ Object

Raw-body upload (storage). Returns the parsed JSON response.



72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/backlex/client.rb', line 72

def put_raw(path, body, content_type)
  uri = URI(@url + path)
  req = Net::HTTP::Put.new(uri)
  req["Content-Type"] = content_type if content_type
  req.body = body
  auth_header(req)
  res = send_request(uri, req)
  code = res.code.to_i
  raise Backlex::Error.from(code, res.body) if code < 200 || code >= 300

  res.body.nil? || res.body.empty? ? nil : JSON.parse(res.body)
end

#request(method, path, body = nil) ⇒ Object

Raw escape hatch — issues a JSON request with auth headers applied.



57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/backlex/client.rb', line 57

def request(method, path, body = nil)
  uri = URI(@url + path)
  req = build_request(method, uri)
  req["Content-Type"] = "application/json"
  req.body = JSON.generate(body) unless body.nil?
  auth_header(req)
  res = send_request(uri, req)
  code = res.code.to_i
  raise Backlex::Error.from(code, res.body) if code < 200 || code >= 300
  return nil if code == 204 || res.body.nil? || res.body.empty?

  JSON.parse(res.body)
end

#storageObject



46
47
48
# File 'lib/backlex/client.rb', line 46

def storage
  @storage ||= Storage.new(self)
end

#subscribe(channel, on_event, on_error = nil) ⇒ Object

Subscribe to a realtime channel (e.g. "items:posts"). Returns a Subscription; #close unsubscribes. on_error may be nil.



52
53
54
# File 'lib/backlex/client.rb', line 52

def subscribe(channel, on_event, on_error = nil)
  Subscription.new(self, "#{@url}/api/realtime/#{channel}/subscribe", on_event, on_error)
end