@smbcloud / cli / commits / 2f4b8ba

Prepare gresiq sdk

keypair34 committed Apr 17, 2026 at 19:40 UTC 2f4b8ba41d9aeb5a0dc3746180d553ade7801409
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 +}