k9o 1.0.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.
Potentially problematic release.
This version of k9o might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +38 -0
- data/LICENSE +201 -0
- data/NOTICE +4 -0
- data/README.md +67 -0
- data/k9o.gemspec +41 -0
- data/lib/k9o/ai.rb +28 -0
- data/lib/k9o/audit.rb +29 -0
- data/lib/k9o/barbican.rb +239 -0
- data/lib/k9o/client.rb +59 -0
- data/lib/k9o/compute.rb +67 -0
- data/lib/k9o/data.rb +157 -0
- data/lib/k9o/envelope.rb +64 -0
- data/lib/k9o/functions.rb +51 -0
- data/lib/k9o/gate.rb +64 -0
- data/lib/k9o/http_transport.rb +318 -0
- data/lib/k9o/kerror.rb +52 -0
- data/lib/k9o/kibo.rb +114 -0
- data/lib/k9o/me.rb +160 -0
- data/lib/k9o/migrations.rb +244 -0
- data/lib/k9o/multipart.rb +42 -0
- data/lib/k9o/org.rb +213 -0
- data/lib/k9o/orgs.rb +33 -0
- data/lib/k9o/policies.rb +65 -0
- data/lib/k9o/projects.rb +33 -0
- data/lib/k9o/realtime.rb +80 -0
- data/lib/k9o/region.rb +95 -0
- data/lib/k9o/storage.rb +176 -0
- data/lib/k9o/studio.rb +175 -0
- data/lib/k9o/url_grammar.rb +113 -0
- data/lib/k9o/vault.rb +235 -0
- data/lib/k9o/version.rb +5 -0
- data/lib/k9o.rb +54 -0
- data/sig/k9o/ai.rbs +16 -0
- data/sig/k9o/gate.rbs +11 -0
- data/sig/k9o/migrations.rbs +138 -0
- data/sig/k9o/region.rbs +19 -0
- data/sig/k9o/storage.rbs +41 -0
- metadata +98 -0
data/lib/k9o/client.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module K9o
|
|
4
|
+
# The customer-facing client returned by K9o.create_client.
|
|
5
|
+
class K9oClient
|
|
6
|
+
attr_reader :config, :projects, :gate, :studio, :compute, :realtime, :org,
|
|
7
|
+
:orgs, :migrations, :functions, :policies, :audit, :me, :kibo, :ai, :storage
|
|
8
|
+
|
|
9
|
+
def initialize(config)
|
|
10
|
+
@config = config
|
|
11
|
+
@projects = Projects.new(config)
|
|
12
|
+
@gate = Gate.new(config)
|
|
13
|
+
@studio = Studio.new(config)
|
|
14
|
+
@compute = Compute.new(config)
|
|
15
|
+
@realtime = Realtime.new(config)
|
|
16
|
+
@org = Org.new(config)
|
|
17
|
+
@orgs = Orgs.new(config)
|
|
18
|
+
@migrations = Migrations.new(config)
|
|
19
|
+
@functions = Functions.new(config)
|
|
20
|
+
@policies = Policies.new(config)
|
|
21
|
+
@audit = Audit.new(config)
|
|
22
|
+
@me = Me.new(config)
|
|
23
|
+
@kibo = KiboNamespace.new(config)
|
|
24
|
+
@ai = Ai.new(config)
|
|
25
|
+
@storage = Storage.new(config)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def from(collection)
|
|
29
|
+
FromBuilder.new(@config, collection)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def base_url
|
|
33
|
+
@config.base_url
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Factory — construct a K9oClient bound to the supplied options.
|
|
38
|
+
#
|
|
39
|
+
# Per ch13 §2.1.8 spec sample:
|
|
40
|
+
# k9o = K9o.create_client(
|
|
41
|
+
# api_key: ENV["K9O_API_KEY"],
|
|
42
|
+
# org_slug: "acme",
|
|
43
|
+
# project_slug: "member-portal",
|
|
44
|
+
# )
|
|
45
|
+
#
|
|
46
|
+
# base_url is OPTIONAL. When omitted it
|
|
47
|
+
# defaults to the platform apex https://api.k9o.io; when the host is the
|
|
48
|
+
# bare apex and org_slug is set, the canonical org-subdomain wire root
|
|
49
|
+
# https://<org_slug>.api.k9o.io is composed. Any other host passes through.
|
|
50
|
+
def self.create_client(base_url: nil, api_key: nil, org_slug: nil, project_slug: nil, default_headers: {}, http_client: nil)
|
|
51
|
+
normalised = UrlGrammar.resolve_base_url(base_url, org_slug)
|
|
52
|
+
config = HttpClientConfig.new(
|
|
53
|
+
base_url: normalised,
|
|
54
|
+
api_key: api_key, org_slug: org_slug, project_slug: project_slug,
|
|
55
|
+
default_headers: default_headers, http_client: http_client
|
|
56
|
+
)
|
|
57
|
+
K9oClient.new(config)
|
|
58
|
+
end
|
|
59
|
+
end
|
data/lib/k9o/compute.rb
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module K9o
|
|
4
|
+
# Compute namespace (Compute LLMs registry).
|
|
5
|
+
# k9o.compute.llms.*
|
|
6
|
+
class Compute
|
|
7
|
+
# Curated 9-model closed enum per ch07 §10.1 (extensible v2.x).
|
|
8
|
+
LLM_MODEL_IDS = %w[
|
|
9
|
+
mistral-7b mistral-8x7b llama-3-8b llama-3-70b
|
|
10
|
+
claude-3-5-sonnet gpt-4o gpt-4o-mini
|
|
11
|
+
text-embedding-3-small text-embedding-3-large
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
attr_reader :llms
|
|
15
|
+
|
|
16
|
+
def initialize(config)
|
|
17
|
+
@llms = Llms.new(config)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# k9o.compute.llms.* per ch07 §10.
|
|
21
|
+
class Llms
|
|
22
|
+
def initialize(config)
|
|
23
|
+
@config = config
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def list(modality: nil)
|
|
27
|
+
slug = Studio.require_project_slug(@config, 'compute.llms')
|
|
28
|
+
q = modality ? { 'modality' => modality } : nil
|
|
29
|
+
HttpTransport.request(config: @config, method: 'GET',
|
|
30
|
+
path: UrlGrammar.project_path(slug, 'compute/llms'), query: q)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def read(model_id)
|
|
34
|
+
slug = Studio.require_project_slug(@config, 'compute.llms')
|
|
35
|
+
HttpTransport.request(config: @config, method: 'GET',
|
|
36
|
+
path: UrlGrammar.project_path(slug, "compute/llms/#{enc(model_id)}"))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Synchronous inference per ch07 §10.2 (PROJECT_AUTHOR/ADMIN).
|
|
40
|
+
def invoke(model_id, prompt:, params: nil)
|
|
41
|
+
slug = Studio.require_project_slug(@config, 'compute.llms')
|
|
42
|
+
body = { 'prompt' => prompt }
|
|
43
|
+
body['params'] = params if params
|
|
44
|
+
HttpTransport.request(config: @config, method: 'POST',
|
|
45
|
+
path: UrlGrammar.project_path(slug, "compute/llms/#{enc(model_id)}/invoke"), body: body)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Streaming inference (SSE per-token) per ch07 §10.2. Returns an
|
|
49
|
+
# Enumerator of chunk hashes (ch13 §8 Ruby idiom). Consume with
|
|
50
|
+
# `.each { |chunk| ... }`.
|
|
51
|
+
def invoke_stream(model_id, prompt:, params: nil)
|
|
52
|
+
slug = Studio.require_project_slug(@config, 'compute.llms')
|
|
53
|
+
body = { 'prompt' => prompt }
|
|
54
|
+
body['params'] = params if params
|
|
55
|
+
HttpTransport.stream_sse(config: @config, method: 'POST',
|
|
56
|
+
path: UrlGrammar.project_path(slug, "compute/llms/#{enc(model_id)}/invoke-stream"),
|
|
57
|
+
body: body)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def enc(s)
|
|
63
|
+
::URI.encode_www_form_component(s)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
data/lib/k9o/data.rb
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module K9o
|
|
4
|
+
# Data-plane DSL.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
#
|
|
8
|
+
# response = k9o.from('posts').select.eq('status', 'published').limit(10).execute
|
|
9
|
+
# response.data.each { |row| puts row }
|
|
10
|
+
class FromBuilder
|
|
11
|
+
def initialize(config, collection)
|
|
12
|
+
@config = config
|
|
13
|
+
@collection = collection
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def select
|
|
17
|
+
SelectBuilder.new(@config, @collection)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def insert(record)
|
|
21
|
+
HttpTransport.request(
|
|
22
|
+
config: @config, method: 'POST', path: FromBuilder.collection_path(@config, @collection), body: record
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def update(patch)
|
|
27
|
+
UpdateBuilder.new(@config, @collection, patch)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def delete
|
|
31
|
+
DeleteBuilder.new(@config, @collection)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.collection_path(config, collection)
|
|
35
|
+
raise KError.new(
|
|
36
|
+
code: 'K9O_MISSING_PROJECT_CONTEXT',
|
|
37
|
+
message: 'data-plane operations require project_slug to be bound on the client',
|
|
38
|
+
context: { collection: collection },
|
|
39
|
+
recommendation: 'Pass project_slug: in K9o.create_client(...).'
|
|
40
|
+
) if config.project_slug.nil? || config.project_slug.empty?
|
|
41
|
+
|
|
42
|
+
unless collection.is_a?(String) && collection.match?(/\A[a-z][a-z0-9_-]{0,62}\z/)
|
|
43
|
+
raise KError.new(
|
|
44
|
+
code: 'K9O_INVALID_FIELD_VALUE',
|
|
45
|
+
message: "collection name #{collection.inspect} does not match required format",
|
|
46
|
+
context: { collection: collection }
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
UrlGrammar.project_path(config.project_slug, "data/#{collection}")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Shared filter/pagination state.
|
|
54
|
+
class BaseBuilder
|
|
55
|
+
def initialize(config, collection)
|
|
56
|
+
@config = config
|
|
57
|
+
@collection = collection
|
|
58
|
+
@filters = []
|
|
59
|
+
@limit_val = nil
|
|
60
|
+
@cursor_val = nil
|
|
61
|
+
@count_mode = nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
%i[eq neq gt gte lt lte like ilike is].each do |op|
|
|
65
|
+
define_method(op) do |column, value|
|
|
66
|
+
@filters << [column, op.to_s, value.to_s]
|
|
67
|
+
self
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def in_(column, values)
|
|
72
|
+
@filters << [column, 'in', "(#{values.map(&:to_s).join(',')})"]
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def not_in(column, values)
|
|
77
|
+
@filters << [column, 'notIn', "(#{values.map(&:to_s).join(',')})"]
|
|
78
|
+
self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def filter(column, op, value)
|
|
82
|
+
@filters << [column, op.to_s, value.to_s]
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def limit(n)
|
|
87
|
+
@limit_val = n
|
|
88
|
+
self
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def cursor(opaque)
|
|
92
|
+
@cursor_val = opaque
|
|
93
|
+
self
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def count(mode)
|
|
97
|
+
@count_mode = mode
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
protected
|
|
102
|
+
|
|
103
|
+
def to_query
|
|
104
|
+
q = {}
|
|
105
|
+
@filters.each do |col, op, val|
|
|
106
|
+
q[col] = val.start_with?('(') ? "#{op}.#{val}" : "#{op}.#{val}"
|
|
107
|
+
end
|
|
108
|
+
q['limit'] = @limit_val.to_s if @limit_val
|
|
109
|
+
q['cursor'] = @cursor_val if @cursor_val
|
|
110
|
+
q['count'] = @count_mode if @count_mode
|
|
111
|
+
q
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class SelectBuilder < BaseBuilder
|
|
116
|
+
def execute
|
|
117
|
+
HttpTransport.request(
|
|
118
|
+
config: @config, method: 'GET',
|
|
119
|
+
path: FromBuilder.collection_path(@config, @collection),
|
|
120
|
+
query: to_query
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class UpdateBuilder < BaseBuilder
|
|
126
|
+
def initialize(config, collection, patch)
|
|
127
|
+
super(config, collection)
|
|
128
|
+
@patch = patch
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def execute
|
|
132
|
+
id_filter = @filters.find { |col, op, _| col == 'id' && op == 'eq' }
|
|
133
|
+
raise KError.new(
|
|
134
|
+
code: 'K9O_INVALID_FIELD_VALUE',
|
|
135
|
+
message: 'update() requires .eq("id", ...) filter; row-mass update is not supported at v0.1.0',
|
|
136
|
+
context: { collection: @collection }
|
|
137
|
+
) unless id_filter
|
|
138
|
+
|
|
139
|
+
path = "#{FromBuilder.collection_path(@config, @collection)}/#{id_filter[2]}"
|
|
140
|
+
HttpTransport.request(config: @config, method: 'PATCH', path: path, body: @patch)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
class DeleteBuilder < BaseBuilder
|
|
145
|
+
def execute
|
|
146
|
+
id_filter = @filters.find { |col, op, _| col == 'id' && op == 'eq' }
|
|
147
|
+
raise KError.new(
|
|
148
|
+
code: 'K9O_INVALID_FIELD_VALUE',
|
|
149
|
+
message: 'delete() requires .eq("id", ...) filter; row-mass delete is not supported at v0.1.0',
|
|
150
|
+
context: { collection: @collection }
|
|
151
|
+
) unless id_filter
|
|
152
|
+
|
|
153
|
+
path = "#{FromBuilder.collection_path(@config, @collection)}/#{id_filter[2]}"
|
|
154
|
+
HttpTransport.request(config: @config, method: 'DELETE', path: path)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
data/lib/k9o/envelope.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module K9o
|
|
4
|
+
# Standard ch01 §1.1 envelope returned by every k9o surface on success.
|
|
5
|
+
class Envelope
|
|
6
|
+
attr_reader :data, :meta
|
|
7
|
+
|
|
8
|
+
def initialize(data:, meta:)
|
|
9
|
+
@data = data
|
|
10
|
+
@meta = meta
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.from_wire(wire)
|
|
14
|
+
Envelope.new(
|
|
15
|
+
data: wire['data'],
|
|
16
|
+
meta: Meta.from_wire(wire['meta'] || {})
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Meta accessor sugar per ch13 §5.
|
|
22
|
+
class Meta
|
|
23
|
+
attr_reader :cursor, :subscription, :idempotency, :warnings, :cost, :timing, :context
|
|
24
|
+
|
|
25
|
+
def initialize(cursor: nil, subscription: nil, idempotency: nil, warnings: [], cost: {}, timing: nil, context: nil)
|
|
26
|
+
@cursor = cursor
|
|
27
|
+
@subscription = subscription
|
|
28
|
+
@idempotency = idempotency
|
|
29
|
+
@warnings = warnings
|
|
30
|
+
@cost = cost
|
|
31
|
+
@timing = timing
|
|
32
|
+
@context = context
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.from_wire(wire)
|
|
36
|
+
cursor = wire['cursor'] ? MetaCursor.new(next_: wire['cursor']['next'], total_count: wire['cursor']['totalCount']) : nil
|
|
37
|
+
sub = wire['subscription'] ? MetaSubscription.new(is_initial: !!wire['subscription']['isInitial']) : nil
|
|
38
|
+
idem = wire['idempotency'] ? MetaIdempotency.new(replayed: !!wire['idempotency']['replayed']) : nil
|
|
39
|
+
warnings = (wire['warnings'] || []).map { |w| MetaWarning.new(code: w['code'] || '', message: w['message'] || '') }
|
|
40
|
+
timing = wire['timing'] ? MetaTiming.new(total_ms: wire['timing']['totalMs'].to_i) : nil
|
|
41
|
+
context = wire['context'] && wire['context']['requestId'] ? MetaContext.new(request_id: wire['context']['requestId']) : nil
|
|
42
|
+
Meta.new(
|
|
43
|
+
cursor: cursor, subscription: sub, idempotency: idem,
|
|
44
|
+
warnings: warnings, cost: wire['cost'] || {},
|
|
45
|
+
timing: timing, context: context
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class MetaCursor
|
|
51
|
+
attr_reader :next, :total_count
|
|
52
|
+
|
|
53
|
+
def initialize(next_:, total_count:)
|
|
54
|
+
@next = next_
|
|
55
|
+
@total_count = total_count
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
MetaSubscription = Struct.new(:is_initial, keyword_init: true)
|
|
60
|
+
MetaIdempotency = Struct.new(:replayed, keyword_init: true)
|
|
61
|
+
MetaWarning = Struct.new(:code, :message, keyword_init: true)
|
|
62
|
+
MetaTiming = Struct.new(:total_ms, keyword_init: true)
|
|
63
|
+
MetaContext = Struct.new(:request_id, keyword_init: true)
|
|
64
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module K9o
|
|
4
|
+
# Function lifecycle namespace — k9o.functions.*.
|
|
5
|
+
class Functions
|
|
6
|
+
def initialize(config)
|
|
7
|
+
@config = config
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def deploy(name:, runtime:, code:, tier: nil, config: nil)
|
|
11
|
+
slug = require_project_slug
|
|
12
|
+
body = { 'name' => name, 'runtime' => runtime, 'code' => code }
|
|
13
|
+
body['tier'] = tier if tier
|
|
14
|
+
body['config'] = config if config
|
|
15
|
+
HttpTransport.request(config: @config, method: 'POST', path: UrlGrammar.project_path(slug, 'functions'), body: body)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def list(status: nil)
|
|
19
|
+
slug = require_project_slug
|
|
20
|
+
q = status ? { 'status' => status } : nil
|
|
21
|
+
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.project_path(slug, 'functions'), query: q)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def get(name)
|
|
25
|
+
slug = require_project_slug
|
|
26
|
+
HttpTransport.request(config: @config, method: 'GET', path: UrlGrammar.project_path(slug, "functions/#{enc(name)}"))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def delete(name)
|
|
30
|
+
slug = require_project_slug
|
|
31
|
+
HttpTransport.request(config: @config, method: 'DELETE', path: UrlGrammar.project_path(slug, "functions/#{enc(name)}"))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Invoke a function; payload passed to the runtime verbatim.
|
|
35
|
+
def invoke(name, payload)
|
|
36
|
+
slug = require_project_slug
|
|
37
|
+
HttpTransport.request(config: @config, method: 'POST',
|
|
38
|
+
path: UrlGrammar.project_path(slug, "functions/#{enc(name)}/invoke"), body: payload)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def require_project_slug
|
|
44
|
+
Studio.require_project_slug(@config, 'functions')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def enc(s)
|
|
48
|
+
::URI.encode_www_form_component(s)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/k9o/gate.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module K9o
|
|
4
|
+
# Gate (Identity-Aware Kernel) namespace. Project-scoped.
|
|
5
|
+
class Gate
|
|
6
|
+
def initialize(config)
|
|
7
|
+
@config = config
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def sign_up(email:, password:, display_name: nil)
|
|
11
|
+
body = { 'email' => email, 'password' => password }
|
|
12
|
+
body['displayName'] = display_name if display_name
|
|
13
|
+
HttpTransport.request(config: @config, method: 'POST', path: gate_path('signup'), body: body)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def sign_in(email:, password:)
|
|
17
|
+
HttpTransport.request(
|
|
18
|
+
config: @config, method: 'POST', path: gate_path('signin'),
|
|
19
|
+
body: { 'email' => email, 'password' => password }
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def sign_out(refresh_token: nil)
|
|
24
|
+
body = refresh_token ? { 'refreshToken' => refresh_token } : {}
|
|
25
|
+
HttpTransport.request(config: @config, method: 'POST', path: gate_path('signout'), body: body)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def whoami
|
|
29
|
+
HttpTransport.request(config: @config, method: 'GET', path: gate_path('whoami'))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def refresh_token(refresh_token:)
|
|
33
|
+
HttpTransport.request(
|
|
34
|
+
config: @config, method: 'POST', path: gate_path('refresh'),
|
|
35
|
+
body: { 'refreshToken' => refresh_token }
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def verify_session
|
|
40
|
+
HttpTransport.request(config: @config, method: 'GET', path: gate_path('verify'))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The project's hosted sign-in URL and WebAuthn rpId:
|
|
44
|
+
# GET /@<slug>/gate/hosted. Unauthenticated and cacheable; no credential is
|
|
45
|
+
# sent even when an api_key is configured. Returns an Envelope whose `data`
|
|
46
|
+
# is { 'signInUrl' => String, 'rpId' => String }. Take both values as given:
|
|
47
|
+
# an unknown org, a deleted project and a non-Gate project all answer the
|
|
48
|
+
# same 404 K9O_PROJECT_NOT_FOUND.
|
|
49
|
+
def hosted
|
|
50
|
+
HttpTransport.request(config: @config, method: 'GET', path: gate_path('hosted'), authenticated: false)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def gate_path(action)
|
|
56
|
+
raise KError.new(
|
|
57
|
+
code: 'K9O_MISSING_PROJECT_CONTEXT',
|
|
58
|
+
message: 'k9o.gate operations require project_slug bound on the client',
|
|
59
|
+
context: {}
|
|
60
|
+
) if @config.project_slug.nil? || @config.project_slug.empty?
|
|
61
|
+
UrlGrammar.project_path(@config.project_slug, "gate/#{action}")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|