8 files changed
+202
-15
Cargo.lock
+41
-14
@@ -802,20 +802,6 @@ dependencies = [
802
"url",
803
]
804
805
-[[package]]
806
-name = "gresiq"
807
-version = "0.3.34"
808
-dependencies = [
809
- "native-tls",
810
- "postgres-native-tls",
811
- "reqwest",
812
- "serde",
813
- "serde_json",
814
- "thiserror 2.0.18",
815
- "tokio",
816
- "tokio-postgres",
817
-]
818
-
805
[[package]]
806
name = "hashbrown"
807
version = "0.15.5"
@@ -964,6 +950,7 @@ dependencies = [
950
"tokio",
951
"tokio-rustls",
952
"tower-service",
953
+ "webpki-roots",
954
]
955
956
[[package]]
@@ -2120,6 +2107,7 @@ dependencies = [
2107
"wasm-bindgen",
2108
"wasm-bindgen-futures",
2109
"web-sys",
2110
+ "webpki-roots",
2111
]
2112
2113
[[package]]
@@ -2518,6 +2506,10 @@ dependencies = [
2506
"uuid",
2507
]
2508
2509
+[[package]]
2510
+name = "smbcloud-auth-sdk-wasm"
2511
+version = "0.1.0"
2512
+
2513
[[package]]
2514
name = "smbcloud-auth-wasm"
2515
version = "0.3.34"
@@ -2572,6 +2564,32 @@ dependencies = [
2564
"url-builder",
2565
]
2566
2567
+[[package]]
2568
+name = "smbcloud-gresiq"
2569
+version = "0.3.34"
2570
+dependencies = [
2571
+ "native-tls",
2572
+ "postgres-native-tls",
2573
+ "reqwest",
2574
+ "serde",
2575
+ "serde_json",
2576
+ "thiserror 2.0.18",
2577
+ "tokio",
2578
+ "tokio-postgres",
2579
+]
2580
+
2581
+[[package]]
2582
+name = "smbcloud-gresiq-sdk"
2583
+version = "0.1.0"
2584
+dependencies = [
2585
+ "log",
2586
+ "reqwest",
2587
+ "serde",
2588
+ "serde_json",
2589
+ "thiserror 2.0.18",
2590
+ "tokio",
2591
+]
2592
+
2593
[[package]]
2594
name = "smbcloud-model"
2595
version = "0.3.35"
@@ -3614,6 +3632,15 @@ dependencies = [
3632
"wasm-bindgen",
3633
]
3634
3635
+[[package]]
3636
+name = "webpki-roots"
3637
+version = "1.0.6"
3638
+source = "registry+https://github.com/rust-lang/crates.io-index"
3639
+checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
3640
+dependencies = [
3641
+ "rustls-pki-types",
3642
+]
3643
+
3644
[[package]]
3645
name = "whoami"
3646
version = "2.1.1"
Cargo.toml
+1
@@ -33,6 +33,7 @@ smbcloud-networking = { version = "0.3", path = "crates/smbcloud-networking" }
33
smbcloud-auth = { version = "0.3", path = "crates/smbcloud-auth" }
34
smbcloud-auth-sdk = { version = "0.3", path = "crates/smbcloud-auth-sdk" }
35
smbcloud-networking-project = { version = "0.3", path = "crates/smbcloud-networking-project" }
36
+smbcloud-gresiq-sdk = { version = "0.1", path = "crates/smbcloud-gresiq-sdk" }
37
smbcloud-utils = { version = "0.3", path = "crates/smbcloud-utils" }
38
spinners = "4.1.0"
39
strum = "0.27"
crates/smbcloud-gresiq-sdk/Cargo.toml
new
+16
@@ -0,0 +1,16 @@
1
+[package]
2
+name = "smbcloud-gresiq-sdk"
3
+version = "0.1.0"
4
+edition = "2021"
5
+description = "Rust client for the smbCloud GresIQ REST gateway."
6
+
7
+[lib]
8
+path = "src/smbcloud_gresiq_sdk.rs"
9
+
10
+[dependencies]
11
+log = { workspace = true }
12
+reqwest = { workspace = true, features = ["json", "rustls-tls"] }
13
+serde = { workspace = true, features = ["derive"] }
14
+serde_json = { workspace = true }
15
+thiserror = { workspace = true }
16
+tokio = { workspace = true, features = ["rt"] }
crates/smbcloud-gresiq-sdk/src/client.rs
new
+86
@@ -0,0 +1,86 @@
1
+use serde::Serialize;
2
+
3
+use crate::error::GresiqError;
4
+
5
+/// Talks to the smbCloud GresIQ REST gateway.
6
+///
7
+/// Cheap to clone — the inner `reqwest::Client` is `Arc`-backed.
8
+/// Build one at startup and clone it wherever you need it.
9
+///
10
+/// # Authentication
11
+///
12
+/// Every request carries two headers set from the fields below:
13
+/// `X-Gresiq-Api-Key` and `X-Gresiq-Api-Secret`. Get these from the
14
+/// GresIQ console after registering a database.
15
+#[derive(Debug, Clone)]
16
+pub struct GresiqClient {
17
+ base_url: String,
18
+ api_key: String,
19
+ api_secret: String,
20
+ http: reqwest::Client,
21
+}
22
+
23
+impl GresiqClient {
24
+ /// Build a client from explicit values.
25
+ pub fn new(
26
+ base_url: impl Into<String>,
27
+ api_key: impl Into<String>,
28
+ api_secret: impl Into<String>,
29
+ ) -> Self {
30
+ GresiqClient {
31
+ base_url: base_url.into(),
32
+ api_key: api_key.into(),
33
+ api_secret: api_secret.into(),
34
+ http: reqwest::Client::new(),
35
+ }
36
+ }
37
+
38
+ /// Read `GRESIQ_BASE_URL`, `GRESIQ_API_KEY`, and `GRESIQ_API_SECRET`
39
+ /// from the environment. Returns `None` if any is missing — the caller
40
+ /// decides what that means (skip telemetry, hard error, etc.).
41
+ pub fn from_env() -> Option<Self> {
42
+ let base_url = std::env::var("GRESIQ_BASE_URL").ok()?;
43
+ let api_key = std::env::var("GRESIQ_API_KEY").ok()?;
44
+ let api_secret = std::env::var("GRESIQ_API_SECRET").ok()?;
45
+ Some(Self::new(base_url, api_key, api_secret))
46
+ }
47
+
48
+ /// POST a record into a GresIQ-managed table.
49
+ ///
50
+ /// `table` is the short, un-prefixed name from the REST path —
51
+ /// e.g. `"pulse/model_loaded"` or `"pulse_inference_events"`.
52
+ /// The gateway resolves the tenant prefix from the api_key.
53
+ ///
54
+ /// Returns `Err` on network failure or a non-2xx response. The caller
55
+ /// is responsible for deciding whether to retry, log, or ignore.
56
+ pub async fn insert<T: Serialize>(
57
+ &self,
58
+ table: &str,
59
+ record: &T,
60
+ ) -> Result<(), GresiqError> {
61
+ let url = format!("{}/gresiq/v1/{}", self.base_url, table);
62
+ let body = serde_json::json!({ "record": record });
63
+
64
+ let response = self
65
+ .http
66
+ .post(&url)
67
+ .header("X-Gresiq-Api-Key", &self.api_key)
68
+ .header("X-Gresiq-Api-Secret", &self.api_secret)
69
+ .json(&body)
70
+ .send()
71
+ .await?;
72
+
73
+ if response.status().is_success() {
74
+ log::debug!("gresiq: {} inserted ok", table);
75
+ return Ok(());
76
+ }
77
+
78
+ let status = response.status().as_u16();
79
+ let message = response
80
+ .text()
81
+ .await
82
+ .unwrap_or_else(|_| "unreadable response body".to_string());
83
+
84
+ Err(GresiqError::Api { status, message })
85
+ }
86
+}
crates/smbcloud-gresiq-sdk/src/error.rs
new
+15
@@ -0,0 +1,15 @@
1
+use thiserror::Error;
2
+
3
+#[derive(Debug, Error)]
4
+pub enum GresiqError {
5
+ /// The HTTP layer failed before we even got a response — network down,
6
+ /// DNS failure, TLS handshake, that sort of thing.
7
+ #[error("request failed: {0}")]
8
+ Http(#[from] reqwest::Error),
9
+
10
+ /// The gateway replied with a non-2xx status. The message is whatever
11
+ /// the server put in the response body, or the HTTP reason phrase if
12
+ /// the body wasn't readable.
13
+ #[error("GresIQ API error {status}: {message}")]
14
+ Api { status: u16, message: String },
15
+}
crates/smbcloud-gresiq-sdk/src/smbcloud_gresiq_sdk.rs
new
+38
@@ -0,0 +1,38 @@
1
+//! Rust client for the smbCloud GresIQ REST gateway.
2
+//!
3
+//! GresIQ is a managed-database layer inside smbCloud. It sits in front of
4
+//! a PostgreSQL database, adds API-key auth, and exposes a simple REST
5
+//! interface for inserting and querying rows.
6
+//!
7
+//! This crate handles the HTTP transport and auth headers. Schema knowledge
8
+//! (which tables exist, what the rows look like) lives in the caller — see
9
+//! `onde::pulse` for an example of wrapping this for a specific schema.
10
+//!
11
+//! # Quick start
12
+//!
13
+//! ```no_run
14
+//! use smbcloud_gresiq_sdk::GresiqClient;
15
+//! use serde::Serialize;
16
+//!
17
+//! #[derive(Serialize)]
18
+//! struct Hit { path: String, status: u16 }
19
+//!
20
+//! #[tokio::main]
21
+//! async fn main() -> anyhow::Result<()> {
22
+//! let client = GresiqClient::from_env()
23
+//! .expect("set GRESIQ_BASE_URL, GRESIQ_API_KEY, GRESIQ_API_SECRET");
24
+//!
25
+//! client.insert("hits", &Hit {
26
+//! path: "/api/chat".into(),
27
+//! status: 200,
28
+//! }).await?;
29
+//!
30
+//! Ok(())
31
+//! }
32
+//! ```
33
+
34
+mod client;
35
+mod error;
36
+
37
+pub use client::GresiqClient;
38
+pub use error::GresiqError;
crates/smbcloud-gresiq/Cargo.toml
+1
-1
@@ -1,5 +1,5 @@
1
[package]
2
-name = "gresiq"
2
+name = "smbcloud-gresiq"
3
description = "GresIQ is a serverless PostgreSQL client on the smbCloud platform."
4
version = "0.3.34"
5
edition = "2024"
sdk/npm/smbcloud-gresiq/package.json
new
+4
@@ -0,0 +1,4 @@
1
+{
2
+ "name": "@smbcloud/sdk-gresiq",
3
+ "description": "Browser Auth SDK for smbCloud, built from Rust and WebAssembly."
4
+}