Class: Lucerna::Identity::Client
- Inherits:
-
Object
- Object
- Lucerna::Identity::Client
- Defined in:
- lib/lucerna/identity/client.rb
Overview
Fire-and-forget delivery of identify calls to People (POST /sdk/v1/people/identify). Mirrors the wire behavior of @lucerna/identity's sync layer, adapted to a server process: a bounded queue drained by one lazy background worker replaces the JS microtask, and flush/close exist because Ruby processes don't drain a microtask queue on exit.
identify validates strictly (bad input is a programming error and fails loud); delivery never raises into the caller — failures report through on_error, and the server's idempotent upsert makes the next identify the retry.
Constant Summary collapse
- DEFAULT_BASE_URL =
"https://api.uselucerna.app"- DEFAULT_REQUEST_TIMEOUT =
5.0- DEFAULT_MAX_QUEUE =
1000
Instance Method Summary collapse
-
#close(timeout: 2) ⇒ Object
Flushes, then stops the worker.
-
#flush(timeout: 2) ⇒ Object
Blocks until everything queued before the call has been attempted, or the timeout elapses.
-
#identify(identity = nil, **kwargs) ⇒ Object
Accepts a Lucerna::Identity, a hash form, or the same keywords as Identity.new.
-
#initialize(api_key:, base_url: nil, request_timeout: nil, on_error: nil, logger: nil, transport: nil, max_queue: DEFAULT_MAX_QUEUE, background: true) ⇒ Client
constructor
A new instance of Client.
Constructor Details
#initialize(api_key:, base_url: nil, request_timeout: nil, on_error: nil, logger: nil, transport: nil, max_queue: DEFAULT_MAX_QUEUE, background: true) ⇒ Client
Returns a new instance of Client.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/lucerna/identity/client.rb', line 28 def initialize(api_key:, base_url: nil, request_timeout: nil, on_error: nil, logger: nil, transport: nil, max_queue: DEFAULT_MAX_QUEUE, background: true) raise ArgumentError, "api_key is required" if api_key.nil? || api_key.to_s.empty? @url = "#{(base_url || DEFAULT_BASE_URL).sub(%r{/+\z}, "")}/sdk/v1/people/identify" @request_timeout = (request_timeout || DEFAULT_REQUEST_TIMEOUT).to_f @reporter = Reporting.reporter(on_error, logger) @transport = transport || HTTP::NetHttpTransport.new @max_queue = max_queue @background = background @headers = { "Authorization" => "Bearer #{api_key}", "Content-Type" => "application/json", "x-lucerna-sdk" => SDK_IDENTITY, }.freeze @queue = Thread::Queue.new @worker = nil @pid = Process.pid @lifecycle_mutex = Mutex.new @last_sent = nil # worker-thread-private after spawn end |
Instance Method Details
#close(timeout: 2) ⇒ Object
Flushes, then stops the worker. The client stays usable — the next identify re-arms it.
88 89 90 91 92 93 94 95 96 |
# File 'lib/lucerna/identity/client.rb', line 88 def close(timeout: 2) drained = flush(timeout: timeout) worker = @lifecycle_mutex.synchronize { @worker } if worker&.alive? @queue << :stop worker.join(1) end drained end |
#flush(timeout: 2) ⇒ Object
Blocks until everything queued before the call has been attempted, or the timeout elapses. Returns true when drained.
78 79 80 81 82 83 84 |
# File 'lib/lucerna/identity/client.rb', line 78 def flush(timeout: 2) return true unless @background && @worker&.alive? ack = Thread::Queue.new @queue << [:flush, ack] !ack.pop(timeout: timeout).nil? end |
#identify(identity = nil, **kwargs) ⇒ Object
Accepts a Lucerna::Identity, a hash form, or the same keywords as Identity.new. Never blocks the caller: past max_queue the event is dropped and reported instead of applying backpressure.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/lucerna/identity/client.rb', line 54 def identify(identity = nil, **kwargs) identity = identity.nil? && !kwargs.empty? ? Identity.new(**kwargs) : Identity.wrap(identity) raise ArgumentError, "identify requires an identity" if identity.nil? payload = JSON.generate(identity.to_payload) if @queue.size >= @max_queue @reporter.call(Error.new("identity queue full — dropped identify for #{identity.user_id}")) return nil end if @background # Fork guard first: in a freshly forked child it clears the # inherited backlog — the enqueue below must survive that. ensure_worker @queue << payload else @queue << payload drain_now # test seam: deliveries run synchronously on the caller end nil end |