From af230a8f452cec6bad8a06cafc6df434da794f44 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sun, 1 Jun 2025 06:56:00 -0700 Subject: [PATCH] Separate model for GQL introspection data (#222) --- .../src/bindings/gen_events.ts | 2 +- .../20250530174021_graphql-introspection.sql | 21 +++++ src-tauri/yaak-models/bindings/gen_models.ts | 4 +- src-tauri/yaak-models/bindings/gen_util.ts | 12 +-- src-tauri/yaak-models/build.rs | 2 + src-tauri/yaak-models/guest-js/util.ts | 1 + .../yaak-models/permissions/default.toml | 2 + src-tauri/yaak-models/src/commands.rs | 61 ++++++++++----- src-tauri/yaak-models/src/lib.rs | 14 ++-- src-tauri/yaak-models/src/models.rs | 77 +++++++++++++++++++ .../src/queries/graphql_introspections.rs | 55 +++++++++++++ src-tauri/yaak-models/src/queries/mod.rs | 1 + src-tauri/yaak-plugins/bindings/gen_events.ts | 2 +- src-tauri/yaak-sync/src/models.rs | 1 + src-tauri/yaak-templates/src/renderer.rs | 12 +-- src-web/components/GitDropdown.tsx | 2 +- src-web/components/RecentRequestsDropdown.tsx | 2 +- src-web/hooks/useIntrospectGraphQL.ts | 56 +++++++++----- 18 files changed, 267 insertions(+), 60 deletions(-) create mode 100644 src-tauri/migrations/20250530174021_graphql-introspection.sql create mode 100644 src-tauri/yaak-models/src/queries/graphql_introspections.rs diff --git a/packages/plugin-runtime-types/src/bindings/gen_events.ts b/packages/plugin-runtime-types/src/bindings/gen_events.ts index 4d762f6e..3363706d 100644 --- a/packages/plugin-runtime-types/src/bindings/gen_events.ts +++ b/packages/plugin-runtime-types/src/bindings/gen_events.ts @@ -29,7 +29,7 @@ export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, }; export type CallHttpRequestActionRequest = { index: number, pluginRefId: string, args: CallHttpRequestActionArgs, }; -export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: string }, }; +export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: JsonValue }, }; export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, }; diff --git a/src-tauri/migrations/20250530174021_graphql-introspection.sql b/src-tauri/migrations/20250530174021_graphql-introspection.sql new file mode 100644 index 00000000..ea47ee25 --- /dev/null +++ b/src-tauri/migrations/20250530174021_graphql-introspection.sql @@ -0,0 +1,21 @@ +-- Clean up old key/values that are no longer used +DELETE +FROM key_values +WHERE key LIKE 'graphql_introspection::%'; + +CREATE TABLE graphql_introspections +( + + id TEXT NOT NULL + PRIMARY KEY, + model TEXT DEFAULT 'graphql_introspection' NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + workspace_id TEXT NOT NULL + REFERENCES workspaces + ON DELETE CASCADE, + request_id TEXT NULL + REFERENCES http_requests + ON DELETE CASCADE, + content TEXT NULL +); diff --git a/src-tauri/yaak-models/bindings/gen_models.ts b/src-tauri/yaak-models/bindings/gen_models.ts index bd1b9c84..ba28db69 100644 --- a/src-tauri/yaak-models/bindings/gen_models.ts +++ b/src-tauri/yaak-models/bindings/gen_models.ts @@ -1,6 +1,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type AnyModel = CookieJar | Environment | Folder | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | KeyValue | Plugin | Settings | SyncState | WebsocketConnection | WebsocketEvent | WebsocketRequest | Workspace | WorkspaceMeta; +export type AnyModel = CookieJar | Environment | Folder | GraphQlIntrospection | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | KeyValue | Plugin | Settings | SyncState | WebsocketConnection | WebsocketEvent | WebsocketRequest | Workspace | WorkspaceMeta; export type Cookie = { raw_cookie: string, domain: CookieDomain, expires: CookieExpires, path: [string, boolean], }; @@ -20,6 +20,8 @@ export type EnvironmentVariable = { enabled?: boolean, name: string, value: stri export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, description: string, headers: Array, name: string, sortPriority: number, }; +export type GraphQlIntrospection = { model: "graphql_introspection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, content: string | null, }; + export type GrpcConnection = { model: "grpc_connection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, elapsed: number, error: string | null, method: string, service: string, status: number, state: GrpcConnectionState, trailers: { [key in string]?: string }, url: string, }; export type GrpcConnectionState = "initialized" | "connected" | "closed"; diff --git a/src-tauri/yaak-models/bindings/gen_util.ts b/src-tauri/yaak-models/bindings/gen_util.ts index 482d1902..a38424de 100644 --- a/src-tauri/yaak-models/bindings/gen_util.ts +++ b/src-tauri/yaak-models/bindings/gen_util.ts @@ -1,9 +1,9 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Environment } from "./gen_models.js"; -import type { Folder } from "./gen_models.js"; -import type { GrpcRequest } from "./gen_models.js"; -import type { HttpRequest } from "./gen_models.js"; -import type { WebsocketRequest } from "./gen_models.js"; -import type { Workspace } from "./gen_models.js"; +import type { Environment } from "./gen_models"; +import type { Folder } from "./gen_models"; +import type { GrpcRequest } from "./gen_models"; +import type { HttpRequest } from "./gen_models"; +import type { WebsocketRequest } from "./gen_models"; +import type { Workspace } from "./gen_models"; export type BatchUpsertResult = { workspaces: Array, environments: Array, folders: Array, httpRequests: Array, grpcRequests: Array, websocketRequests: Array, }; diff --git a/src-tauri/yaak-models/build.rs b/src-tauri/yaak-models/build.rs index ca467281..5c2ccf25 100644 --- a/src-tauri/yaak-models/build.rs +++ b/src-tauri/yaak-models/build.rs @@ -1,9 +1,11 @@ const COMMANDS: &[&str] = &[ "delete", "duplicate", + "get_graphql_introspection", "get_settings", "grpc_events", "upsert", + "upsert_graphql_introspection", "websocket_events", "workspace_models", ]; diff --git a/src-tauri/yaak-models/guest-js/util.ts b/src-tauri/yaak-models/guest-js/util.ts index b75efcff..412fa98b 100644 --- a/src-tauri/yaak-models/guest-js/util.ts +++ b/src-tauri/yaak-models/guest-js/util.ts @@ -5,6 +5,7 @@ export function newStoreData(): ModelStoreData { cookie_jar: {}, environment: {}, folder: {}, + graphql_introspection: {}, grpc_connection: {}, grpc_event: {}, grpc_request: {}, diff --git a/src-tauri/yaak-models/permissions/default.toml b/src-tauri/yaak-models/permissions/default.toml index 691305a7..59aa9564 100644 --- a/src-tauri/yaak-models/permissions/default.toml +++ b/src-tauri/yaak-models/permissions/default.toml @@ -4,8 +4,10 @@ permissions = [ "allow-delete", "allow-duplicate", "allow-get-settings", + "allow-get-graphql-introspection", "allow-grpc-events", "allow-upsert", + "allow-upsert-graphql-introspection", "allow-websocket-events", "allow-workspace-models", ] diff --git a/src-tauri/yaak-models/src/commands.rs b/src-tauri/yaak-models/src/commands.rs index ff5124d4..b16fb2e6 100644 --- a/src-tauri/yaak-models/src/commands.rs +++ b/src-tauri/yaak-models/src/commands.rs @@ -1,6 +1,6 @@ use crate::error::Error::GenericError; use crate::error::Result; -use crate::models::{AnyModel, GrpcEvent, Settings, WebsocketEvent}; +use crate::models::{AnyModel, GraphQlIntrospection, GrpcEvent, Settings, WebsocketEvent}; use crate::query_manager::QueryManagerExt; use crate::util::UpdateSource; use tauri::{AppHandle, Runtime, WebviewWindow}; @@ -90,6 +90,26 @@ pub(crate) fn get_settings(app_handle: AppHandle) -> Result( + app_handle: AppHandle, + request_id: &str, +) -> Result> { + Ok(app_handle.db().get_graphql_introspection(request_id)) +} + +#[tauri::command] +pub(crate) fn upsert_graphql_introspection( + app_handle: AppHandle, + request_id: &str, + workspace_id: &str, + content: Option, + window: WebviewWindow, +) -> Result { + let source = UpdateSource::from_window(&window); + Ok(app_handle.db().upsert_graphql_introspection(workspace_id, request_id, content, &source)?) +} + #[tauri::command] pub(crate) fn workspace_models( window: WebviewWindow, @@ -121,11 +141,11 @@ pub(crate) fn workspace_models( } let j = serde_json::to_string(&l)?; - + // NOTE: There's something weird that happens on Linux. If we send Cyrillic (or maybe other) // unicode characters in this response (doesn't matter where) then the following bug happens: // https://feedback.yaak.app/p/editing-the-url-sometimes-freezes-the-app - // + // // It's as if every string resulting from the JSON.parse of the models gets encoded slightly // wrong or something, causing the above bug where Codemirror can't calculate the cursor // position anymore (even when none of the characters are included directly in the input). @@ -137,19 +157,22 @@ pub(crate) fn workspace_models( } fn escape_str_for_webview(input: &str) -> String { - input.chars().map(|c| { - let code = c as u32; - // ASCII - if code <= 0x7F { - c.to_string() - // BMP characters encoded normally - } else if code < 0xFFFF { - format!("\\u{:04X}", code) - // Beyond BMP encoded a surrogate pairs - } else { - let high = ((code - 0x10000) >> 10) + 0xD800; - let low = ((code - 0x10000) & 0x3FF) + 0xDC00; - format!("\\u{:04X}\\u{:04X}", high, low) - } - }).collect() -} \ No newline at end of file + input + .chars() + .map(|c| { + let code = c as u32; + // ASCII + if code <= 0x7F { + c.to_string() + // BMP characters encoded normally + } else if code < 0xFFFF { + format!("\\u{:04X}", code) + // Beyond BMP encoded a surrogate pairs + } else { + let high = ((code - 0x10000) >> 10) + 0xD800; + let low = ((code - 0x10000) & 0x3FF) + 0xDC00; + format!("\\u{:04X}\\u{:04X}", high, low) + } + }) + .collect() +} diff --git a/src-tauri/yaak-models/src/lib.rs b/src-tauri/yaak-models/src/lib.rs index cf36193d..5db752d2 100644 --- a/src-tauri/yaak-models/src/lib.rs +++ b/src-tauri/yaak-models/src/lib.rs @@ -4,9 +4,9 @@ use crate::util::ModelChangeEvent; use log::info; use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; +use sqlx::SqlitePool; use sqlx::migrate::Migrator; use sqlx::sqlite::SqliteConnectOptions; -use sqlx::SqlitePool; use std::fs::create_dir_all; use std::path::PathBuf; use std::str::FromStr; @@ -14,7 +14,7 @@ use std::time::Duration; use tauri::async_runtime::Mutex; use tauri::path::BaseDirectory; use tauri::plugin::TauriPlugin; -use tauri::{generate_handler, AppHandle, Emitter, Manager, Runtime}; +use tauri::{AppHandle, Emitter, Manager, Runtime, generate_handler}; use tokio::sync::mpsc; mod commands; @@ -39,13 +39,15 @@ impl SqliteConnection { pub fn init() -> TauriPlugin { tauri::plugin::Builder::new("yaak-models") .invoke_handler(generate_handler![ - upsert, delete, duplicate, - workspace_models, - grpc_events, - websocket_events, + get_graphql_introspection, get_settings, + grpc_events, + upsert, + upsert_graphql_introspection, + websocket_events, + workspace_models, ]) .setup(|app_handle, _api| { let app_path = app_handle.path().app_data_dir().unwrap(); diff --git a/src-tauri/yaak-models/src/models.rs b/src-tauri/yaak-models/src/models.rs index d9917ea4..57e94546 100644 --- a/src-tauri/yaak-models/src/models.rs +++ b/src-tauri/yaak-models/src/models.rs @@ -1342,6 +1342,79 @@ impl UpsertModelInfo for HttpResponse { } } +#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] +#[serde(default, rename_all = "camelCase")] +#[ts(export, export_to = "gen_models.ts")] +#[enum_def(table_name = "graphql_introspections")] +pub struct GraphQlIntrospection { + #[ts(type = "\"graphql_introspection\"")] + pub model: String, + pub id: String, + pub created_at: NaiveDateTime, + pub updated_at: NaiveDateTime, + pub workspace_id: String, + pub request_id: String, + pub content: Option, +} + +impl UpsertModelInfo for GraphQlIntrospection { + fn table_name() -> impl IntoTableRef { + GraphQlIntrospectionIden::Table + } + + fn id_column() -> impl IntoIden + Eq + Clone { + GraphQlIntrospectionIden::Id + } + + fn generate_id() -> String { + generate_prefixed_id("gi") + } + + fn order_by() -> (impl IntoColumnRef, Order) { + (GraphQlIntrospectionIden::CreatedAt, Desc) + } + + fn get_id(&self) -> String { + self.id.clone() + } + + fn insert_values( + self, + source: &UpdateSource, + ) -> Result)>> { + use GraphQlIntrospectionIden::*; + Ok(vec![ + (CreatedAt, upsert_date(source, self.created_at)), + (UpdatedAt, upsert_date(source, self.updated_at)), + (WorkspaceId, self.workspace_id.into()), + (RequestId, self.request_id.into()), + (Content, self.content.into()), + ]) + } + + fn update_columns() -> Vec { + vec![ + GraphQlIntrospectionIden::UpdatedAt, + GraphQlIntrospectionIden::Content, + ] + } + + fn from_row(r: &Row) -> rusqlite::Result + where + Self: Sized, + { + Ok(Self { + id: r.get("id")?, + model: r.get("model")?, + created_at: r.get("created_at")?, + updated_at: r.get("updated_at")?, + workspace_id: r.get("workspace_id")?, + request_id: r.get("request_id")?, + content: r.get("content")?, + }) + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] @@ -2002,6 +2075,7 @@ define_any_model! { CookieJar, Environment, Folder, + GraphQlIntrospection, GrpcConnection, GrpcEvent, GrpcRequest, @@ -2031,6 +2105,9 @@ impl<'de> Deserialize<'de> for AnyModel { Some(m) if m == "cookie_jar" => AnyModel::CookieJar(fv(value).unwrap()), Some(m) if m == "environment" => AnyModel::Environment(fv(value).unwrap()), Some(m) if m == "folder" => AnyModel::Folder(fv(value).unwrap()), + Some(m) if m == "graphql_introspection" => { + AnyModel::GraphQlIntrospection(fv(value).unwrap()) + } Some(m) if m == "grpc_connection" => AnyModel::GrpcConnection(fv(value).unwrap()), Some(m) if m == "grpc_event" => AnyModel::GrpcEvent(fv(value).unwrap()), Some(m) if m == "grpc_request" => AnyModel::GrpcRequest(fv(value).unwrap()), diff --git a/src-tauri/yaak-models/src/queries/graphql_introspections.rs b/src-tauri/yaak-models/src/queries/graphql_introspections.rs new file mode 100644 index 00000000..1b667b35 --- /dev/null +++ b/src-tauri/yaak-models/src/queries/graphql_introspections.rs @@ -0,0 +1,55 @@ +use crate::db_context::DbContext; +use crate::error::Result; +use crate::models::{GraphQlIntrospection, GraphQlIntrospectionIden}; +use crate::util::UpdateSource; +use chrono::{Duration, Utc}; +use sea_query::{Expr, Query, SqliteQueryBuilder}; +use sea_query_rusqlite::RusqliteBinder; + +impl<'a> DbContext<'a> { + pub fn get_graphql_introspection(&self, request_id: &str) -> Option { + self.find_optional(GraphQlIntrospectionIden::RequestId, request_id) + } + + pub fn upsert_graphql_introspection( + &self, + workspace_id: &str, + request_id: &str, + content: Option, + source: &UpdateSource, + ) -> Result { + // Clean up old ones every time a new one is upserted + self.delete_expired_graphql_introspections()?; + + match self.get_graphql_introspection(request_id) { + None => self.upsert( + &GraphQlIntrospection { + content, + request_id: request_id.to_string(), + workspace_id: workspace_id.to_string(), + ..Default::default() + }, + source, + ), + Some(introspection) => self.upsert( + &GraphQlIntrospection { + content, + ..introspection + }, + source, + ), + } + } + + pub fn delete_expired_graphql_introspections(&self) -> Result<()> { + let cutoff = Utc::now().naive_utc() - Duration::days(7); + let (sql, params) = Query::delete() + .from_table(GraphQlIntrospectionIden::Table) + .cond_where(Expr::col(GraphQlIntrospectionIden::UpdatedAt).lt(cutoff)) + .build_rusqlite(SqliteQueryBuilder); + + let mut stmt = self.conn.resolve().prepare(sql.as_str())?; + stmt.execute(&*params.as_params())?; + Ok(()) + } +} diff --git a/src-tauri/yaak-models/src/queries/mod.rs b/src-tauri/yaak-models/src/queries/mod.rs index 19c583cc..331638fb 100644 --- a/src-tauri/yaak-models/src/queries/mod.rs +++ b/src-tauri/yaak-models/src/queries/mod.rs @@ -2,6 +2,7 @@ mod batch; mod cookie_jars; mod environments; mod folders; +mod graphql_introspections; mod grpc_connections; mod grpc_events; mod grpc_requests; diff --git a/src-tauri/yaak-plugins/bindings/gen_events.ts b/src-tauri/yaak-plugins/bindings/gen_events.ts index 4d762f6e..3363706d 100644 --- a/src-tauri/yaak-plugins/bindings/gen_events.ts +++ b/src-tauri/yaak-plugins/bindings/gen_events.ts @@ -29,7 +29,7 @@ export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, }; export type CallHttpRequestActionRequest = { index: number, pluginRefId: string, args: CallHttpRequestActionArgs, }; -export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: string }, }; +export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: JsonValue }, }; export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, }; diff --git a/src-tauri/yaak-sync/src/models.rs b/src-tauri/yaak-sync/src/models.rs index 85dc72ac..a1dc3ffe 100644 --- a/src-tauri/yaak-sync/src/models.rs +++ b/src-tauri/yaak-sync/src/models.rs @@ -117,6 +117,7 @@ impl TryFrom for SyncModel { // Non-sync models AnyModel::CookieJar(m) => return Err(UnknownModel(m.model)), + AnyModel::GraphQlIntrospection(m) => return Err(UnknownModel(m.model)), AnyModel::GrpcConnection(m) => return Err(UnknownModel(m.model)), AnyModel::GrpcEvent(m) => return Err(UnknownModel(m.model)), AnyModel::HttpResponse(m) => return Err(UnknownModel(m.model)), diff --git a/src-tauri/yaak-templates/src/renderer.rs b/src-tauri/yaak-templates/src/renderer.rs index e867a697..f7946363 100644 --- a/src-tauri/yaak-templates/src/renderer.rs +++ b/src-tauri/yaak-templates/src/renderer.rs @@ -242,7 +242,7 @@ mod parse_and_render_tests { async fn render_valid_fn() -> Result<()> { let vars = HashMap::new(); let template = r#"${[ say_hello(a='John', b='Kate') ]}"#; - let result = r#"say_hello: 2, Some("John") Some("Kate")"#; + let result = r#"say_hello: 2, Some(String("John")) Some(String("Kate"))"#; struct CB {} impl TemplateCallback for CB { @@ -271,7 +271,7 @@ mod parse_and_render_tests { async fn render_fn_arg() -> Result<()> { let vars = HashMap::new(); let template = r#"${[ upper(foo='bar') ]}"#; - let result = r#"BAR"#; + let result = r#""BAR""#; struct CB {} impl TemplateCallback for CB { async fn run( @@ -305,7 +305,7 @@ mod parse_and_render_tests { let mut vars = HashMap::new(); vars.insert("foo".to_string(), "bar".to_string()); let template = r#"${[ upper(foo=b64'Zm9vICdiYXInIGJheg') ]}"#; - let result = r#"FOO 'BAR' BAZ"#; + let result = r#""FOO 'BAR' BAZ""#; struct CB {} impl TemplateCallback for CB { async fn run(&self, fn_name: &str, args: HashMap) -> Result { @@ -334,7 +334,7 @@ mod parse_and_render_tests { let mut vars = HashMap::new(); vars.insert("foo".to_string(), "bar".to_string()); let template = r#"${[ upper(foo='${[ foo ]}') ]}"#; - let result = r#"BAR"#; + let result = r#""BAR""#; struct CB {} impl TemplateCallback for CB { async fn run(&self, fn_name: &str, args: HashMap) -> Result { @@ -364,7 +364,7 @@ mod parse_and_render_tests { let mut vars = HashMap::new(); vars.insert("foo".to_string(), "bar".to_string()); let template = r#"${[ no_op(inner='${[ foo ]}') ]}"#; - let result = r#"bar"#; + let result = r#""bar""#; struct CB {} impl TemplateCallback for CB { async fn run(&self, fn_name: &str, args: HashMap) -> Result { @@ -392,7 +392,7 @@ mod parse_and_render_tests { async fn render_nested_fn() -> Result<()> { let vars = HashMap::new(); let template = r#"${[ upper(foo=secret()) ]}"#; - let result = r#"ABC"#; + let result = r#""ABC""#; struct CB {} impl TemplateCallback for CB { async fn run(&self, fn_name: &str, args: HashMap) -> Result { diff --git a/src-web/components/GitDropdown.tsx b/src-web/components/GitDropdown.tsx index 63550712..27001a94 100644 --- a/src-web/components/GitDropdown.tsx +++ b/src-web/components/GitDropdown.tsx @@ -343,7 +343,7 @@ function SetupSyncDropdown({ workspaceMeta }: { workspaceMeta: WorkspaceMeta }) color: 'success', label: 'Open Workspace Settings', leftSlot: , - onSelect: openWorkspaceSettings, + onSelect: () => openWorkspaceSettings('general'), }, { type: 'separator' }, { diff --git a/src-web/components/RecentRequestsDropdown.tsx b/src-web/components/RecentRequestsDropdown.tsx index c893330a..5e005c17 100644 --- a/src-web/components/RecentRequestsDropdown.tsx +++ b/src-web/components/RecentRequestsDropdown.tsx @@ -5,7 +5,7 @@ import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace'; import { useHotKey } from '../hooks/useHotKey'; import { useKeyboardEvent } from '../hooks/useKeyboardEvent'; import { useRecentRequests } from '../hooks/useRecentRequests'; -import {allRequestsAtom} from "../hooks/useAllRequests"; +import { allRequestsAtom } from '../hooks/useAllRequests'; import { jotaiStore } from '../lib/jotai'; import { resolvedModelName } from '../lib/resolvedModelName'; import { router } from '../lib/router'; diff --git a/src-web/hooks/useIntrospectGraphQL.ts b/src-web/hooks/useIntrospectGraphQL.ts index 4bbf8995..4ffa4831 100644 --- a/src-web/hooks/useIntrospectGraphQL.ts +++ b/src-web/hooks/useIntrospectGraphQL.ts @@ -1,5 +1,7 @@ -import type { HttpRequest } from '@yaakapp-internal/models'; -import type { GraphQLSchema, IntrospectionQuery } from 'graphql'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { invoke } from '@tauri-apps/api/core'; +import type { GraphQlIntrospection, HttpRequest } from '@yaakapp-internal/models'; +import type { GraphQLSchema } from 'graphql'; import { buildClientSchema, getIntrospectionQuery } from 'graphql'; import { useCallback, useEffect, useState } from 'react'; import { minPromiseMillis } from '../lib/minPromiseMillis'; @@ -7,7 +9,6 @@ import { getResponseBodyText } from '../lib/responseBody'; import { sendEphemeralRequest } from '../lib/sendEphemeralRequest'; import { useActiveEnvironment } from './useActiveEnvironment'; import { useDebouncedValue } from './useDebouncedValue'; -import { useKeyValue } from './useKeyValue'; const introspectionRequestBody = JSON.stringify({ query: getIntrospectionQuery(), @@ -20,18 +21,38 @@ export function useIntrospectGraphQL( ) { // Debounce the request because it can change rapidly, and we don't // want to send so too many requests. - const request = useDebouncedValue(baseRequest); + const debouncedRequest = useDebouncedValue(baseRequest); const activeEnvironment = useActiveEnvironment(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(); const [schema, setSchema] = useState(null); + const queryClient = useQueryClient(); - const { value: introspection, set: setIntrospection } = useKeyValue({ - key: ['graphql_introspection', baseRequest.id], - fallback: null, - namespace: 'global', + const introspection = useQuery({ + queryKey: ['introspection', baseRequest.id], + queryFn: async () => + invoke('plugin:yaak-models|get_graphql_introspection', { + requestId: baseRequest.id, + }), }); + const upsertIntrospection = useCallback( + async (content: string | null) => { + const v = await invoke( + 'plugin:yaak-models|upsert_graphql_introspection', + { + requestId: baseRequest.id, + workspaceId: baseRequest.workspaceId, + content: content ?? '', + }, + ); + + // Update local introspection + queryClient.setQueryData(['introspection', baseRequest.id], v); + }, + [baseRequest.id, baseRequest.workspaceId, queryClient], + ); + const refetch = useCallback(async () => { try { setIsLoading(true); @@ -62,15 +83,14 @@ export function useIntrospectGraphQL( return setError('Empty body returned in response'); } - const { data } = JSON.parse(bodyText); - console.log(`Got introspection response for ${baseRequest.url}`, data); - await setIntrospection(data); + console.log(`Got introspection response for ${baseRequest.url}`, bodyText); + await upsertIntrospection(bodyText); } catch (err) { setError(String(err)); } finally { setIsLoading(false); } - }, [activeEnvironment?.id, baseRequest, setIntrospection]); + }, [activeEnvironment?.id, baseRequest, upsertIntrospection]); useEffect(() => { // Skip introspection if automatic is disabled and we already have one @@ -81,27 +101,27 @@ export function useIntrospectGraphQL( refetch().catch(console.error); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [request.id, request.url, request.method, activeEnvironment?.id]); + }, [baseRequest.id, debouncedRequest.url, debouncedRequest.method, activeEnvironment?.id]); const clear = useCallback(async () => { setError(''); setSchema(null); - await setIntrospection(null); - }, [setIntrospection]); + await upsertIntrospection(null); + }, [upsertIntrospection]); useEffect(() => { - if (introspection == null) { + if (introspection.data?.content == null) { return; } try { - const schema = buildClientSchema(introspection); + const schema = buildClientSchema(JSON.parse(introspection.data.content).data); setSchema(schema); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { setError('message' in e ? e.message : String(e)); } - }, [introspection]); + }, [introspection.data?.content]); return { schema, isLoading, error, refetch, clear }; }