edgebase_admin 0.1.4

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: 7788c6699a8440a552520aaaf8f7374d52b8fbfe41888b119686f56ddff67e91
4
+ data.tar.gz: 1006a04397e2ff7b73049156f853f7b6a566e2b5cd01a14a57154dab2fb3d7bc
5
+ SHA512:
6
+ metadata.gz: 16c9a66d72b3c0b8440efba908b01b8d9a99aa5bd44f53cbe5efd48fb5acf5d610f6f0b67d611c6bc020885461b46f15d24af652bab7d6d5d466a85c0b78f284
7
+ data.tar.gz: dc86005559bc4a81e7553d02aea3a501b556bacc1751051d50f4d091184f7c9e4a8c501138851b3e07b367d8dbe4301bdaa55955e6f178d0b00e05eb1b7d1c2f
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 melodysdreamj
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # EdgeBase Ruby Admin SDK
2
+
3
+ Trusted server-side Ruby SDK for EdgeBase.
4
+
5
+ Use `edgebase_admin` from backend apps, scripts, cron jobs, and other trusted Ruby runtimes that hold a Service Key. It exposes admin auth, database access, raw SQL, storage, push, analytics, functions, and native edge resources.
6
+
7
+ Ruby uses a synchronous, object-oriented API. Most methods return values directly rather than promises or futures.
8
+
9
+ ## Documentation Map
10
+
11
+ Use this README for a fast overview, then jump into the docs when you need depth:
12
+
13
+ - [SDK Overview](https://edgebase.fun/docs/sdks)
14
+ Install commands and the public SDK matrix
15
+ - [Admin SDK](https://edgebase.fun/docs/sdks/client-vs-server)
16
+ Trusted-server boundaries and admin-only capabilities
17
+ - [Admin SDK Reference](https://edgebase.fun/docs/admin-sdk/reference)
18
+ Cross-language examples for auth, database, storage, functions, push, and analytics
19
+ - [Admin User Management](https://edgebase.fun/docs/authentication/admin-users)
20
+ Create, update, delete, and manage users with a Service Key
21
+ - [Database Admin SDK](https://edgebase.fun/docs/database/admin-sdk)
22
+ Table queries, filters, pagination, batch writes, and raw SQL
23
+ - [Storage](https://edgebase.fun/docs/storage/upload-download)
24
+ Uploads, downloads, metadata, and signed URLs
25
+ - [Analytics Admin SDK](https://edgebase.fun/docs/analytics/admin-sdk)
26
+ Request metrics, event tracking, and event queries
27
+ - [Push Admin SDK](https://edgebase.fun/docs/push/admin-sdk)
28
+ Push send, topic broadcast, token inspection, and logs
29
+ - [Native Resources](https://edgebase.fun/docs/server/native-resources)
30
+ KV, D1, Vectorize, and other trusted edge-native resources
31
+
32
+ ## For AI Coding Assistants
33
+
34
+ This package includes an `llms.txt` file for AI-assisted development.
35
+
36
+ Use it when you want an agent or code assistant to:
37
+
38
+ - keep Service Keys on trusted servers
39
+ - use the real Ruby method names and keyword arguments
40
+ - avoid copying JS promise-based examples into Ruby
41
+ - know when to use `admin_auth` versus the `auth` alias
42
+
43
+ You can find it:
44
+
45
+ - in this repository: [llms.txt](https://github.com/edge-base/edgebase/blob/main/packages/sdk/ruby/packages/admin/llms.txt)
46
+ - in your environment after install, inside the `edgebase_admin` package directory as `llms.txt`
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ gem install edgebase_admin
52
+ ```
53
+
54
+ If you use Bundler, add `edgebase_admin` to your Gemfile and run `bundle install`.
55
+
56
+ ## Quick Start
57
+
58
+ ```ruby
59
+ require "edgebase_admin"
60
+
61
+ admin = EdgebaseAdmin::AdminClient.new(
62
+ "https://your-project.edgebase.fun",
63
+ service_key: ENV.fetch("EDGEBASE_SERVICE_KEY")
64
+ )
65
+
66
+ users = admin.admin_auth.list_users(limit: 20)
67
+
68
+ rows = admin.sql(
69
+ "shared",
70
+ "SELECT id, title FROM posts WHERE status = ?",
71
+ ["published"]
72
+ )
73
+
74
+ bucket = admin.storage.bucket("avatars")
75
+ bucket.upload("user-1.jpg", "binary-data", content_type: "image/jpeg")
76
+
77
+ admin.push.send("user-1", {
78
+ "title" => "Deployment finished",
79
+ "body" => "Your content is live."
80
+ })
81
+ ```
82
+
83
+ ## Core API
84
+
85
+ Once you create an admin client, these are the main surfaces you will use:
86
+
87
+ - `admin.db(namespace = "shared", instance_id = nil)`
88
+ Server-side database access
89
+ - `admin.admin_auth`
90
+ Admin user management
91
+ - `admin.auth`
92
+ Alias for `admin.admin_auth`
93
+ - `admin.sql(namespace = "shared", query, params = nil, instance_id: nil)`
94
+ Raw SQL execution
95
+ - `admin.storage`
96
+ Server-side storage access
97
+ - `admin.functions`
98
+ Call app functions from trusted code
99
+ - `admin.push`
100
+ Send push notifications
101
+ - `admin.analytics`
102
+ Query analytics and track server-side events
103
+ - `admin.kv(namespace)`, `admin.d1(database)`, `admin.vector(index)`
104
+ Access platform resources from trusted code
105
+ - `admin.broadcast(channel, event, payload = {})`
106
+ Server-side database-live broadcast
107
+ - `admin.destroy`
108
+ No-op cleanup hook
109
+
110
+ ## Database Access
111
+
112
+ ```ruby
113
+ posts = admin.db("app").table("posts")
114
+ rows = posts.where("status", "==", "published").get
115
+ ```
116
+
117
+ For instance databases, pass the instance id as the second argument:
118
+
119
+ ```ruby
120
+ admin.db("workspace", "ws-123")
121
+ admin.db("user", "user-123")
122
+ ```
123
+
124
+ ## Admin Users
125
+
126
+ ```ruby
127
+ created = admin.admin_auth.create_user(
128
+ email: "admin@example.com",
129
+ password: "secure-pass-123",
130
+ data: { "displayName" => "June" }
131
+ )
132
+
133
+ admin.admin_auth.set_custom_claims(created["id"], {
134
+ "role" => "moderator"
135
+ })
136
+
137
+ users = admin.admin_auth.list_users(limit: 20)
138
+ ```
139
+
140
+ ## Raw SQL
141
+
142
+ ```ruby
143
+ shared_rows = admin.sql(
144
+ "shared",
145
+ "SELECT 1 AS ok"
146
+ )
147
+
148
+ workspace_rows = admin.sql(
149
+ "workspace",
150
+ "SELECT * FROM documents WHERE status = ?",
151
+ ["published"],
152
+ instance_id: "ws-123"
153
+ )
154
+ ```
155
+
156
+ ## Push And Analytics
157
+
158
+ ```ruby
159
+ admin.push.send("user-123", {
160
+ "title" => "Hello",
161
+ "body" => "From the admin SDK"
162
+ })
163
+
164
+ overview = admin.analytics.overview("range" => "7d")
165
+ ```
166
+
167
+ ## Choose The Right Package
168
+
169
+ | Package | Use it for |
170
+ | --- | --- |
171
+ | `edgebase_admin` | Trusted server-side Ruby code with Service Key access |
172
+ | `edgebase_core` | Lower-level primitives for custom integrations |
173
+
174
+ ## License
175
+
176
+ MIT
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "edgebase_core"
4
+
5
+ module EdgebaseAdmin
6
+ # Admin auth — server-side user management via Service Key.
7
+ #
8
+ # user = admin.admin_auth.get_user("user-id")
9
+ # new_user = admin.admin_auth.create_user(email: "admin@example.com", password: "secure")
10
+ # admin.admin_auth.set_custom_claims("user-id", { "role" => "pro" })
11
+ # admin.admin_auth.revoke_all_sessions("user-id")
12
+ class AdminAuthClient
13
+ def initialize(client)
14
+ @client = client
15
+ end
16
+
17
+ def get_user(user_id)
18
+ require_service_key!
19
+ unwrap_user(@client.get("/auth/admin/users/#{user_id}"))
20
+ end
21
+
22
+ def create_user(email = nil, password = nil, data: nil, **kwargs)
23
+ require_service_key!
24
+ body = normalize_create_user_payload(email, password, data, kwargs)
25
+ unwrap_user(@client.post("/auth/admin/users", body))
26
+ end
27
+
28
+ def update_user(user_id, data)
29
+ require_service_key!
30
+ unwrap_user(@client.patch("/auth/admin/users/#{user_id}", data))
31
+ end
32
+
33
+ def delete_user(user_id)
34
+ require_service_key!
35
+ @client.delete("/auth/admin/users/#{user_id}")
36
+ end
37
+
38
+ def list_users(limit: 20, cursor: nil)
39
+ require_service_key!
40
+ params = { "limit" => limit.to_s }
41
+ params["cursor"] = cursor if cursor
42
+ result = @client.get("/auth/admin/users", params: params)
43
+ result.is_a?(Hash) ? result : { "users" => [], "cursor" => nil }
44
+ end
45
+
46
+ def set_custom_claims(user_id, claims)
47
+ require_service_key!
48
+ unwrap_user(@client.put("/auth/admin/users/#{user_id}/claims", claims))
49
+ end
50
+
51
+ def revoke_all_sessions(user_id)
52
+ require_service_key!
53
+ @client.post("/auth/admin/users/#{user_id}/revoke")
54
+ end
55
+
56
+ def disable_mfa(user_id)
57
+ require_service_key!
58
+ @client.delete("/auth/admin/users/#{user_id}/mfa")
59
+ end
60
+
61
+ private
62
+
63
+ def normalize_create_user_payload(email, password, data, kwargs)
64
+ if email.is_a?(Hash) && password.nil? && data.nil? && kwargs.empty?
65
+ return stringify_hash(email)
66
+ end
67
+
68
+ body = stringify_hash(kwargs)
69
+ body["email"] = email if email
70
+ body["password"] = password if password
71
+ body["data"] = data if data
72
+ body
73
+ end
74
+
75
+ def stringify_hash(value)
76
+ value.each_with_object({}) do |(key, val), result|
77
+ result[key.to_s] = val
78
+ end
79
+ end
80
+
81
+ def unwrap_user(value)
82
+ return value["user"] if value.is_a?(Hash) && value["user"].is_a?(Hash)
83
+
84
+ value
85
+ end
86
+
87
+ def require_service_key!
88
+ sk = @client.instance_variable_get(:@service_key)
89
+ return if sk && !sk.empty?
90
+
91
+ raise EdgebaseCore::EdgeBaseError.new(
92
+ 403,
93
+ "Service Key required for admin operations. " \
94
+ "Pass service_key: when constructing AdminClient."
95
+ )
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "edgebase_core"
4
+ require_relative "admin_auth"
5
+ require_relative "kv_client"
6
+ require_relative "d1_client"
7
+ require_relative "vectorize_client"
8
+ require_relative "push_client"
9
+ require_relative "functions_client"
10
+ require_relative "analytics_client"
11
+
12
+ module EdgebaseAdmin
13
+ # DB namespace block reference for table access.
14
+ #
15
+ # Obtained via `admin.db("shared")`.
16
+ class DbRef
17
+ attr_reader :_namespace, :_instance_id
18
+
19
+ def initialize(core, namespace, instance_id = nil)
20
+ @core = core
21
+ @_namespace = namespace
22
+ @_instance_id = instance_id
23
+ end
24
+
25
+ # Get a TableRef for the named table.
26
+ def table(name)
27
+ EdgebaseCore::TableRef.new(
28
+ @core, name,
29
+ namespace: @_namespace,
30
+ instance_id: @_instance_id
31
+ )
32
+ end
33
+ end
34
+
35
+ # Unified admin client — db, storage, auth access via Service Key.
36
+ #
37
+ # admin = EdgebaseAdmin::AdminClient.new("http://localhost:8688", service_key: ENV.fetch("EDGEBASE_SERVICE_KEY"))
38
+ # table = admin.db("shared").table("posts")
39
+ # record = table.insert({ "title" => "Hello" })
40
+ #
41
+ # bucket = admin.storage.bucket("documents")
42
+ # bucket.upload("file.txt", "Hello", content_type: "text/plain")
43
+ class AdminClient
44
+ attr_reader :admin_auth
45
+
46
+ def initialize(base_url, service_key:)
47
+ @http = EdgebaseCore::HttpClient.new(base_url, service_key: service_key)
48
+ @core = EdgebaseCore::GeneratedDbApi.new(@http)
49
+ @admin_auth = AdminAuthClient.new(@http)
50
+ end
51
+
52
+ # Get a DbRef for the given namespace.
53
+ def db(namespace = "shared", instance_id: nil)
54
+ DbRef.new(@core, namespace, instance_id)
55
+ end
56
+
57
+ # Get the StorageClient for file operations.
58
+ def storage
59
+ EdgebaseCore::StorageClient.new(@http)
60
+ end
61
+
62
+ # Get a KvClient for the named KV namespace.
63
+ def kv(namespace)
64
+ KvClient.new(@http, namespace)
65
+ end
66
+
67
+ # Get a D1Client for the named D1 database.
68
+ def d1(database)
69
+ D1Client.new(@http, database)
70
+ end
71
+
72
+ # Get a VectorizeClient for the named Vectorize index.
73
+ def vector(index)
74
+ VectorizeClient.new(@http, index)
75
+ end
76
+
77
+ # Get a PushClient for push notification operations.
78
+ def push
79
+ PushClient.new(@http)
80
+ end
81
+
82
+ # Get a FunctionsClient for calling app functions.
83
+ def functions
84
+ FunctionsClient.new(@http)
85
+ end
86
+
87
+ # Get an AnalyticsClient for metrics and custom event tracking.
88
+ def analytics
89
+ AnalyticsClient.new(@core, EdgebaseAdmin::GeneratedAdminApi.new(@http))
90
+ end
91
+
92
+ # Execute raw SQL via DatabaseDO.
93
+ #
94
+ # rows = admin.sql("posts",
95
+ # "SELECT authorId, COUNT(*) as cnt FROM posts GROUP BY authorId ORDER BY cnt DESC LIMIT ?",
96
+ # [10])
97
+ def sql(namespace = "shared", query = nil, params = nil, instance_id: nil)
98
+ if !query.is_a?(String) || query.strip.empty?
99
+ raise ArgumentError, "Invalid sql() signature: query must be a non-empty string"
100
+ end
101
+
102
+ body = {
103
+ "namespace" => namespace,
104
+ "sql" => query,
105
+ "params" => params || [],
106
+ }
107
+ body["id"] = instance_id if instance_id
108
+ admin_core = EdgebaseAdmin::GeneratedAdminApi.new(@http)
109
+ result = admin_core.execute_sql(body)
110
+ return result["rows"] if result.is_a?(Hash) && result["rows"].is_a?(Array)
111
+ return result["items"] if result.is_a?(Hash) && result["items"].is_a?(Array)
112
+ return result["results"] if result.is_a?(Hash) && result["results"].is_a?(Array)
113
+
114
+ result
115
+ end
116
+
117
+ # Send a broadcast message to a database-live channel.
118
+ #
119
+ # admin.broadcast("notifications", "alert", { "message" => "Maintenance in 5 min" })
120
+ def broadcast(channel, event, payload = {})
121
+ admin_core = EdgebaseAdmin::GeneratedAdminApi.new(@http)
122
+ admin_core.database_live_broadcast({
123
+ "channel" => channel,
124
+ "event" => event,
125
+ "payload" => payload
126
+ })
127
+ end
128
+
129
+ # Stateless HTTP client; nothing to tear down, but final-suite runners
130
+ # expect every admin SDK to expose a destroy/cleanup hook.
131
+ def destroy
132
+ nil
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EdgebaseAdmin
4
+ class AnalyticsClient
5
+ def initialize(core, admin_core)
6
+ @methods = EdgebaseCore::GeneratedAnalyticsMethods.new(core)
7
+ @admin_core = admin_core
8
+ end
9
+
10
+ def overview(options = {})
11
+ result = @admin_core.query_analytics(query: build_query("overview", options))
12
+ result.is_a?(Hash) ? result : {}
13
+ end
14
+
15
+ def time_series(options = {})
16
+ result = @admin_core.query_analytics(query: build_query("timeSeries", options))
17
+ result.is_a?(Hash) ? Array(result["timeSeries"]) : []
18
+ end
19
+
20
+ def breakdown(options = {})
21
+ result = @admin_core.query_analytics(query: build_query("breakdown", options))
22
+ result.is_a?(Hash) ? Array(result["breakdown"]) : []
23
+ end
24
+
25
+ def top_endpoints(options = {})
26
+ result = @admin_core.query_analytics(query: build_query("topEndpoints", options))
27
+ result.is_a?(Hash) ? Array(result["topItems"]) : []
28
+ end
29
+
30
+ def track(name, properties = {}, user_id: nil)
31
+ event = {
32
+ "name" => name,
33
+ "timestamp" => (Time.now.to_f * 1000).to_i
34
+ }
35
+ event["properties"] = properties unless properties.nil? || properties.empty?
36
+ event["userId"] = user_id if user_id
37
+ track_batch([event])
38
+ end
39
+
40
+ def track_batch(events)
41
+ normalized = Array(events).map do |event|
42
+ payload = event.dup
43
+ payload["timestamp"] ||= (Time.now.to_f * 1000).to_i
44
+ payload
45
+ end
46
+ return if normalized.empty?
47
+
48
+ @methods.track({ "events" => normalized })
49
+ end
50
+
51
+ def query_events(options = {})
52
+ @admin_core.query_custom_events(query: options)
53
+ end
54
+
55
+ private
56
+
57
+ def build_query(metric, options)
58
+ { "metric" => metric }.merge(options || {})
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "edgebase_core"
4
+
5
+ module EdgebaseAdmin
6
+ # Client for a user-defined D1 database.
7
+ #
8
+ # rows = admin.d1("analytics").exec("SELECT * FROM events WHERE type = ?", ["pageview"])
9
+ class D1Client
10
+ def initialize(http_client, database)
11
+ @http = http_client
12
+ @admin_core = EdgebaseAdmin::GeneratedAdminApi.new(http_client)
13
+ @database = database
14
+ end
15
+
16
+ # Execute a SQL query. Use ? placeholders for bind parameters.
17
+ def exec(query, params = nil)
18
+ body = { "query" => query }
19
+ body["params"] = params if params
20
+ res = @admin_core.execute_d1_query(@database, body)
21
+ res["results"] || []
22
+ end
23
+
24
+ alias query exec
25
+ end
26
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EdgebaseAdmin
4
+ class FunctionsClient
5
+ def initialize(http_client)
6
+ @http = http_client
7
+ end
8
+
9
+ def call(path, method: "POST", body: nil, query: nil)
10
+ normalized_path = "/functions/#{path.sub(%r{^/}, "")}"
11
+
12
+ case method.to_s.upcase
13
+ when "GET"
14
+ @http.get(normalized_path, params: query)
15
+ when "PUT"
16
+ @http.put(normalized_path, body)
17
+ when "PATCH"
18
+ @http.patch(normalized_path, body)
19
+ when "DELETE"
20
+ @http.delete(normalized_path)
21
+ else
22
+ @http.post(normalized_path, body)
23
+ end
24
+ end
25
+
26
+ def get(path, query: nil)
27
+ call(path, method: "GET", query: query)
28
+ end
29
+
30
+ def post(path, body = nil)
31
+ call(path, method: "POST", body: body)
32
+ end
33
+
34
+ def put(path, body = nil)
35
+ call(path, method: "PUT", body: body)
36
+ end
37
+
38
+ def patch(path, body = nil)
39
+ call(path, method: "PATCH", body: body)
40
+ end
41
+
42
+ def delete(path)
43
+ call(path, method: "DELETE")
44
+ end
45
+ end
46
+ end