volcano-sdk 0.32.0 → 0.32.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 551cf1235bb42130cc2c2689af7b4a68bad7582d2df5dc8345df72ee8d061925
4
- data.tar.gz: 579af3b2c9301b7b22662e31706752c4d4098438d7ec3c25b6bb8029d17d6f4f
3
+ metadata.gz: ea2aa2e1bb816dd0f4feef5c80d3dab616b24d56b54e81394fb63d5774bddae0
4
+ data.tar.gz: 7ce5d0819d1a808fb4ee54c63c830a2a5173b0d15449174a007ac4b03236f0ac
5
5
  SHA512:
6
- metadata.gz: f3e37060a57939cf40d621880d6f9490af9912934b426d85485a37bd5ece0398d3a9adf0fc7034b211a21ef8b663fe6e2fd2aca6d4b7520cdfcc7373572b0d5f
7
- data.tar.gz: f1d77ddbca839dfb7944e83cb025096dad93dd1fe846d13346e9825ec3280fede17d984b5ae14aed330ce4caa0f7a7eb0289107f985f089379da68a04095dce2
6
+ metadata.gz: '037199a369e46cb09775fb9e85b1396ff373d3a39b88411670ccae2b9a3494985755016b1af78f41d84cbe674ff5703c3f1cef4c49b7c8f05a3e9e7ba46b38ed'
7
+ data.tar.gz: d178936e2018bcad91947371b2ee56878ef5f06f42df97e5a273035ae9395a2219062efd80bad18c2d8b4f13abd5eb8eb4908d17d8e46561faa1f4a36ac77471
data/README.md CHANGED
@@ -505,26 +505,32 @@ handle = client.durable.start(
505
505
  execution_name: "order-9"
506
506
  )
507
507
 
508
- execution = client.durable.get(project_id, "charge-order", handle.id)
508
+ owner_client = Volcano::Client.new(
509
+ api_url: "https://api.volcano.dev",
510
+ anon_key: ENV.fetch("VOLCANO_ANON_KEY"),
511
+ access_token: ENV.fetch("VOLCANO_PLATFORM_TOKEN")
512
+ )
513
+ execution = owner_client.durable.get(project_id, "charge-order", handle.id)
509
514
  puts [execution.status, execution.terminal?, execution.result]
510
515
 
511
- page = client.durable.list(project_id, "charge-order", status: "running")
516
+ page = owner_client.durable.list(project_id, "charge-order", status: "running")
512
517
  puts [page.total, page.has_more]
513
518
 
514
- client.durable.stop(project_id, "charge-order", handle.id)
519
+ owner_client.durable.stop(project_id, "charge-order", handle.id)
515
520
  ```
516
521
 
517
522
  `start` takes a durable function's name or its id and returns a handle, never a
518
- result: an execution can run for hours, so its result is read back with `get`.
523
+ result: an execution can run for up to 366 days, so its result is read back with `get`.
519
524
  It uses the same credential `invoke` does — the active user session, then a
520
525
  configured service key, then the anonymous key — and is the only durable
521
526
  operation an application credential may perform. An `execution_name` makes the
522
527
  start idempotent: starting again under the same name returns the execution that
523
528
  already exists rather than beginning a second one.
524
529
 
525
- `get`, `list`, and `stop` are owner-scoped and require a platform token or a
526
- configured service key, because an execution is addressed by its id alone. An
527
- auth-user session from sign-in is not accepted. `get` carries the deeply
530
+ `get`, `list`, and `stop` are owner-scoped and require the project owner's
531
+ platform user token, because an execution is addressed by its id alone.
532
+ Auth-user sessions, anonymous keys, service keys, and project access tokens are
533
+ not accepted. `get` carries the deeply
528
534
  frozen `result` once the execution has succeeded, and `error` when it failed;
529
535
  `result_expired` separates a result the platform has discarded from a function
530
536
  that returned nothing. `terminal?` reports whether the execution has stopped
@@ -1,33 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Ruby SDK runtime and immutable database builders.
3
4
  module Volcano
4
- # Captures caller-owned query values before storing them in immutable builders.
5
- module ImmutableQueryValue
6
- module_function
7
-
8
- def capture(value)
9
- case value
10
- when String then value.dup.freeze
11
- when Array then value.map { |item| capture(item) }.freeze
12
- when Hash then capture_hash(value)
13
- else value
14
- end
15
- end
16
-
17
- def capture_hash(value)
18
- value.to_h { |key, item| [capture(key), capture(item)] }.freeze
19
- end
20
- end
21
- private_constant :ImmutableQueryValue
22
-
23
5
  QueryContext = Data.define(:client, :transport, :database_name)
24
6
  private_constant :QueryContext
25
7
  QueryState = Data.define(:columns, :filters, :order, :limit, :offset)
26
8
  private_constant :QueryState
9
+ # @type var empty_columns: Array[String]
10
+ empty_columns = []
11
+ empty_columns.freeze
12
+ # @type var empty_conditions: Array[query_condition]
13
+ empty_conditions = []
14
+ empty_conditions.freeze
27
15
  EMPTY_QUERY_STATE = QueryState.new(
28
- columns: [].freeze,
29
- filters: [].freeze,
30
- order: [].freeze,
16
+ columns: empty_columns,
17
+ filters: empty_conditions,
18
+ order: empty_conditions,
31
19
  limit: nil,
32
20
  offset: nil
33
21
  )
@@ -35,6 +23,7 @@ module Volcano
35
23
 
36
24
  # Adds the shared immutable filter vocabulary to database builders.
37
25
  module FilterMethods
26
+ # @dynamic copy
38
27
  def eq(column, value) = add_filter(column, 'eq', value)
39
28
  def neq(column, value) = add_filter(column, 'neq', value)
40
29
  def gt(column, value) = add_filter(column, 'gt', value)
@@ -50,9 +39,9 @@ module Volcano
50
39
 
51
40
  def add_filter(column, operator, value)
52
41
  condition = {
53
- 'column' => ImmutableQueryValue.capture(column),
42
+ 'column' => ImmutableRequestValue.capture(column),
54
43
  'operator' => operator,
55
- 'value' => ImmutableQueryValue.capture(value)
44
+ 'value' => ImmutableRequestValue.capture(value)
56
45
  }.freeze
57
46
  copy(filters: [*@filters, condition])
58
47
  end
@@ -62,7 +51,7 @@ module Volcano
62
51
  # Creates immutable queries scoped to one project database.
63
52
  class Database
64
53
  def initialize(client, transport, name)
65
- database_name = ImmutableQueryValue.capture(name)
54
+ database_name = ImmutableRequestValue.capture(name)
66
55
  @context = QueryContext.new(client:, transport:, database_name:)
67
56
  end
68
57
 
@@ -77,7 +66,7 @@ module Volcano
77
66
 
78
67
  def initialize(context, table, state: EMPTY_QUERY_STATE)
79
68
  @context = context
80
- @table = ImmutableQueryValue.capture(table)
69
+ @table = ImmutableRequestValue.capture(table)
81
70
  @columns = state.columns
82
71
  @filters = state.filters
83
72
  @order = state.order
@@ -87,13 +76,13 @@ module Volcano
87
76
  end
88
77
 
89
78
  def select(*columns)
90
- copy(columns: ImmutableQueryValue.capture(columns))
79
+ copy(columns: ImmutableRequestValue.capture(columns))
91
80
  end
92
81
 
93
- def insert(values) = InsertBuilder.new(@context, @table, ImmutableQueryValue.capture(values))
82
+ def insert(values) = InsertBuilder.new(@context, @table, ImmutableRequestValue.capture(values))
94
83
 
95
84
  def update(values)
96
- UpdateBuilder.new(@context, @table, ImmutableQueryValue.capture(values), filters: @filters)
85
+ UpdateBuilder.new(@context, @table, ImmutableRequestValue.capture(values), filters: @filters)
97
86
  end
98
87
 
99
88
  def delete
@@ -101,7 +90,7 @@ module Volcano
101
90
  end
102
91
 
103
92
  def order(column, ascending: true)
104
- clause = { 'column' => ImmutableQueryValue.capture(column), 'ascending' => ascending }.freeze
93
+ clause = { 'column' => ImmutableRequestValue.capture(column), 'ascending' => ascending }.freeze
105
94
  copy(order: [*@order, clause])
106
95
  end
107
96
 
@@ -123,7 +112,7 @@ module Volcano
123
112
  )
124
113
  end
125
114
  end
126
- Transport.body(response, 200).fetch('data')
115
+ Transport.json_rows(Transport.body(response, 200))
127
116
  end
128
117
 
129
118
  private
@@ -149,7 +138,9 @@ module Volcano
149
138
  end
150
139
 
151
140
  def query_body
152
- { 'table' => @table }.tap do |body|
141
+ # @type var body: sdk_payload
142
+ body = { 'table' => @table }
143
+ body.tap do |body|
153
144
  add_query_selection(body)
154
145
  body['limit'] = @limit unless @limit.nil?
155
146
  body['offset'] = @offset unless @offset.nil?
@@ -184,7 +175,7 @@ module Volcano
184
175
  )
185
176
  end
186
177
  end
187
- Transport.body(response, 200).fetch('data')
178
+ Transport.json_rows(Transport.body(response, 200))
188
179
  end
189
180
  end
190
181
 
@@ -192,7 +183,7 @@ module Volcano
192
183
  class UpdateBuilder
193
184
  include FilterMethods
194
185
 
195
- def initialize(context, table, values, filters: [].freeze)
186
+ def initialize(context, table, values, filters: EMPTY_QUERY_STATE.filters)
196
187
  @context = context
197
188
  @table = table
198
189
  @values = values
@@ -210,7 +201,7 @@ module Volcano
210
201
  )
211
202
  end
212
203
  end
213
- Transport.body(response, 200).fetch('data')
204
+ Transport.json_rows(Transport.body(response, 200))
214
205
  end
215
206
 
216
207
  private
@@ -224,7 +215,7 @@ module Volcano
224
215
  class DeleteBuilder
225
216
  include FilterMethods
226
217
 
227
- def initialize(context, table, filters: [].freeze)
218
+ def initialize(context, table, filters: EMPTY_QUERY_STATE.filters)
228
219
  @context = context
229
220
  @table = table
230
221
  @filters = filters
@@ -241,7 +232,7 @@ module Volcano
241
232
  )
242
233
  end
243
234
  end
244
- Transport.body(response, 200).fetch('data')
235
+ Transport.json_rows(Transport.body(response, 200))
245
236
  end
246
237
 
247
238
  private
@@ -40,10 +40,10 @@ module Volcano
40
40
 
41
41
  # Reads an execution, including its result once it has succeeded.
42
42
  #
43
- # Owner-scoped: an execution is addressed by its id alone and an anon key
44
- # is held by everyone who loads the page, so this takes the project id and
45
- # a platform token or service key. An auth-user session from sign-in is
46
- # not enough. Poll it from a backend, not a browser.
43
+ # Owner-scoped: an execution is addressed by its id alone, so this takes
44
+ # the project id and the project owner's platform user token. Auth-user
45
+ # sessions, anon keys, service keys, and project access tokens are not
46
+ # accepted. Poll it from a backend, not a browser.
47
47
  def get(project_id, function_name, execution_id)
48
48
  project, function_id, execution = execution_path(project_id, function_name, execution_id)
49
49
  response = Transport.invoke do
@@ -100,7 +100,7 @@ module Volcano
100
100
  # An empty path segment would address the collection instead of the
101
101
  # execution, which is a different request rather than a failed one.
102
102
  def identifier(value, field)
103
- raise ArgumentError, "#{field} must be a non-empty String" unless present_string?(value)
103
+ raise ArgumentError, "#{field} must be a non-empty String" unless value.is_a?(String) && present_string?(value)
104
104
 
105
105
  value.strip.freeze
106
106
  end
@@ -128,7 +128,7 @@ module Volcano
128
128
  end
129
129
 
130
130
  def validate_page_limit(limit)
131
- return if limit.nil? || (positive_integer?(limit) && limit <= MAX_PAGE_SIZE)
131
+ return if limit.nil? || (limit.is_a?(Integer) && positive_integer?(limit) && limit <= MAX_PAGE_SIZE)
132
132
 
133
133
  raise ArgumentError, "limit must be an Integer between 1 and #{MAX_PAGE_SIZE}"
134
134
  end
@@ -15,9 +15,16 @@ module Volcano
15
15
  private_constant :DURABLE_TERMINAL_STATUSES
16
16
 
17
17
  # Why a failed or timed-out execution ended.
18
- DurableExecutionError = Data.define(:type, :message) do
18
+ DurableExecutionError = Data.define(:type, :message)
19
+
20
+ # Reopens the generated record for checked initialization.
21
+ class DurableExecutionError
22
+ # @dynamic type, message, members, with, to_h, deconstruct, deconstruct_keys
23
+ # @dynamic self.[], self.members
19
24
  def initialize(type: nil, message: nil)
20
- super(type: type&.dup&.freeze, message: message&.dup&.freeze)
25
+ type = type&.dup&.freeze
26
+ message = message&.dup&.freeze
27
+ super
21
28
  end
22
29
  end
23
30
 
@@ -27,7 +34,13 @@ module Volcano
27
34
  # once its retention lapses. That is not the same as a function that returned
28
35
  # nothing, so read `result_expired` before concluding anything from a missing
29
36
  # result.
30
- DurableExecution = Data.define(*DURABLE_EXECUTION_ATTRIBUTES) do
37
+ DurableExecution = Data.define(*DURABLE_EXECUTION_ATTRIBUTES)
38
+
39
+ # Reopens the generated record for checked initialization.
40
+ class DurableExecution
41
+ # @dynamic id, function_id, name, status, region, created_at, result, result_expired
42
+ # @dynamic error, completed_at, members, with, to_h, deconstruct, deconstruct_keys
43
+ # @dynamic self.[], self.members
31
44
  def initialize(**attributes)
32
45
  unknown = attributes.keys - DURABLE_EXECUTION_ATTRIBUTES
33
46
  raise ArgumentError, "unknown keywords: #{unknown.join(', ')}" unless unknown.empty?
@@ -38,7 +51,8 @@ module Volcano
38
51
  values = DURABLE_EXECUTION_ATTRIBUTES.to_h do |name|
39
52
  [name, immutable_value(attributes[name])]
40
53
  end
41
- super(**values)
54
+ attributes = values
55
+ super
42
56
  end
43
57
 
44
58
  # Reports whether the execution has stopped changing.
@@ -64,12 +78,15 @@ module Volcano
64
78
  end
65
79
 
66
80
  # One page of a durable function's executions, most recent first.
67
- DurableExecutionPage = Data.define(:executions, :page, :limit, :total, :has_more) do
81
+ DurableExecutionPage = Data.define(:executions, :page, :limit, :total, :has_more)
82
+
83
+ # Reopens the generated record for checked initialization.
84
+ class DurableExecutionPage
85
+ # @dynamic executions, page, limit, total, has_more, members, with, to_h
86
+ # @dynamic deconstruct, deconstruct_keys, self.[], self.members
68
87
  def initialize(executions:, page:, limit:, total:, has_more:)
69
- super(
70
- executions: executions.to_a.dup.freeze,
71
- page: page, limit: limit, total: total, has_more: has_more
72
- )
88
+ executions = executions.to_a.dup.freeze
89
+ super
73
90
  end
74
91
  end
75
92
 
@@ -77,18 +94,17 @@ module Volcano
77
94
  module DurableResponses
78
95
  INCOMPLETE_EXECUTION = 'Expected a complete durable execution'
79
96
  INCOMPLETE_PAGE = 'Expected a complete durable execution page'
80
- EXECUTION_FIELDS = %w[id function_id name status region].freeze
81
- private_constant :INCOMPLETE_EXECUTION, :INCOMPLETE_PAGE, :EXECUTION_FIELDS
97
+ private_constant :INCOMPLETE_EXECUTION, :INCOMPLETE_PAGE
82
98
 
83
99
  private
84
100
 
85
101
  def durable_execution(payload)
86
102
  values = execution_payload(payload)
87
103
  DurableExecution.new(
88
- id: values.fetch('id'), function_id: values.fetch('function_id'),
89
- name: values.fetch('name'), status: values.fetch('status'),
90
- region: values.fetch('region'), created_at: parse_time(values.fetch('created_at')),
91
- result: values['result'], result_expired: values['result_expired'],
104
+ id: execution_text(values, 'id'), function_id: execution_text(values, 'function_id'),
105
+ name: execution_text(values, 'name'), status: execution_text(values, 'status'),
106
+ region: execution_text(values, 'region'), created_at: execution_time(values),
107
+ result: execution_result(values['result']), result_expired: execution_expired(values['result_expired']),
92
108
  error: execution_error(values['error']), completed_at: parse_time(values['completed_at'])
93
109
  )
94
110
  end
@@ -96,12 +112,36 @@ module Volcano
96
112
  def execution_payload(payload)
97
113
  raise TypeError, INCOMPLETE_EXECUTION unless payload.is_a?(Hash)
98
114
 
99
- complete = EXECUTION_FIELDS.all? { |field| present_string?(payload[field]) }
100
- raise TypeError, INCOMPLETE_EXECUTION unless complete && payload['created_at']
101
-
102
115
  payload
103
116
  end
104
117
 
118
+ def execution_text(values, field)
119
+ value = values[field]
120
+ raise TypeError, INCOMPLETE_EXECUTION unless value.is_a?(String) && !value.strip.empty?
121
+
122
+ value
123
+ end
124
+
125
+ def execution_time(values)
126
+ case (value = values['created_at'])
127
+ when String then Time.iso8601(value)
128
+ when Time then value
129
+ else raise TypeError, INCOMPLETE_EXECUTION
130
+ end
131
+ end
132
+
133
+ def execution_expired(value)
134
+ return value if value.nil? || value.is_a?(TrueClass) || value.is_a?(FalseClass)
135
+
136
+ raise TypeError, INCOMPLETE_EXECUTION
137
+ end
138
+
139
+ def execution_result(value)
140
+ Transport.json_value(value)
141
+ rescue TypeError
142
+ raise TypeError, INCOMPLETE_EXECUTION, cause: nil
143
+ end
144
+
105
145
  def execution_error(payload)
106
146
  return if payload.nil?
107
147
  raise TypeError, INCOMPLETE_EXECUTION unless payload.is_a?(Hash)
@@ -110,10 +150,10 @@ module Volcano
110
150
  end
111
151
 
112
152
  def durable_execution_page(payload)
113
- data, has_more = page_payload(payload)
153
+ values, data, has_more = page_payload(payload)
114
154
  DurableExecutionPage.new(
115
155
  executions: data.map { |entry| durable_execution(entry) }, has_more: has_more,
116
- page: count(payload['page']), limit: count(payload['limit']), total: count(payload['total'])
156
+ page: count(values['page']), limit: count(values['limit']), total: count(values['total'])
117
157
  )
118
158
  end
119
159
 
@@ -124,7 +164,7 @@ module Volcano
124
164
  has_more = payload['has_more']
125
165
  raise TypeError, INCOMPLETE_PAGE unless data.is_a?(Array) && [true, false].include?(has_more)
126
166
 
127
- [data, has_more]
167
+ [payload, data, has_more]
128
168
  end
129
169
 
130
170
  def count(value)
@@ -138,7 +178,10 @@ module Volcano
138
178
  end
139
179
 
140
180
  def parse_time(value)
141
- value.is_a?(String) ? Time.iso8601(value) : value
181
+ return Time.iso8601(value) if value.is_a?(String)
182
+ return value if value.nil? || value.is_a?(Time)
183
+
184
+ raise TypeError, INCOMPLETE_EXECUTION
142
185
  end
143
186
  end
144
187
  private_constant :DurableResponses
@@ -32,7 +32,7 @@ module Volcano
32
32
 
33
33
  def refresh(original)
34
34
  # Resolution has released its cache lock before refresh callbacks run.
35
- @client.auth.__send__(:refresh_captured_session, @binding)
35
+ @client.auth.__send__(:refresh_captured_session, active_binding)
36
36
  rescue Error::SessionChangedError
37
37
  raise
38
38
  rescue Error::VolcanoError
@@ -41,11 +41,18 @@ module Volcano
41
41
 
42
42
  def validate
43
43
  if @binding.last
44
- @client.auth.__send__(:validate_read_failure, @binding)
44
+ @client.auth.__send__(:validate_read_failure, active_binding)
45
45
  elsif @client.capture_session_binding[1] != @binding[1]
46
46
  raise Error::SessionChangedError
47
47
  end
48
48
  end
49
+
50
+ def active_binding
51
+ generation, owner, session = @binding
52
+ raise Error::SessionChangedError unless session
53
+
54
+ [generation, owner, session]
55
+ end
49
56
  end
50
57
  private_constant :FunctionAuth
51
58
  end
@@ -3,6 +3,9 @@
3
3
  require_relative 'function_auth'
4
4
 
5
5
  module Volcano
6
+ INVALID_FUNCTION_NAME = 'Function name must be DNS-safe: lowercase letters, numbers, and hyphens; 1-63 characters'
7
+ private_constant :INVALID_FUNCTION_NAME
8
+
6
9
  # Invokes deployed Volcano functions by name.
7
10
  class Functions
8
11
  FUNCTION_NAME = /\A[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\z/
@@ -21,7 +24,7 @@ module Volcano
21
24
  def invoke(name, payload = {})
22
25
  auth = FunctionAuth.new(@client)
23
26
  validate_invocation(name, payload)
24
- invoke_owned(auth, name.dup.freeze, ImmutableQueryValue.capture(payload))
27
+ invoke_owned(auth, name.dup.freeze, ImmutableRequestValue.capture(payload))
25
28
  end
26
29
 
27
30
  private
@@ -53,9 +56,7 @@ module Volcano
53
56
  # X-Volcano-Function-Invoked only after dispatch, so its absence is what
54
57
  # separates the two. X-Volcano-Version cannot: the server stamps it on every
55
58
  # response, including errors raised before the function is reached.
56
- def stale_mapping?(response)
57
- response.status == 404 && header(response.headers, FUNCTION_INVOKED_HEADER).nil?
58
- end
59
+ def stale_mapping?(response) = response.status == 404 && header(response.headers, FUNCTION_INVOKED_HEADER).nil?
59
60
 
60
61
  def invoke_resolved(authorization, resolution, payload)
61
62
  if resolution.invoke_url.nil?
@@ -77,8 +78,8 @@ module Volcano
77
78
  # Hold the name's lock across the round trip so concurrent callers wait
78
79
  # for one resolve instead of each opening their own.
79
80
  FunctionResolution.resolve_lock(@api_url, authorization, name).synchronize do
80
- cached = FunctionResolution.lookup(@api_url, authorization, name)
81
- next cached_resolution(cached) if cached
81
+ cached_under_lock = FunctionResolution.lookup(@api_url, authorization, name)
82
+ next cached_resolution(cached_under_lock) if cached_under_lock
82
83
 
83
84
  resolve_uncached(authorization, name)
84
85
  end
@@ -88,7 +89,7 @@ module Volcano
88
89
  resolved = Transport.invoke do
89
90
  @transport.resolve_function_for_invocation(authorization: authorization, name: name)
90
91
  end
91
- payload = Transport.body(resolved, 200)
92
+ payload = Transport.json_object(Transport.body(resolved, 200), message: 'Expected a complete function response')
92
93
  resolution = resolved_resolution(payload)
93
94
  FunctionResolution.store(@api_url, authorization, name, resolution, cache_ttl(payload))
94
95
  resolution
@@ -98,24 +99,26 @@ module Volcano
98
99
  end
99
100
 
100
101
  def cached_resolution(cached)
101
- cached.resolution || raise(FunctionResolution.error_for(cached.failure))
102
+ return cached.resolution if cached.resolution
103
+
104
+ failure = cached.failure || raise(TypeError, 'Expected a cached function resolution')
105
+ raise FunctionResolution.error_for(failure)
102
106
  end
103
107
 
104
108
  def validate_invocation(name, payload)
105
- unless name.is_a?(String) && FUNCTION_NAME.match?(name)
106
- raise ArgumentError,
107
- 'Function name must be DNS-safe: lowercase letters, numbers, and hyphens; 1-63 characters'
108
- end
109
+ raise ArgumentError, INVALID_FUNCTION_NAME unless name.is_a?(String) && FUNCTION_NAME.match?(name)
109
110
  raise TypeError, 'Function payload must be a Hash' unless payload.is_a?(Hash)
110
111
  end
111
112
 
112
113
  def resolved_resolution(payload)
113
- function_id = payload['function_id'] if payload.is_a?(Hash)
114
+ payload = Transport.json_object(payload)
115
+ function_id = payload['function_id']
114
116
  raise TypeError, 'Expected a complete function response' unless function_id.is_a?(String) && !function_id.empty?
115
117
 
116
118
  # Absent when the deployment serves no public invocation domain, as in
117
119
  # local development; the function is reached through the API instead.
118
- invoke_url = FunctionResolution.valid_invoke_url(payload['invoke_url'], @api_url)
120
+ candidate = payload['invoke_url']
121
+ invoke_url = FunctionResolution.valid_invoke_url(candidate.is_a?(String) ? candidate : nil, @api_url)
119
122
  FunctionResolution::Resolution.new(function_id: function_id, invoke_url: invoke_url)
120
123
  end
121
124
 
@@ -136,7 +139,7 @@ module Volcano
136
139
  Transport.body(response, 200) unless response.status.between?(200, 299) || dispatched
137
140
 
138
141
  FunctionResponse.new(
139
- data: response.body, status: response.status,
142
+ data: Transport.json_value(response.body), status: response.status,
140
143
  headers: response.headers || {}, version: version
141
144
  )
142
145
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ruby SDK runtime and immutable caller-owned request values.
4
+ module Volcano
5
+ # Copies JSON-like request values before storing them in builders or sending them.
6
+ module ImmutableRequestValue
7
+ def self.capture(value)
8
+ # @type var active: Hash[Integer, bool]
9
+ active = {}
10
+ capture_value(value, active)
11
+ rescue SystemStackError
12
+ raise TypeError, 'Request value nesting is too deep'
13
+ end
14
+
15
+ def self.capture_value(value, active)
16
+ case value
17
+ when String, Time then value.dup.freeze
18
+ when Array, Hash then capture_container(value, active)
19
+ else value
20
+ end
21
+ end
22
+
23
+ def self.capture_container(value, active)
24
+ id = value.object_id
25
+ raise TypeError, 'Request value contains a cycle' if active.key?(id)
26
+
27
+ active[id] = true
28
+ copy = value.is_a?(Array) ? value.map { |item| capture_value(item, active) } : capture_hash(value, active)
29
+ active.delete(id)
30
+ copy.freeze
31
+ end
32
+
33
+ def self.capture_hash(value, active)
34
+ value.to_h { |key, item| [capture_value(key, active), capture_value(item, active)] }
35
+ end
36
+ end
37
+ private_constant :ImmutableRequestValue
38
+ end
data/lib/volcano/logs.rb CHANGED
@@ -3,8 +3,6 @@
3
3
  module Volcano
4
4
  # Searches retained project logs and activity.
5
5
  class Logs
6
- include ImmutableLogJSON
7
-
8
6
  def initialize(client, transport)
9
7
  @client = client
10
8
  @transport = transport
@@ -46,26 +44,27 @@ module Volcano
46
44
  end
47
45
  raise TypeError, 'Log request must be a Hash' unless request.is_a?(Hash)
48
46
 
49
- [project_id.dup.freeze, immutable_json(request)]
47
+ [project_id.dup.freeze, ImmutableRequestValue.capture(request)]
50
48
  end
51
49
 
52
50
  def search_response(body)
53
- data = response_data(body)
51
+ body, data = response_data(body)
54
52
  limit = body['limit']
55
53
  has_more = body['has_more']
56
54
  next_cursor = body['next_cursor']
57
- unless limit.is_a?(Integer) && [true, false].include?(has_more) &&
58
- (next_cursor.nil? || next_cursor.is_a?(String))
55
+ unless limit.is_a?(Integer) && boolean?(has_more) && (next_cursor.nil? || next_cursor.is_a?(String))
59
56
  raise TypeError, 'Expected a complete log response'
60
57
  end
61
58
 
59
+ has_more = has_more == true
60
+
62
61
  LogSearchResponse.new(
63
62
  data: data, limit: limit, has_more: has_more, next_cursor: next_cursor
64
63
  )
65
64
  end
66
65
 
67
66
  def activity_response(body)
68
- data = response_data(body)
67
+ body, data = response_data(body)
69
68
  total = body['total']
70
69
  raise TypeError, 'Expected a complete log response' unless total.is_a?(Integer)
71
70
 
@@ -73,10 +72,16 @@ module Volcano
73
72
  end
74
73
 
75
74
  def response_data(body)
76
- data = body['data'] if body.is_a?(Hash)
75
+ raise TypeError, 'Expected a complete log response' unless body.is_a?(Hash)
76
+
77
+ data = body['data']
77
78
  raise TypeError, 'Expected a complete log response' unless data.is_a?(Array) && data.all?(Hash)
78
79
 
79
- data
80
+ [body, data]
81
+ end
82
+
83
+ def boolean?(value)
84
+ value.is_a?(TrueClass) || value.is_a?(FalseClass)
80
85
  end
81
86
  end
82
87
  end
@@ -7,7 +7,14 @@ module Volcano
7
7
  ].freeze
8
8
  private_constant :AUTH_SESSION_ATTRIBUTES
9
9
 
10
- AuthSession = Data.define(*AUTH_SESSION_ATTRIBUTES) do
10
+ AuthSession = Data.define(*AUTH_SESSION_ATTRIBUTES)
11
+
12
+ # Reopens the generated record for checked initialization.
13
+ class AuthSession
14
+ # @dynamic id, user_id, provider, expires_at, is_active, is_current, user_agent
15
+ # @dynamic ip_address, last_ip_address, last_activity_at, session_started_at
16
+ # @dynamic created_at, updated_at, members, with, to_h, deconstruct
17
+ # @dynamic deconstruct_keys, self.[], self.members
11
18
  def initialize(**attributes)
12
19
  unknown = attributes.keys - AUTH_SESSION_ATTRIBUTES
13
20
  raise ArgumentError, "unknown keywords: #{unknown.join(', ')}" unless unknown.empty?
@@ -18,7 +25,8 @@ module Volcano
18
25
  values = AUTH_SESSION_ATTRIBUTES.to_h do |name|
19
26
  [name, freeze_session_value(attributes[name])]
20
27
  end
21
- super(**values)
28
+ attributes = values
29
+ super
22
30
  end
23
31
 
24
32
  private
@@ -28,19 +36,28 @@ module Volcano
28
36
  end
29
37
  end
30
38
 
31
- SessionPage = Data.define(:sessions, :total, :page, :limit, :total_pages) do
39
+ SessionPage = Data.define(:sessions, :total, :page, :limit, :total_pages)
40
+
41
+ # Reopens the generated record for checked initialization.
42
+ class SessionPage
43
+ # @dynamic sessions, total, page, limit, total_pages, members, with, to_h
44
+ # @dynamic deconstruct, deconstruct_keys, self.[], self.members
32
45
  def initialize(sessions:, total:, page:, limit:, total_pages:)
33
- super(sessions: sessions.to_a.dup.freeze, total: total, page: page, limit: limit,
34
- total_pages: total_pages)
46
+ sessions = sessions.to_a.dup.freeze
47
+ super
35
48
  end
36
49
  end
37
- LinkedOAuthProvider = Data.define(:provider, :linked_at, :updated_at) do
50
+ LinkedOAuthProvider = Data.define(:provider, :linked_at, :updated_at)
51
+
52
+ # Reopens the generated record for checked initialization.
53
+ class LinkedOAuthProvider
54
+ # @dynamic provider, linked_at, updated_at, members, with, to_h, deconstruct
55
+ # @dynamic deconstruct_keys, self.[], self.members
38
56
  def initialize(provider:, linked_at:, updated_at:)
39
- super(
40
- provider: immutable_value(provider),
41
- linked_at: immutable_value(linked_at),
42
- updated_at: immutable_value(updated_at)
43
- )
57
+ provider = immutable_value(provider)
58
+ linked_at = immutable_value(linked_at)
59
+ updated_at = immutable_value(updated_at)
60
+ super
44
61
  end
45
62
 
46
63
  private
@@ -49,30 +66,46 @@ module Volcano
49
66
  value.frozen? ? value : value.dup.freeze
50
67
  end
51
68
  end
52
- OAuthProviderTokenStatus = Data.define(:message, :provider, :expires_in) do
69
+ OAuthProviderTokenStatus = Data.define(:message, :provider, :expires_in)
70
+
71
+ # Reopens the generated record for checked initialization.
72
+ class OAuthProviderTokenStatus
73
+ # @dynamic message, provider, expires_in, members, with, to_h, deconstruct
74
+ # @dynamic deconstruct_keys, self.[], self.members
53
75
  def initialize(message:, provider:, expires_in:)
54
76
  values = [message, provider]
55
77
  valid = values.all? { |value| value.is_a?(String) && !value.strip.empty? }
56
78
  raise TypeError, 'Expected complete OAuth provider token status' unless valid && expires_in.is_a?(Integer)
57
79
 
58
- super(message: message.dup.freeze, provider: provider.dup.freeze, expires_in: expires_in)
80
+ message = message.dup.freeze
81
+ provider = provider.dup.freeze
82
+ super
59
83
  end
60
84
  end
61
85
  EmailChangeResult = Data.define(:message, :new_email)
62
- LockLease = Data.define(:key, :token, :expires_at, :fencing_token) do
86
+ LockLease = Data.define(:key, :token, :expires_at, :fencing_token)
87
+
88
+ # Reopens the generated record for checked initialization.
89
+ class LockLease
90
+ # @dynamic key, token, expires_at, fencing_token, members, with, to_h
91
+ # @dynamic deconstruct, deconstruct_keys, self.[], self.members
63
92
  def initialize(key:, token:, expires_at:, fencing_token:)
64
- super(
65
- key: key.dup.freeze,
66
- token: token.dup.freeze,
67
- expires_at: expires_at&.dup&.freeze,
68
- fencing_token: fencing_token
69
- )
93
+ key = key.dup.freeze
94
+ token = token.dup.freeze
95
+ expires_at = expires_at&.dup&.freeze
96
+ super
70
97
  end
71
98
  end
72
- LockState = Data.define(:held, :expires_at, :fencing_token) do
99
+ LockState = Data.define(:held, :expires_at, :fencing_token)
100
+
101
+ # Reopens the generated record for checked initialization.
102
+ class LockState
103
+ # @dynamic held, expires_at, fencing_token, members, with, to_h, deconstruct
104
+ # @dynamic deconstruct_keys, self.[], self.members
73
105
  def initialize(held:, expires_at:, fencing_token:)
74
106
  immutable_expiry = expires_at&.then { |value| value.frozen? ? value : value.dup.freeze }
75
- super(held: held, expires_at: immutable_expiry, fencing_token: fencing_token)
107
+ expires_at = immutable_expiry
108
+ super
76
109
  end
77
110
  end
78
111
  end
@@ -10,7 +10,10 @@ module Volcano
10
10
  def on(event, callback = nil, &block)
11
11
  raise ArgumentError, "unsupported realtime event: #{event}" unless allowed_event?(event)
12
12
 
13
- @callbacks[event] << (callback || block || raise(ArgumentError, 'callback or block is required'))
13
+ handler = callback || block || raise(ArgumentError, 'callback or block is required')
14
+ raise ArgumentError, 'callback must respond to call' unless handler.respond_to?(:call)
15
+
16
+ @callbacks[event] << handler
14
17
  self
15
18
  end
16
19
 
@@ -39,6 +39,8 @@ module Volcano
39
39
  ensure_postgres!
40
40
  event = postgres_event(event)
41
41
  handler = callback || block || raise(ArgumentError, 'callback or block is required')
42
+ raise ArgumentError, 'callback must respond to call' unless handler.respond_to?(:call)
43
+
42
44
  register_postgres_listener(event, schema.to_s, table.to_s, handler)
43
45
  end
44
46
 
@@ -10,15 +10,26 @@ module Volcano
10
10
  private_constant :SESSION_USER_CODER
11
11
 
12
12
  # Local credentials; refresh credentials and user identity may be unknown.
13
- Session = Data.define(:access_token, :refresh_token, :user_id, :user) do
13
+ Session = Data.define(:access_token, :refresh_token, :user_id, :user)
14
+
15
+ # Reopens the generated record for checked initialization.
16
+ class Session
17
+ # @dynamic access_token, refresh_token, user_id, user, members, with, to_h
18
+ # @dynamic deconstruct, deconstruct_keys, self.[], self.members
14
19
  def initialize(access_token:, refresh_token: nil, user_id: nil, user: nil)
15
- super(access_token:, refresh_token:, user_id:, user: immutable_user(user))
20
+ user = immutable_user(user)
21
+ super
16
22
  end
17
23
 
18
24
  private
19
25
 
20
26
  def immutable_user(value)
21
- SESSION_USER_CODER.load(SESSION_USER_CODER.dump(value))
27
+ return if value.nil?
28
+
29
+ snapshot = SESSION_USER_CODER.load(SESSION_USER_CODER.dump(value))
30
+ raise TypeError, 'Session user snapshot must be a Hash' unless snapshot.is_a?(Hash)
31
+
32
+ snapshot
22
33
  rescue JSON::GeneratorError, JSON::NestingError
23
34
  raise TypeError, 'Session user snapshot must contain JSON values or timestamps', cause: nil
24
35
  end
@@ -20,21 +20,20 @@ module Volcano
20
20
 
21
21
  NETWORK_ERRORS = [IOError, SystemCallError, SocketError, Timeout::Error].freeze
22
22
 
23
- module_function
24
-
25
- def invoke
23
+ def self.invoke
26
24
  yield
27
- rescue *NETWORK_ERRORS => e
25
+ rescue IOError, SystemCallError, SocketError, Timeout::Error => e
28
26
  raise Error::TransportError, e.message, cause: e
29
27
  end
30
28
 
31
- def body(response, expected_status)
29
+ def self.body(response, expected_status)
32
30
  return response.body if response.status == expected_status
33
31
 
34
32
  raise response_error(response)
35
33
  end
36
34
 
37
- def response_error(response)
35
+ def self.response_error(response)
36
+ # @type var payload: Hash[String, Object?]
38
37
  payload = response.body.is_a?(Hash) ? response.body : {}
39
38
  retry_after = integer_header(response.headers, 'Retry-After') if response.status == 429
40
39
  error_type(response.status).new(
@@ -45,20 +44,63 @@ module Volcano
45
44
  )
46
45
  end
47
46
 
48
- def error_message(payload)
49
- payload['error'] || payload['message'] || 'Volcano request failed'
47
+ def self.error_message(payload)
48
+ error = payload['error']
49
+ return error if error.is_a?(String)
50
+
51
+ message = payload['message']
52
+ message.is_a?(String) ? message : 'Volcano request failed'
53
+ end
54
+
55
+ def self.json_value(value)
56
+ case value
57
+ when Array then value.map { |entry| json_value(entry) }
58
+ when Hash then json_object(value)
59
+ else json_scalar(value)
60
+ end
61
+ end
62
+
63
+ def self.json_object(value, message: 'Expected a JSON object')
64
+ raise TypeError, message unless value.is_a?(Hash)
65
+
66
+ value.to_h do |key, item|
67
+ raise TypeError, message unless key.is_a?(String)
68
+
69
+ [key, json_value(item)]
70
+ end
71
+ end
72
+
73
+ def self.json_rows(value)
74
+ rows = json_object(value)['data']
75
+ raise TypeError, 'Expected database rows' unless rows.is_a?(Array)
76
+
77
+ rows.map { |row| json_object(row) }
78
+ end
79
+
80
+ def self.finite_float(value)
81
+ raise TypeError, 'Expected a finite JSON number' unless value.finite?
82
+
83
+ value
84
+ end
85
+
86
+ def self.json_scalar(value)
87
+ case value
88
+ when NilClass, TrueClass, FalseClass, Integer, String then value
89
+ when Float then finite_float(value)
90
+ else raise TypeError, 'Expected a JSON response'
91
+ end
50
92
  end
51
93
 
52
- def error_type(status)
94
+ def self.error_type(status)
53
95
  ERROR_TYPES.fetch(status) do
54
96
  status.between?(500, 599) ? Error::ServerError : Error::VolcanoError
55
97
  end
56
98
  end
57
99
 
58
- def integer_header(headers, name)
100
+ def self.integer_header(headers, name)
59
101
  value = headers&.find { |key, _| key.casecmp?(name) }&.last
60
102
  Integer(value, exception: false)
61
103
  end
62
- private_class_method :error_type, :integer_header, :response_error
104
+ private_class_method :error_type, :integer_header, :response_error, :finite_float, :json_scalar
63
105
  end
64
106
  end
data/lib/volcano/user.rb CHANGED
@@ -10,15 +10,20 @@ module Volcano
10
10
  User = Data.define(
11
11
  :id, :project_id, :email, :email_confirmed, :user_metadata, :app_metadata,
12
12
  :avatar_url, :status, :banned_until, :last_sign_in_at, :created_at, :updated_at
13
- ) do
13
+ )
14
+
15
+ # Reopens the generated record for checked initialization.
16
+ class User
17
+ # @dynamic id, project_id, email, email_confirmed, user_metadata, app_metadata
18
+ # @dynamic avatar_url, status, banned_until, last_sign_in_at, created_at, updated_at
19
+ # @dynamic members, with, to_h, deconstruct, deconstruct_keys, self.[], self.members
14
20
  def initialize(id:, email:, status:, **attributes)
15
21
  validate_optional_attributes(attributes)
16
- super(
17
- id: freeze_value(id),
18
- email: freeze_value(email),
19
- status: freeze_value(status),
20
- **USER_OPTIONAL_ATTRIBUTES.to_h { |name| [name, freeze_value(attributes[name])] }
21
- )
22
+ id = freeze_value(id)
23
+ email = freeze_value(email)
24
+ status = freeze_value(status)
25
+ attributes = USER_OPTIONAL_ATTRIBUTES.to_h { |name| [name, freeze_value(attributes[name])] }
26
+ super
22
27
  end
23
28
 
24
29
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Volcano
4
- VERSION = '0.32.0'
4
+ VERSION = '0.32.2'
5
5
  end
data/lib/volcano.rb CHANGED
@@ -16,6 +16,7 @@ require_relative 'volcano/auth_state'
16
16
  require_relative 'volcano/errors'
17
17
  require_relative 'volcano/redaction'
18
18
  require_relative 'volcano/transport'
19
+ require_relative 'volcano/immutable_request_value'
19
20
  require_relative 'volcano/generated_transport'
20
21
  require_relative 'volcano/auth_notification_dispatch'
21
22
  require_relative 'volcano/auth_session_binding'
@@ -1,8 +1,10 @@
1
1
  module Volcano
2
+ type auth_state_event = :initial_session | :signed_in | :signed_out | :token_refreshed
3
+
2
4
  class Auth
3
5
  def current_session: () -> Session?
4
6
  def current_session=: (Session session) -> void
5
- def on_auth_state_change: () { (Symbol, Session?) -> void } -> AuthSubscription
7
+ def on_auth_state_change: () { (auth_state_event, Session?) -> void } -> AuthSubscription
6
8
  def sign_in: (email: String, password: String) -> Session
7
9
  def sign_up: (email: String, password: String, ?metadata: session_user_input, ?sign_in_when_allowed: bool) -> SignUpResult
8
10
  def sign_in_anonymously: (?metadata: session_user_input) -> Session
@@ -20,7 +20,7 @@ module Volcano
20
20
  | (id: String, user_id: String, provider: String, expires_at: Time, is_active: bool, is_current: bool, ?user_agent: String?, ?ip_address: String?, ?last_ip_address: String?, ?last_activity_at: Time?, ?session_started_at: Time?, ?created_at: Time?, ?updated_at: Time?) -> AuthSession
21
21
  def self.members: () -> [:id, :user_id, :provider, :expires_at, :is_active, :is_current, :user_agent, :ip_address, :last_ip_address, :last_activity_at, :session_started_at, :created_at, :updated_at]
22
22
 
23
- def initialize: (id: String, user_id: String, provider: String, expires_at: Time, is_active: bool, is_current: bool, ?user_agent: String?, ?ip_address: String?, ?last_ip_address: String?, ?last_activity_at: Time?, ?session_started_at: Time?, ?created_at: Time?, ?updated_at: Time?) -> void
23
+ private def initialize: (**Object? attributes) -> void
24
24
  def members: () -> [:id, :user_id, :provider, :expires_at, :is_active, :is_current, :user_agent, :ip_address, :last_ip_address, :last_activity_at, :session_started_at, :created_at, :updated_at]
25
25
  def with: (?id: String, ?user_id: String, ?provider: String, ?expires_at: Time, ?is_active: bool, ?is_current: bool, ?user_agent: String?, ?ip_address: String?, ?last_ip_address: String?, ?last_activity_at: Time?, ?session_started_at: Time?, ?created_at: Time?, ?updated_at: Time?) -> AuthSession
26
26
  def to_h: () -> { id: String, user_id: String, provider: String, expires_at: Time, is_active: bool, is_current: bool, user_agent: String?, ip_address: String?, last_ip_address: String?, last_activity_at: Time?, session_started_at: Time?, created_at: Time?, updated_at: Time? }
data/sig/client.rbs ADDED
@@ -0,0 +1,15 @@
1
+ module Volcano
2
+ class Client
3
+ attr_reader auth: Auth
4
+ attr_reader functions: Functions
5
+ attr_reader durable: Durable
6
+ attr_reader logs: Logs
7
+ attr_reader storage: Storage
8
+ attr_reader locks: Locks
9
+ attr_reader realtime: Realtime
10
+
11
+ def initialize: (anon_key: String, ?api_url: String, ?service_key: String?, ?timeout: (Integer | Float), ?access_token: String?, ?refresh_token: String?) -> void
12
+ def current_session: () -> Session?
13
+ def database: (String name) -> Database
14
+ end
15
+ end
@@ -0,0 +1,81 @@
1
+ module Volcano
2
+ type sdk_json = nil | bool | Integer | Float | String | Time | Array[sdk_json] | Hash[String | Symbol, sdk_json]
3
+ type sdk_payload = Hash[String | Symbol, sdk_json]
4
+ type database_rows = Array[Hash[String, json_value]]
5
+
6
+ class Database
7
+ def from: (String table) -> QueryBuilder
8
+ end
9
+
10
+ class QueryBuilder
11
+ def select: (*String columns) -> QueryBuilder
12
+ def insert: (sdk_payload values) -> InsertBuilder
13
+ def update: (sdk_payload values) -> UpdateBuilder
14
+ def delete: () -> DeleteBuilder
15
+ def order: (String column, ?ascending: bool) -> QueryBuilder
16
+ def limit: (Integer count) -> QueryBuilder
17
+ def offset: (Integer count) -> QueryBuilder
18
+ def execute: () -> database_rows
19
+
20
+ def eq: (String column, sdk_json value) -> QueryBuilder
21
+ def neq: (String column, sdk_json value) -> QueryBuilder
22
+ def gt: (String column, sdk_json value) -> QueryBuilder
23
+ def gte: (String column, sdk_json value) -> QueryBuilder
24
+ def lt: (String column, sdk_json value) -> QueryBuilder
25
+ def lte: (String column, sdk_json value) -> QueryBuilder
26
+ def like: (String column, String pattern) -> QueryBuilder
27
+ def ilike: (String column, String pattern) -> QueryBuilder
28
+ def is: (String column, sdk_json value) -> QueryBuilder
29
+ def in: (String column, Array[sdk_json] values) -> QueryBuilder
30
+ end
31
+
32
+ class InsertBuilder
33
+ def execute: () -> database_rows
34
+ end
35
+
36
+ class UpdateBuilder
37
+ def execute: () -> database_rows
38
+
39
+ def eq: (String column, sdk_json value) -> UpdateBuilder
40
+ def neq: (String column, sdk_json value) -> UpdateBuilder
41
+ def gt: (String column, sdk_json value) -> UpdateBuilder
42
+ def gte: (String column, sdk_json value) -> UpdateBuilder
43
+ def lt: (String column, sdk_json value) -> UpdateBuilder
44
+ def lte: (String column, sdk_json value) -> UpdateBuilder
45
+ def like: (String column, String pattern) -> UpdateBuilder
46
+ def ilike: (String column, String pattern) -> UpdateBuilder
47
+ def is: (String column, sdk_json value) -> UpdateBuilder
48
+ def in: (String column, Array[sdk_json] values) -> UpdateBuilder
49
+ end
50
+
51
+ class DeleteBuilder
52
+ def execute: () -> database_rows
53
+
54
+ def eq: (String column, sdk_json value) -> DeleteBuilder
55
+ def neq: (String column, sdk_json value) -> DeleteBuilder
56
+ def gt: (String column, sdk_json value) -> DeleteBuilder
57
+ def gte: (String column, sdk_json value) -> DeleteBuilder
58
+ def lt: (String column, sdk_json value) -> DeleteBuilder
59
+ def lte: (String column, sdk_json value) -> DeleteBuilder
60
+ def like: (String column, String pattern) -> DeleteBuilder
61
+ def ilike: (String column, String pattern) -> DeleteBuilder
62
+ def is: (String column, sdk_json value) -> DeleteBuilder
63
+ def in: (String column, Array[sdk_json] values) -> DeleteBuilder
64
+ end
65
+
66
+ class Functions
67
+ def invoke: (String name, ?sdk_payload payload) -> FunctionResponse
68
+ end
69
+
70
+ class Durable
71
+ def start: (String function_name, ?sdk_payload payload, ?execution_name: String?) -> DurableExecution
72
+ def get: (String project_id, String function_name, String execution_id) -> DurableExecution
73
+ def list: (String project_id, String function_name, ?status: String?, ?page: Integer?, ?limit: Integer?) -> DurableExecutionPage
74
+ def stop: (String project_id, String function_name, String execution_id) -> DurableExecution
75
+ end
76
+
77
+ class Logs
78
+ def search: (String project_id, sdk_payload request) -> LogSearchResponse
79
+ def activity: (String project_id, sdk_payload request) -> LogActivityResponse
80
+ end
81
+ end
@@ -36,7 +36,7 @@ module Volcano
36
36
  | (id: String, function_id: String, name: String, status: String, region: String, created_at: Time, ?result: json_value, ?result_expired: bool?, ?error: DurableExecutionError?, ?completed_at: Time?) -> DurableExecution
37
37
  def self.members: () -> [:id, :function_id, :name, :status, :region, :created_at, :result, :result_expired, :error, :completed_at]
38
38
 
39
- def initialize: (id: String, function_id: String, name: String, status: String, region: String, created_at: Time, ?result: json_value, ?result_expired: bool?, ?error: DurableExecutionError?, ?completed_at: Time?) -> void
39
+ private def initialize: (**(json_value | Time | DurableExecutionError) attributes) -> void
40
40
  def members: () -> [:id, :function_id, :name, :status, :region, :created_at, :result, :result_expired, :error, :completed_at]
41
41
  def with: (?id: String, ?function_id: String, ?name: String, ?status: String, ?region: String, ?created_at: Time, ?result: json_value, ?result_expired: bool?, ?error: DurableExecutionError?, ?completed_at: Time?) -> DurableExecution
42
42
  def to_h: () -> { id: String, function_id: String, name: String, status: String, region: String, created_at: Time, result: json_value, result_expired: bool?, error: DurableExecutionError?, completed_at: Time? }
data/sig/locks.rbs ADDED
@@ -0,0 +1,10 @@
1
+ module Volcano
2
+ class Locks
3
+ def get: (String key, ?request_id: String?) -> LockState
4
+ def acquire: (String key, ttl: Integer, ?token: String?, ?request_id: String?) -> LockLease
5
+ def renew: (String key, LockLease lease, ttl: Integer, ?request_id: String?) -> LockLease
6
+ def release: (String key, LockLease lease, ?request_id: String?) -> nil
7
+ def force_release: (String key, ?request_id: String?) -> nil
8
+ def with_lock: [T] (String key, ttl: Integer, ?token: String?, ?request_id: String?) { (LockGuard) -> T } -> T
9
+ end
10
+ end
data/sig/realtime.rbs CHANGED
@@ -1,11 +1,4 @@
1
1
  module Volcano
2
- class Client
3
- attr_reader auth: Auth
4
- attr_reader realtime: Realtime
5
-
6
- def initialize: (anon_key: String, ?api_url: String, ?service_key: String?, ?timeout: Integer, ?access_token: String?, ?refresh_token: String?) -> void
7
- end
8
-
9
2
  class Realtime
10
3
  def channel: (String name, ?type: String | Symbol, ?auto_fetch: bool, ?fetch_batch_window_ms: Integer, ?fetch_max_batch_size: Integer) -> Channel
11
4
  def connected?: () -> bool
@@ -30,8 +23,14 @@ module Volcano
30
23
  def unsubscribe: () -> nil
31
24
  def send: (event: String | Symbol, **json_value payload) -> nil
32
25
 
33
- def on: (String event, ^(Object?) -> void callback) -> self
34
- | (String event) { (Object?) -> void } -> self
26
+ def on: ("message" event, ^(Hash[String, json_value]) -> void callback) -> self
27
+ | ("message" event) { (Hash[String, json_value]) -> void } -> self
28
+ | ("join" | "leave" event, ^(PresenceInfo) -> void callback) -> self
29
+ | ("join" | "leave" event) { (PresenceInfo) -> void } -> self
30
+ | ("presence_sync" event, ^(Hash[String, PresenceInfo]) -> void callback) -> self
31
+ | ("presence_sync" event) { (Hash[String, PresenceInfo]) -> void } -> self
32
+ | ("INSERT" | "UPDATE" | "DELETE" | "*" event, ^(PostgresChange) -> void callback) -> self
33
+ | ("INSERT" | "UPDATE" | "DELETE" | "*" event) { (PostgresChange) -> void } -> self
35
34
  def on_presence_sync: (^(Hash[String, PresenceInfo]) -> void callback) -> self
36
35
  | () { (Hash[String, PresenceInfo]) -> void } -> self
37
36
  def on_postgres_changes: (String | Symbol event, schema: String, table: String, callback: ^(PostgresChange) -> void) -> self
data/sig/user.rbs CHANGED
@@ -19,7 +19,7 @@ module Volcano
19
19
  | (id: String, email: String, status: String, ?project_id: String?, ?email_confirmed: bool?, ?user_metadata: Hash[String, json_value]?, ?app_metadata: Hash[String, json_value]?, ?avatar_url: String?, ?banned_until: Time?, ?last_sign_in_at: Time?, ?created_at: Time?, ?updated_at: Time?) -> User
20
20
  def self.members: () -> [:id, :project_id, :email, :email_confirmed, :user_metadata, :app_metadata, :avatar_url, :status, :banned_until, :last_sign_in_at, :created_at, :updated_at]
21
21
 
22
- def initialize: (id: String, email: String, status: String, ?project_id: String?, ?email_confirmed: bool?, ?user_metadata: Hash[String, json_value]?, ?app_metadata: Hash[String, json_value]?, ?avatar_url: String?, ?banned_until: Time?, ?last_sign_in_at: Time?, ?created_at: Time?, ?updated_at: Time?) -> void
22
+ private def initialize: (id: String, email: String, status: String, **Object? attributes) -> void
23
23
  def members: () -> [:id, :project_id, :email, :email_confirmed, :user_metadata, :app_metadata, :avatar_url, :status, :banned_until, :last_sign_in_at, :created_at, :updated_at]
24
24
  def with: (?id: String, ?project_id: String?, ?email: String, ?email_confirmed: bool?, ?user_metadata: Hash[String, json_value]?, ?app_metadata: Hash[String, json_value]?, ?avatar_url: String?, ?status: String, ?banned_until: Time?, ?last_sign_in_at: Time?, ?created_at: Time?, ?updated_at: Time?) -> User
25
25
  def to_h: () -> { id: String, project_id: String?, email: String, email_confirmed: bool?, user_metadata: Hash[String, json_value]?, app_metadata: Hash[String, json_value]?, avatar_url: String?, status: String, banned_until: Time?, last_sign_in_at: Time?, created_at: Time?, updated_at: Time? }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: volcano-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.32.0
4
+ version: 0.32.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kong
@@ -560,6 +560,7 @@ files:
560
560
  - lib/volcano/generated_transport_support.rb
561
561
  - lib/volcano/generated_transport_upload_session_api.rb
562
562
  - lib/volcano/generated_transport_upload_sessions.rb
563
+ - lib/volcano/immutable_request_value.rb
563
564
  - lib/volcano/lock_auto_renewal.rb
564
565
  - lib/volcano/lock_guard.rb
565
566
  - lib/volcano/lock_lease_clock.rb
@@ -620,13 +621,16 @@ files:
620
621
  - sig/auth_session_records.rbs
621
622
  - sig/auth_sessions.rbs
622
623
  - sig/auth_subscription.rbs
624
+ - sig/client.rbs
623
625
  - sig/connection_string.rbs
626
+ - sig/core_facades.rbs
624
627
  - sig/durable_records.rbs
625
628
  - sig/email_change_result.rbs
626
629
  - sig/errors.rbs
627
630
  - sig/function_response.rbs
628
631
  - sig/lock_guard.rbs
629
632
  - sig/lock_records.rbs
633
+ - sig/locks.rbs
630
634
  - sig/log_responses.rbs
631
635
  - sig/oauth_records.rbs
632
636
  - sig/realtime.rbs