velix-sdk 0.1.0.pre.alpha1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 40da25499d3ae666cd66289c246ee35de7f530b7531cf7a6c4573951dd44b10a
4
+ data.tar.gz: 15670d812a8dc1a27e8eaf53aa34619d93aee1b72c7f0a0936ef25550beb9ce3
5
+ SHA512:
6
+ metadata.gz: 97aa391c0f9fbf9f8d46af5503c48f8fc0571b73deea523f63056f7dec0fe3111a989dcdff421b9a5b5841ef2f8f72292ee3735a3d588041106ffb716c39223d
7
+ data.tar.gz: d3006602956f427a885e59e378e4a93885ce4f3147ea15ac99267797182f256769260bc2e4c25bb718e5adb9298904a6fe497c76a7109a95207b17849f0186d3
data/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ Todas as mudanças notáveis deste projeto são documentadas neste arquivo.
4
+
5
+ O formato segue [Keep a Changelog](https://keepachangelog.com/pt-BR/1.0.0/)
6
+ e este projeto adere a [Semantic Versioning](https://semver.org/lang/pt-BR/).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Fixed
11
+
12
+ - Retry deixou de reenviar cegamente qualquer `5xx`. Requisições não
13
+ idempotentes (`POST`, `PATCH`, `PUT`, `DELETE`) só são retentadas quando o
14
+ status é `429` ou `503` (`RETRYABLE_STATUSES`), casos em que o servidor
15
+ garante que a requisição original não foi processada. Isso elimina o risco
16
+ de duplicar `checkin`/`enroll` em erro `500`.
17
+ - Erros de parse do corpo da resposta deixaram de ser engolidos
18
+ silenciosamente (`JSON.parse rescue {}`). Agora um corpo malformado
19
+ propaga um `Velix::VelixError` estruturado em vez de virar `{}` e esconder
20
+ a mensagem de erro original.
21
+
22
+ ## [0.1.0.pre.alpha1] - pré-release
23
+
24
+ ### Added
25
+
26
+ - Cliente HTTP baseado em `Net::HTTP` (stdlib, zero dependências de runtime).
27
+ - Autenticação via header `x-api-key`.
28
+ - Módulos `checkin`, `persons`, `events`, `tenants`.
29
+ - Retry com backoff exponencial para status retentáveis.
data/README.md ADDED
@@ -0,0 +1,170 @@
1
+ # velix-sdk — Ruby SDK ![version](https://img.shields.io/badge/version-0.1.0--alpha1-orange)
2
+
3
+ > ⚠️ **Alpha / pre-release.** This SDK targets the real API-key-protected surface of the
4
+ > VELIX backend (`/v1/api/*`, see `public-api.yaml` task #593). Only six endpoints exist
5
+ > today; everything else (Velix Time included) is intentionally not implemented. Do not
6
+ > use in production integrations yet.
7
+
8
+ Official Ruby SDK for the VELIX Biometrics platform — facial access control B2B SaaS.
9
+
10
+ ## Requirements
11
+
12
+ - Ruby 3.1+
13
+ - Zero runtime dependencies (uses stdlib `net/http`)
14
+
15
+ ## Installation
16
+
17
+ Add to your `Gemfile`:
18
+
19
+ ```ruby
20
+ gem "velix-sdk", "~> 0.1.0.pre"
21
+ ```
22
+
23
+ Then:
24
+
25
+ ```bash
26
+ bundle install
27
+ ```
28
+
29
+ Or install directly:
30
+
31
+ ```bash
32
+ gem install velix-sdk
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```ruby
38
+ require "velix"
39
+
40
+ client = Velix::Client.new(
41
+ api_url: ENV["VELIX_API_URL"],
42
+ api_key: ENV["VELIX_API_KEY"]
43
+ )
44
+
45
+ result = client.checkin.identify(image_base64: frame_base64)
46
+ puts result.matched ? "GRANTED" : "DENIED"
47
+ ```
48
+
49
+ Auth is sent as `x-api-key: vlx_<hex>` on every request (the API also accepts
50
+ `Authorization: Bearer vlx_<hex>` as an alternative, but the SDK always uses the
51
+ `x-api-key` header).
52
+
53
+ ## Environment Variables
54
+
55
+ | Variable | Required | Description |
56
+ |----------|----------|-------------|
57
+ | `VELIX_API_URL` | Yes | API base URL (`https://api.velixbiometrics.com`) |
58
+ | `VELIX_API_KEY` | Yes | Integrator API key (`vlx_...`) |
59
+
60
+ ## Modules
61
+
62
+ The API-key surface has exactly six real endpoints, one per method below. There is no
63
+ list/update/delete for persons, events, or tenants under `/v1/api/*` — do not expect
64
+ those methods.
65
+
66
+ | Module | Method | Endpoint | Scope |
67
+ |--------|--------|----------|-------|
68
+ | `client.onboarding` | `create()` | `POST /v1/api/onboarding` | `onboarding:write` |
69
+ | `client.checkin` | `identify()` | `POST /v1/api/checkin/identify` | `checkin:write` |
70
+ | `client.lgpd` | `create_deletion_request()` | `POST /v1/api/deletion-request` | `lgpd:write` |
71
+ | `client.me` | `find()` | `GET /v1/api/me/{personId}` | `me:read` |
72
+ | `client.events` | `create_guest()` | `POST /v1/api/events/{id}/guests` | `events:write` |
73
+ | `client.events` | `get_guest()` | `GET /v1/api/events/{id}/guests/{guestId}` | `events:read` |
74
+
75
+ `client.time` exists only as a stub that raises `NotImplementedError` — Velix Time has
76
+ no endpoint under `/v1/api/*` yet (see spec note "Velix Time — COBERTURA PARCIAL").
77
+
78
+ ## Onboarding Module
79
+
80
+ ```ruby
81
+ result = client.onboarding.create(
82
+ name: "João Silva",
83
+ frames: [frame1_base64, frame2_base64, frame3_base64], # min 1, tenant-configured minimum
84
+ email: "joao@company.com",
85
+ external_id: "EMP-001" # upserts by external key when provided
86
+ )
87
+ # result.person_id => "uuid"
88
+ # result.identity_id => "uuid"
89
+ # result.enrolled => true
90
+ # result.frames_processed => 3
91
+ # result.frames_results => [...]
92
+ ```
93
+
94
+ ## Checkin Module
95
+
96
+ ```ruby
97
+ result = client.checkin.identify(
98
+ image_base64: frame_base64,
99
+ top_k: 3,
100
+ liveness: {
101
+ token: challenge_token, # from GET /v1/public/checkin/{tenantSlug}/liveness/challenge
102
+ samples: [{ action: "center", image_base64: sample_base64 }]
103
+ }
104
+ )
105
+ # result.matched => true
106
+ # result.person_id => "uuid"
107
+ # result.quality_score => 0.92
108
+ ```
109
+
110
+ Liveness score is never returned by the API — only `matched`/`quality_score` are
111
+ exposed, by design.
112
+
113
+ ## LGPD Module
114
+
115
+ ```ruby
116
+ result = client.lgpd.create_deletion_request(person_id: "uuid")
117
+ # result.protocol_number => "PROTO-123"
118
+ ```
119
+
120
+ ## Me Module
121
+
122
+ ```ruby
123
+ person = client.me.find("uuid")
124
+ # person.id, person.name, person.email, person.phone, person.photo_url, person.created_at
125
+ ```
126
+
127
+ ## Events Module
128
+
129
+ ```ruby
130
+ guest = client.events.create_guest("event-uuid", name: "Ana", email: "ana@empresa.com")
131
+ # guest.id, guest.event_id, guest.status, guest.category_id
132
+
133
+ guest = client.events.get_guest("event-uuid", "guest-uuid")
134
+ ```
135
+
136
+ ## Error Handling
137
+
138
+ ```ruby
139
+ begin
140
+ result = client.checkin.identify(image_base64: frame)
141
+ rescue Velix::AuthError
142
+ puts "Invalid API key"
143
+ rescue Velix::BiometricError => e
144
+ puts "Face not recognized: #{e.message}"
145
+ rescue Velix::RateLimitError => e
146
+ puts "Rate limit: #{e.message}"
147
+ rescue Velix::VelixError => e
148
+ puts "HTTP #{e.status}: #{e.message}"
149
+ end
150
+ ```
151
+
152
+ ## Running Tests
153
+
154
+ ```bash
155
+ bundle exec rspec # all tests
156
+ bundle exec rspec spec/checkin_spec.rb # single file
157
+ bundle exec rspec --format documentation # verbose
158
+ ```
159
+
160
+ ## Local Development
161
+
162
+ ```bash
163
+ git clone <repo>
164
+ bundle install
165
+ bundle exec rspec
166
+ ```
167
+
168
+ ## Get an API Key
169
+
170
+ Access the dashboard at **velixbiometrics.com** → Settings → API Keys → New Key.
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+
7
+ require_relative "config"
8
+ require_relative "error"
9
+ require_relative "retry"
10
+ require_relative "modules/onboarding"
11
+ require_relative "modules/checkin"
12
+ require_relative "modules/lgpd"
13
+ require_relative "modules/me"
14
+ require_relative "modules/events"
15
+ require_relative "modules/time"
16
+
17
+ module Velix
18
+ class Client
19
+ include Velix::Retry
20
+
21
+ USER_AGENT = "velix-ruby-sdk/0.1.0.pre.alpha1"
22
+
23
+ def initialize(api_url:, api_key:, timeout: 30, max_retries: 3)
24
+ @config = Config.new(api_url: api_url, api_key: api_key,
25
+ timeout: timeout, max_retries: max_retries)
26
+ end
27
+
28
+ def onboarding = @onboarding ||= Modules::Onboarding.new(self)
29
+ def checkin = @checkin ||= Modules::Checkin.new(self)
30
+ def lgpd = @lgpd ||= Modules::Lgpd.new(self)
31
+ def me = @me ||= Modules::Me.new(self)
32
+ def events = @events ||= Modules::Events.new(self)
33
+
34
+ # Velix Time has no endpoint under /v1/api/* yet — see modules/time.rb.
35
+ def time = @time ||= Modules::Time.new(self)
36
+
37
+ def get(path, params = {})
38
+ uri = build_uri(path, params)
39
+ request(Net::HTTP::Get.new(uri))
40
+ end
41
+
42
+ def post(path, body = {})
43
+ uri = build_uri(path)
44
+ req = Net::HTTP::Post.new(uri)
45
+ req.body = body.to_json
46
+ request(req)
47
+ end
48
+
49
+ def put(path, body = {})
50
+ uri = build_uri(path)
51
+ req = Net::HTTP::Put.new(uri)
52
+ req.body = body.to_json
53
+ request(req)
54
+ end
55
+
56
+ def patch(path, body = {})
57
+ uri = build_uri(path)
58
+ req = Net::HTTP::Patch.new(uri)
59
+ req.body = body.to_json
60
+ request(req)
61
+ end
62
+
63
+ def delete(path)
64
+ uri = build_uri(path)
65
+ request(Net::HTTP::Delete.new(uri))
66
+ end
67
+
68
+ private
69
+
70
+ def build_uri(path, params = {})
71
+ uri = URI.join(@config.api_url, path)
72
+ uri.query = URI.encode_www_form(params) unless params.empty?
73
+ uri
74
+ end
75
+
76
+ def request(req)
77
+ req["x-api-key"] = @config.api_key
78
+ req["Content-Type"] = "application/json"
79
+ req["Accept"] = "application/json"
80
+ req["User-Agent"] = USER_AGENT
81
+
82
+ with_retry(max_retries: @config.max_retries, method: req.method) do
83
+ uri = req.uri
84
+ http = Net::HTTP.new(uri.host, uri.port)
85
+ http.use_ssl = uri.scheme == "https"
86
+ http.open_timeout = @config.timeout
87
+ http.read_timeout = @config.timeout
88
+
89
+ response = http.request(req)
90
+ handle_response(response)
91
+ end
92
+ end
93
+
94
+ def handle_response(response)
95
+ body = parse_body(response.body)
96
+ # Envelope de erro real: {"success":false,"error":{"code":"...","message":"..."}}.
97
+ # body["message"] no nível raiz não existe — ficava sempre no fallback
98
+ # genérico, mascarando a mensagem real da API.
99
+ message = body.is_a?(Hash) ? body.dig("error", "message") || body["message"] : nil
100
+
101
+ case response.code.to_i
102
+ when 200, 201, 204
103
+ body.is_a?(Hash) && body.key?("data") ? body["data"] : body
104
+ when 401, 403
105
+ raise AuthError.new(message || "Unauthorized", status: response.code.to_i)
106
+ when 404
107
+ raise NotFoundError.new(message || "Not found", status: 404)
108
+ when 422
109
+ raise BiometricError.new(message || "Unprocessable", status: 422)
110
+ when 429
111
+ raise RateLimitError.new(message || "Rate limit exceeded", status: 429)
112
+ when 500..599
113
+ raise ServerError.new(message || "Server error", status: response.code.to_i)
114
+ else
115
+ raise VelixError.new(message || "Unexpected error", status: response.code.to_i)
116
+ end
117
+ end
118
+
119
+ # Parse errors are never swallowed: a malformed body on an error
120
+ # response must still surface as a structured VelixError instead of
121
+ # silently becoming an empty hash (which hides the real failure and
122
+ # nils out the error message).
123
+ def parse_body(raw)
124
+ return {} if raw.nil? || raw.empty?
125
+
126
+ JSON.parse(raw)
127
+ rescue JSON::ParserError => e
128
+ raise VelixError.new("Failed to parse response body: #{e.message}", status: nil, code: "invalid_json")
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Velix
4
+ Config = Data.define(:api_url, :api_key, :timeout, :max_retries) do
5
+ def initialize(api_url:, api_key:, timeout: 30, max_retries: 3)
6
+ super
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Velix
4
+ class VelixError < StandardError
5
+ attr_reader :status, :code
6
+
7
+ def initialize(message, status: nil, code: nil)
8
+ super(message)
9
+ @status = status
10
+ @code = code
11
+ end
12
+ end
13
+
14
+ class AuthError < VelixError; end
15
+ class NotFoundError < VelixError; end
16
+ class RateLimitError < VelixError; end
17
+ class BiometricError < VelixError; end
18
+ class ServerError < VelixError; end
19
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Velix
4
+ module Modules
5
+ # POST /v1/api/checkin/identify — scope checkin:write
6
+ #
7
+ # Liveness score is never returned by the API by design (security rule) —
8
+ # this endpoint's response only ever reports `matched`, never a raw
9
+ # liveness/confidence score.
10
+ class Checkin
11
+ Result = Data.define(:matched, :person_id, :quality_score, :message)
12
+
13
+ def initialize(client)
14
+ @client = client
15
+ end
16
+
17
+ # image_base64: main frame, required
18
+ # images: optional array of extra base64 frames
19
+ # top_k: optional integer 1..10
20
+ # liveness: optional { token:, samples: [{ action:, image_base64: }] }
21
+ # location: optional { latitude:, longitude:, accuracy: }
22
+ def identify(image_base64:, images: nil, top_k: nil, liveness: nil, location: nil)
23
+ body = { imageBase64: image_base64 }
24
+ body[:images] = images if images
25
+ body[:topK] = top_k if top_k
26
+ body[:liveness] = serialize_liveness(liveness) if liveness
27
+ body[:location] = location if location
28
+
29
+ resp = @client.post("/v1/api/checkin/identify", body)
30
+
31
+ Result.new(
32
+ matched: resp["matched"],
33
+ person_id: resp["person_id"],
34
+ quality_score: resp["quality_score"],
35
+ message: resp["message"]
36
+ )
37
+ end
38
+
39
+ private
40
+
41
+ # Maps idiomatic Ruby keys (token:, samples: [{ action:, image_base64: }])
42
+ # onto the wire's literal camelCase field names (token, samples[].action,
43
+ # samples[].imageBase64) as defined by LivenessBlock/LivenessSample in
44
+ # the spec.
45
+ def serialize_liveness(liveness)
46
+ {
47
+ token: liveness[:token],
48
+ samples: Array(liveness[:samples]).map do |sample|
49
+ { action: sample[:action], imageBase64: sample[:image_base64] || sample[:imageBase64] }
50
+ end
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Velix
4
+ module Modules
5
+ # /v1/api/events/{id}/guests — Velix Events guest endpoints.
6
+ #
7
+ # Only two operations exist in the real API: creating a guest
8
+ # (events:write) and reading one back (events:read). There is no
9
+ # list/find/create/update/delete-event or update-config endpoint under
10
+ # the API-key surface — do not add methods for those without a
11
+ # corresponding spec entry.
12
+ class Events
13
+ Guest = Data.define(:id, :event_id, :name, :email, :status, :category_id)
14
+
15
+ def initialize(client)
16
+ @client = client
17
+ end
18
+
19
+ # cpf, phone, birth_date, category_id, companion_of are optional.
20
+ def create_guest(event_id, name:, email:, cpf: nil, phone: nil, birth_date: nil,
21
+ category_id: nil, companion_of: nil)
22
+ body = { name: name, email: email }
23
+ body[:cpf] = cpf if cpf
24
+ body[:phone] = phone if phone
25
+ body[:birthDate] = birth_date if birth_date
26
+ body[:categoryId] = category_id if category_id
27
+ body[:companionOf] = companion_of if companion_of
28
+
29
+ resp = @client.post("/v1/api/events/#{event_id}/guests", body)
30
+ build_guest(resp)
31
+ end
32
+
33
+ def get_guest(event_id, guest_id)
34
+ resp = @client.get("/v1/api/events/#{event_id}/guests/#{guest_id}")
35
+ build_guest(resp)
36
+ end
37
+
38
+ private
39
+
40
+ def build_guest(resp)
41
+ Guest.new(
42
+ id: resp["id"],
43
+ event_id: resp["eventId"],
44
+ name: resp["name"],
45
+ email: resp["email"],
46
+ status: resp["status"],
47
+ category_id: resp["categoryId"]
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Velix
4
+ module Modules
5
+ # POST /v1/api/deletion-request — scope lgpd:write
6
+ class Lgpd
7
+ Result = Data.define(:protocol_number, :message)
8
+
9
+ def initialize(client)
10
+ @client = client
11
+ end
12
+
13
+ def create_deletion_request(person_id:)
14
+ resp = @client.post("/v1/api/deletion-request", { person_id: person_id })
15
+
16
+ Result.new(protocol_number: resp["protocol_number"], message: resp["message"])
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Velix
4
+ module Modules
5
+ # GET /v1/api/me/{personId} — scope me:read
6
+ class Me
7
+ Result = Data.define(:id, :name, :email, :phone, :photo_url, :created_at)
8
+
9
+ def initialize(client)
10
+ @client = client
11
+ end
12
+
13
+ def find(person_id)
14
+ resp = @client.get("/v1/api/me/#{person_id}")
15
+
16
+ Result.new(
17
+ id: resp["id"],
18
+ name: resp["name"],
19
+ email: resp["email"],
20
+ phone: resp["phone"],
21
+ photo_url: resp["photo_url"],
22
+ created_at: resp["created_at"]
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Velix
4
+ module Modules
5
+ # POST /v1/api/onboarding — scope onboarding:write
6
+ class Onboarding
7
+ Result = Data.define(:person_id, :identity_id, :enrolled, :frames_processed,
8
+ :frames_results, :embedding_id, :message)
9
+
10
+ def initialize(client)
11
+ @client = client
12
+ end
13
+
14
+ # frames: array of base64 jpeg strings (no data URI prefix), min 1
15
+ def create(name:, frames:, email: nil, phone: nil, document: nil, document_type: nil,
16
+ external_id: nil, metadata: nil, role: nil, access_groups: nil)
17
+ body = { name: name, frames: frames }
18
+ body[:email] = email if email
19
+ body[:phone] = phone if phone
20
+ body[:document] = document if document
21
+ body[:document_type] = document_type if document_type
22
+ body[:external_id] = external_id if external_id
23
+ body[:metadata] = metadata if metadata
24
+ body[:role] = role if role
25
+ body[:access_groups] = access_groups if access_groups
26
+
27
+ resp = @client.post("/v1/api/onboarding", body)
28
+
29
+ Result.new(
30
+ person_id: resp["person_id"],
31
+ identity_id: resp["identity_id"],
32
+ enrolled: resp["enrolled"],
33
+ frames_processed: resp["frames_processed"],
34
+ frames_results: resp["frames_results"],
35
+ embedding_id: resp["embedding_id"],
36
+ message: resp["message"]
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Velix
4
+ module Modules
5
+ # Velix Time (ponto/punch) has NO endpoint exposed under the API-key
6
+ # surface (`/v1/api/*`) as of the current public-api.yaml spec. The
7
+ # `time:read`/`time:write` scopes exist on ApplicationApiKey but no
8
+ # controller is mounted for them yet (see spec note "Velix Time —
9
+ # COBERTURA PARCIAL"). Do not implement calls against guessed endpoints.
10
+ #
11
+ # This stub exists only so that any accidental `client.time` call fails
12
+ # loudly instead of silently hitting a nonexistent/wrong endpoint.
13
+ class Time
14
+ def initialize(client)
15
+ @client = client
16
+ end
17
+
18
+ def method_missing(name, *)
19
+ raise NotImplementedError,
20
+ "Velix::Modules::Time##{name} is not implemented — Velix Time has no endpoint " \
21
+ "under /v1/api/* yet (see public-api.yaml, task #616 follow-up)."
22
+ end
23
+
24
+ def respond_to_missing?(*)
25
+ true
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Velix
4
+ module Retry
5
+ RETRYABLE_STATUSES = [429, 503].freeze
6
+ IDEMPOTENT_METHODS = %w[GET].freeze
7
+
8
+ # Retries are only safe for:
9
+ # - idempotent HTTP methods (GET), regardless of status
10
+ # - RETRYABLE_STATUSES (429/503), where the server guarantees the
11
+ # request was not processed, even for non-idempotent methods (POST)
12
+ #
13
+ # Any other 5xx on a non-idempotent method (POST/PATCH/PUT/DELETE) is
14
+ # raised immediately — retrying could duplicate a checkin/enroll.
15
+ def with_retry(max_retries:, method: "GET")
16
+ attempts = 0
17
+ begin
18
+ attempts += 1
19
+ yield
20
+ rescue RateLimitError, ServerError => e
21
+ raise unless retryable?(method, e.status)
22
+ raise if attempts >= max_retries
23
+
24
+ wait = (2**(attempts - 1)) + rand
25
+ sleep(wait)
26
+ retry
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def retryable?(method, status)
33
+ IDEMPOTENT_METHODS.include?(method.to_s.upcase) || RETRYABLE_STATUSES.include?(status)
34
+ end
35
+ end
36
+ end
data/lib/velix.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "velix/client"
4
+
5
+ module Velix
6
+ def self.new(**kwargs)
7
+ Client.new(**kwargs)
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: velix-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre.alpha1
5
+ platform: ruby
6
+ authors:
7
+ - Velix Biometrics
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-07-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Integre controle de acesso biométrico VELIX em aplicações Ruby.
14
+ email:
15
+ - dev@velixbiometrics.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - CHANGELOG.md
21
+ - README.md
22
+ - lib/velix.rb
23
+ - lib/velix/client.rb
24
+ - lib/velix/config.rb
25
+ - lib/velix/error.rb
26
+ - lib/velix/modules/checkin.rb
27
+ - lib/velix/modules/events.rb
28
+ - lib/velix/modules/lgpd.rb
29
+ - lib/velix/modules/me.rb
30
+ - lib/velix/modules/onboarding.rb
31
+ - lib/velix/modules/time.rb
32
+ - lib/velix/retry.rb
33
+ homepage: https://velixbiometrics.com
34
+ licenses:
35
+ - MIT
36
+ metadata:
37
+ homepage_uri: https://velixbiometrics.com
38
+ source_code_uri: https://github.com/VELIX-Biometrics/sdk-velix-ruby
39
+ changelog_uri: https://github.com/VELIX-Biometrics/sdk-velix-ruby/blob/main/CHANGELOG.md
40
+ rubygems_mfa_required: 'true'
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 3.1.0
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.1
55
+ requirements: []
56
+ rubygems_version: 3.0.3.1
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: SDK oficial do VELIX para Ruby
60
+ test_files: []