axhub-sdk 0.6.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86b1b04e5aef262ab07bdb552bb11e590488b9c1377e3f94f5bc76f736426fa5
4
- data.tar.gz: 5ceef20e46b9480a9787347cd329e2cfd85410f30795ba6cb3f975d60b49c288
3
+ metadata.gz: b05fc409d0332a6e77331fa9b8d893fde93d99b022b6209f9159faabf3d2d38e
4
+ data.tar.gz: a8fcbda4d4ec7be14156c3f70e605100e0d372f267826e77d7fe969f986661cf
5
5
  SHA512:
6
- metadata.gz: 5225e19be9f9a0204e1a3368180a2df7bfced437ac4538799aad4fcbee3e282e268e40086ad3fcfbd937d540c3b17f7086c8c34cd330d87d7d8d3f71602df1a2
7
- data.tar.gz: 906db2bde2bc9d6a652aefa9a0b62e70161989705e47056e3a50aa28768be079e7083ce183b65e127eece32d9b684af2f924f08ebc2cf0edf9fd83c22be36116
6
+ metadata.gz: 1b8e7c144ad7949bbb4eef4453e67f6786b21d5596e2a432aeef7455eee6f6d8424587a971ac1f930718684a1bf0cb1f6d14153b137042fe174dd97b4c1af5a9
7
+ data.tar.gz: 74b8bca75475ba90f5fb9aed68a1151633dab2a3981990abbd5cff81a41d979b0c79db912e3a63158851aa292b5a4135f2b3884986a821275a1a6b4cc2f7a86e
data/README.md CHANGED
@@ -25,7 +25,7 @@ export AXHUB_TENANT_SLUG="test"
25
25
 
26
26
  PAT mode is explicit: `token_type: :pat` sends `X-Api-Key`. JWT mode is `token_type: :jwt` and sends `Authorization: Bearer`.
27
27
 
28
- ## Agent quickstart: create a disposable app and table
28
+ ## Agent quickstart: create a disposable app and enable its database
29
29
 
30
30
  ```ruby
31
31
  require 'axhub_sdk'
@@ -44,7 +44,6 @@ raise 'authGetApiV1Me did not return a user id' if user_id.nil? || user_id.empty
44
44
 
45
45
  suffix = (Time.now.to_f * 1000).to_i.to_s[-8, 8]
46
46
  slug = "agent-rb-#{suffix}"
47
- table = "items#{suffix[-6, 6]}"
48
47
 
49
48
  app = client.apps.create(
50
49
  slug: slug,
@@ -57,28 +56,22 @@ app = client.apps.create(
57
56
  )
58
57
  app_id = app['id']
59
58
 
60
- client.request(
61
- 'schemaPostApiV1AppsByAppIDTables',
62
- path_params: { appID: app_id },
63
- body: {
64
- table_name: table,
65
- owner_column: 'owner_id',
66
- columns: [
67
- { name: 'owner_id', type: 'uuid', nullable: false },
68
- { name: 'title', type: 'text', nullable: false },
69
- { name: 'status', type: 'text', nullable: false }
70
- ]
71
- }
72
- )
59
+ # Enable raw DB mode: a dedicated Postgres role is issued and DATABASE_URL is
60
+ # injected into the app on its next deploy. The app then does row CRUD over
61
+ # direct SQL with its own pg driver.
62
+ client.request('appsPostApiV1AppsByAppIDRawDb', path_params: { appID: app_id }, body: {})
63
+
64
+ # Admin introspection/browse of the physical DB.
65
+ tables = client.request('schemaGetApiV1AppsByAppIDDbTables', path_params: { appID: app_id })
73
66
 
74
- puts "created #{app_id} #{table}"
67
+ puts "created #{app_id} #{tables}"
75
68
  ```
76
69
 
77
70
  ## How to call the full API surface
78
71
 
79
72
  - High-level app create: `client.apps.create(**body)` uses `default_tenant_id`.
80
73
  - Any route by operation id: `client.request(operation_id, path_params: {}, query: {}, body: nil)`.
81
- - Generated facade: `client.data.schema_post_api_v1_apps_by_app_id_tables(path_params: {}, body: {...})`.
74
+ - Generated facade: `client.data.schema_get_api_v1_apps_by_app_id_db_tables(path_params: {})`.
82
75
  - Route inventory: `AxHub::ROUTES`, `AxHub::CONTEXT_ROUTES`, `AxHub::ERROR_CODES`, and `AxHub::OPERATION_METHODS`.
83
76
  - Errors: catch `AxHub::Error` and branch on `code`, `category`, `status`, and `retryable`.
84
77
 
@@ -90,21 +83,15 @@ Use the high-level `apps.create` helper for the first app, then use generated op
90
83
  |------|--------------|----------------------|-------------------|
91
84
  | Create env var | `appsPostApiV1AppsByAppIDEnvVars` | `appID` | `env.list` includes `key` |
92
85
  | Delete env var | `appsDeleteApiV1AppsByAppIDEnvVarsByKey` | `appID`, `key` | `env.list` no longer includes `key` |
93
- | Create table | `schemaPostApiV1AppsByAppIDTables` | `appID` | response `tableName` equals requested name |
94
- | Inspect table | `schemaGetApiV1AppsByAppIDTablesByTableName` | `appID`, `tableName` | response `id` and `tableName` match |
95
- | Add column | `schemaPostApiV1AppsByAppIDTablesByTableNameColumns` | `appID`, `tableName` | inspect contains column name |
96
- | Drop column | `schemaDeleteApiV1AppsByAppIDTablesByTableNameColumnsByColumnName` | `appID`, `tableName`, `columnName` | inspect no longer contains column name |
97
- | Add table grant | `schemaPostApiV1AppsByAppIDTablesByTableNameGrants` | `appID`, `tableName` | response has grant `id` |
98
- | List grants | `schemaGetApiV1AppsByAppIDTablesByTableNameGrants` | `appID`, `tableName` | list contains grant `id` |
99
- | Revoke/delete grant | `schemaDeleteApiV1AppsByAppIDTablesByTableNameGrantsByGrantID` | `appID`, `tableName`, `grantID` | list still contains grant with `revokedAt` set |
100
- | Browse admin rows | `schemaGetApiV1AppsByAppIDTablesByTableNameRows` | `appID`, `tableName` | response has `rows` and `columns` arrays |
101
- | Delete table | `schemaDeleteApiV1AppsByAppIDTablesByTableName` | `appID`, `tableName` | follow-up inspect returns `404` or `410` |
86
+ | Enable raw DB | `appsPostApiV1AppsByAppIDRawDb` | `appID` | dedicated Postgres role issued; `DATABASE_URL` injected on next deploy |
87
+ | Disable raw DB | `appsDeleteApiV1AppsByAppIDRawDb` | `appID` | raw DB mode turned off (data preserved) |
88
+ | List DB tables | `schemaGetApiV1AppsByAppIDDbTables` | `appID` | response lists physical `information_schema` tables |
89
+ | Browse DB rows | `schemaGetApiV1AppsByAppIDDbTablesByTableRows` | `appID`, `table` | response has `rows` array |
102
90
  | Delete app | `appsDeleteApiV1AppsByAppID`, then `appsDeleteApiV1AppsByAppIDPermanent` | `appID` | app is soft-deleted, then permanently deleted |
103
91
 
104
92
  Important semantics from live QA:
105
93
 
106
- - Table delete is hard enough for client assertions: a follow-up table inspect returns `404 not_found` or `410`.
107
- - Table grant delete is a soft revoke: the grant can remain in `listGrants`, but the same grant id must have `revokedAt` set. Do not assert disappearance.
94
+ - App database access is raw: `appsPostApiV1AppsByAppIDRawDb` issues a dedicated Postgres role and injects `DATABASE_URL` on the next deploy (the connection string is never returned). The app does its own row CRUD via direct SQL; the SDK exposes admin introspection/browse only (`schemaGetApiV1AppsByAppIDDbTables` / `...DbTablesByTableRows`).
108
95
  - Deployment creation without a connected git/bootstrap source can return a precondition-style 4xx. That verifies SDK error handling, not a deploy bug.
109
96
 
110
97
 
@@ -114,7 +101,7 @@ The SDK behavior documented here reflects live production QA against the AX Hub
114
101
 
115
102
  - Tenant used for destructive QA: slug `test`, id `cc1e58f1-8e46-4ac7-96c1-190c4cdd5b70`.
116
103
  - Go, Java, Kotlin, Python, and Ruby each ran the generated all-operation sweep against 189 backend routes: SDK exceptions `0`, backend 5xx `0`.
117
- - Go, Java, Kotlin, Python, and Ruby each passed strict destructive DB QA: 22 live steps, 17 assertions, 7 cleanup calls. The flow created an app, env var, table, column, table grant, row, then updated, listed, counted, browsed, deleted, and re-read to prove deletion semantics.
104
+ - Go, Java, Kotlin, Python, and Ruby each passed strict destructive DB QA: the flow exercised the raw-DB enable/reset lifecycle (instead of the removed dynamic-table flow), then deleted the app and re-read to prove deletion semantics.
118
105
  - Node ran the full production mutation suite and a real app bootstrap/deploy wait. Deployment id `d3a48ce3-0f9c-4bab-aa07-863c31c44460` finished `succeeded`, then the app was deleted permanently.
119
106
 
120
107
  Do not print tokens. Use short-lived PATs for agent QA and revoke them after the run.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AxHub
4
- VERSION = '0.6.0'
4
+ VERSION = '0.8.0'
5
5
  end
data/lib/axhub_sdk.rb CHANGED
@@ -74,21 +74,6 @@ module AxHub
74
74
  { 'method' => "DELETE", 'path' => "/api/v1/apps/{appID}/staging", 'tag' => "Deploy", 'operationId' => "deployDeleteApiV1AppsByAppIDStaging" },
75
75
  { 'method' => "PUT", 'path' => "/api/v1/apps/{appID}/staging", 'tag' => "Deploy", 'operationId' => "deployPutApiV1AppsByAppIDStaging" },
76
76
  { 'method' => "POST", 'path' => "/api/v1/apps/{appID}/suspend", 'tag' => "Apps", 'operationId' => "appsPostApiV1AppsByAppIDSuspend" },
77
- { 'method' => "GET", 'path' => "/api/v1/apps/{appID}/tables", 'tag' => "Schema", 'operationId' => "schemaGetApiV1AppsByAppIDTables" },
78
- { 'method' => "POST", 'path' => "/api/v1/apps/{appID}/tables", 'tag' => "Schema", 'operationId' => "schemaPostApiV1AppsByAppIDTables" },
79
- { 'method' => "DELETE", 'path' => "/api/v1/apps/{appID}/tables/{tableName}", 'tag' => "Schema", 'operationId' => "schemaDeleteApiV1AppsByAppIDTablesByTableName" },
80
- { 'method' => "GET", 'path' => "/api/v1/apps/{appID}/tables/{tableName}", 'tag' => "Schema", 'operationId' => "schemaGetApiV1AppsByAppIDTablesByTableName" },
81
- { 'method' => "POST", 'path' => "/api/v1/apps/{appID}/tables/{tableName}/columns", 'tag' => "Schema", 'operationId' => "schemaPostApiV1AppsByAppIDTablesByTableNameColumns" },
82
- { 'method' => "DELETE", 'path' => "/api/v1/apps/{appID}/tables/{tableName}/columns/{columnName}", 'tag' => "Schema", 'operationId' => "schemaDeleteApiV1AppsByAppIDTablesByTableNameColumnsByColumnName" },
83
- { 'method' => "GET", 'path' => "/api/v1/apps/{appID}/tables/{tableName}/grants", 'tag' => "Schema", 'operationId' => "schemaGetApiV1AppsByAppIDTablesByTableNameGrants" },
84
- { 'method' => "POST", 'path' => "/api/v1/apps/{appID}/tables/{tableName}/grants", 'tag' => "Schema", 'operationId' => "schemaPostApiV1AppsByAppIDTablesByTableNameGrants" },
85
- { 'method' => "DELETE", 'path' => "/api/v1/apps/{appID}/tables/{tableName}/grants/{grantID}", 'tag' => "Schema", 'operationId' => "schemaDeleteApiV1AppsByAppIDTablesByTableNameGrantsByGrantID" },
86
- { 'method' => "GET", 'path' => "/api/v1/apps/{appID}/tables/{tableName}/rows", 'tag' => "Schema", 'operationId' => "schemaGetApiV1AppsByAppIDTablesByTableNameRows" },
87
- { 'method' => "POST", 'path' => "/api/v1/apps/{appID}/tables/{tableName}/rows", 'tag' => "Schema", 'operationId' => "schemaPostApiV1AppsByAppIDTablesByTableNameRows" },
88
- { 'method' => "DELETE", 'path' => "/api/v1/apps/{appID}/tables/{tableName}/rows/{id}", 'tag' => "Schema", 'operationId' => "schemaDeleteApiV1AppsByAppIDTablesByTableNameRowsById" },
89
- { 'method' => "PATCH", 'path' => "/api/v1/apps/{appID}/tables/{tableName}/rows/{id}", 'tag' => "Schema", 'operationId' => "schemaPatchApiV1AppsByAppIDTablesByTableNameRowsById" },
90
- { 'method' => "GET", 'path' => "/api/v1/apps/{appID}/tables/check-availability", 'tag' => "Schema", 'operationId' => "schemaGetApiV1AppsByAppIDTablesCheckAvailability" },
91
- { 'method' => "GET", 'path' => "/api/v1/apps/{appID}/tables/column-types", 'tag' => "Schema", 'operationId' => "schemaGetApiV1AppsByAppIDTablesColumnTypes" },
92
77
  { 'method' => "GET", 'path' => "/api/v1/apps/discover", 'tag' => "Apps", 'operationId' => "appsGetApiV1AppsDiscover" },
93
78
  { 'method' => "GET", 'path' => "/api/v1/apps/search", 'tag' => "Apps", 'operationId' => "appsGetApiV1AppsSearch" },
94
79
  { 'method' => "DELETE", 'path' => "/api/v1/comments/{commentID}", 'tag' => "Apps", 'operationId' => "appsDeleteApiV1CommentsByCommentID" },
@@ -370,6 +355,22 @@ module AxHub
370
355
  authPostOauthRevoke
371
356
  authPostOauthToken
372
357
  ].freeze
358
+ # Token-style OAuth responses keep RFC 6749/8628 standard keys in snake_case.
359
+ # Must match the python SDK's _OAUTH_RESPONSE_SNAKE_CASE_OPERATIONS / _OAUTH_RESPONSE_SNAKE_KEYS.
360
+ OAUTH_RESPONSE_SNAKE_CASE_OPERATIONS = %w[
361
+ authPostOauthDeviceAuthorization
362
+ authPostOauthToken
363
+ ].freeze
364
+ OAUTH_RESPONSE_SNAKE_KEYS = %w[
365
+ access_token token_type expires_in refresh_token id_token scope resource tenant
366
+ ].freeze
367
+ def self.camelize_oauth_response(value)
368
+ case value
369
+ when Hash then value.map { |k, v| [OAUTH_RESPONSE_SNAKE_KEYS.include?(k.to_s) ? k.to_s : camel(k), camelize_oauth_response(v)] }.to_h
370
+ when Array then value.map { |v| camelize_oauth_response(v) }
371
+ else value
372
+ end
373
+ end
373
374
  class Client
374
375
  attr_reader :base_url, :apps
375
376
  def initialize(base_url: DEFAULT_BASE_URL, token: nil, token_type: nil, default_tenant_id: nil, default_tenant_slug: nil, timeout_seconds: 30)
@@ -391,7 +392,7 @@ module AxHub
391
392
  req.body = JSON.generate(body)
392
393
  end
393
394
  end
394
- _send(req, uri, camelize: true)
395
+ _send(req, uri, camelize: OAUTH_RESPONSE_SNAKE_CASE_OPERATIONS.include?(operation_id) ? :oauth_snake : true)
395
396
  end
396
397
 
397
398
  private
@@ -429,7 +430,11 @@ module AxHub
429
430
  retryable = err.key?('retryable') ? !!err['retryable'] : !!info&.retryable
430
431
  raise Error.new(category: err['category'] || info&.category || 'unknown', code: err['code'] || "http_#{res.code}", message: err['message'], status: res.code.to_i, retryable: retryable, request_id: err['request_id'] || err['requestId'])
431
432
  end
432
- camelize ? AxHub.camelize(parsed) : parsed
433
+ case camelize
434
+ when :oauth_snake then AxHub.camelize_oauth_response(parsed)
435
+ when true then AxHub.camelize(parsed)
436
+ else parsed
437
+ end
433
438
  end
434
439
 
435
440
  def request_id
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: axhub-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jocoding AX Partners
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-01 00:00:00.000000000 Z
11
+ date: 2026-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest