diff --git a/crates/yaak-git/bindings/gen_models.ts b/crates/yaak-git/bindings/gen_models.ts index ee83e6645..866a91e47 100644 --- a/crates/yaak-git/bindings/gen_models.ts +++ b/crates/yaak-git/bindings/gen_models.ts @@ -2,22 +2,44 @@ export type DnsOverride = { hostname: string, ipv4: Array, ipv6: Array, enabled?: boolean, }; -export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null, variables: Array, color: string | null, sortPriority: number, }; +export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null, +/** + * Variables defined in this environment scope. + * Child environments override parent variables by name. + */ +variables: Array, color: string | null, sortPriority: number, }; export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, }; 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 GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record, description: string, message: string, metadata: Array, method: string | null, name: string, service: string | null, sortPriority: number, url: string, }; +export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record, description: string, message: string, metadata: Array, method: string | null, name: string, service: string | null, sortPriority: number, +/** + * Server URL (http for plaintext or https for secure) + */ +url: string, }; -export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, body: Record, bodyType: string | null, description: string, headers: Array, method: string, name: string, sortPriority: number, url: string, urlParameters: Array, }; +export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, body: Record, bodyType: string | null, description: string, headers: Array, method: string, name: string, sortPriority: number, url: string, +/** + * URL parameters used for both path placeholders (`:id`) and query string entries. + */ +urlParameters: Array, }; export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id?: string, }; -export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id?: string, }; +export type HttpUrlParameter = { enabled?: boolean, +/** + * Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id` + * Other entries are appended as query parameters + */ +name: string, value: string, id?: string, }; export type SyncModel = { "type": "workspace" } & Workspace | { "type": "environment" } & Environment | { "type": "folder" } & Folder | { "type": "http_request" } & HttpRequest | { "type": "grpc_request" } & GrpcRequest | { "type": "websocket_request" } & WebsocketRequest; -export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, description: string, headers: Array, message: string, name: string, sortPriority: number, url: string, urlParameters: Array, }; +export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, description: string, headers: Array, message: string, name: string, sortPriority: number, url: string, +/** + * URL parameters used for both path placeholders (`:id`) and query string entries. + */ +urlParameters: Array, }; export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, authentication: Record, authenticationType: string | null, description: string, headers: Array, name: string, encryptionKeyChallenge: string | null, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, settingDnsOverrides: Array, }; diff --git a/crates/yaak-models/bindings/gen_models.ts b/crates/yaak-models/bindings/gen_models.ts index 8fcf34672..430128d24 100644 --- a/crates/yaak-models/bindings/gen_models.ts +++ b/crates/yaak-models/bindings/gen_models.ts @@ -18,7 +18,12 @@ export type EditorKeymap = "default" | "vim" | "vscode" | "emacs"; export type EncryptedKey = { encryptedKey: string, }; -export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null, variables: Array, color: string | null, sortPriority: number, }; +export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null, +/** + * Variables defined in this environment scope. + * Child environments override parent variables by name. + */ +variables: Array, color: string | null, sortPriority: number, }; export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, }; @@ -34,13 +39,21 @@ export type GrpcEvent = { model: "grpc_event", id: string, createdAt: string, up export type GrpcEventType = "info" | "error" | "client_message" | "server_message" | "connection_start" | "connection_end"; -export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record, description: string, message: string, metadata: Array, method: string | null, name: string, service: string | null, sortPriority: number, url: string, }; +export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record, description: string, message: string, metadata: Array, method: string | null, name: string, service: string | null, sortPriority: number, +/** + * Server URL (http for plaintext or https for secure) + */ +url: string, }; -export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, body: Record, bodyType: string | null, description: string, headers: Array, method: string, name: string, sortPriority: number, url: string, urlParameters: Array, }; +export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, body: Record, bodyType: string | null, description: string, headers: Array, method: string, name: string, sortPriority: number, url: string, +/** + * URL parameters used for both path placeholders (`:id`) and query string entries. + */ +urlParameters: Array, }; export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id?: string, }; -export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, contentLengthCompressed: number | null, elapsed: number, elapsedHeaders: number, elapsedDns: number, error: string | null, headers: Array, remoteAddr: string | null, requestContentLength: number | null, requestHeaders: Array, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, }; +export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, contentLengthCompressed: number | null, elapsed: number, elapsedHeaders: number, elapsedDns: number, error: string | null, headers: Array, remoteAddr: string | null, requestContentLength: number | null, requestHeaders: Array, method: string | null, requestName: string | null, requestBodyType: string | null, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, }; export type HttpResponseEvent = { model: "http_response_event", id: string, createdAt: string, updatedAt: string, workspaceId: string, responseId: string, event: HttpResponseEventData, }; @@ -55,7 +68,12 @@ export type HttpResponseHeader = { name: string, value: string, }; export type HttpResponseState = "initialized" | "connected" | "closed"; -export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id?: string, }; +export type HttpUrlParameter = { enabled?: boolean, +/** + * Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id` + * Other entries are appended as query parameters + */ +name: string, value: string, id?: string, }; export type KeyValue = { model: "key_value", id: string, createdAt: string, updatedAt: string, key: string, namespace: string, value: string, }; @@ -69,10 +87,10 @@ export type ParentHeaders = { headers: Array, }; export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt: string, checkedAt: string | null, directory: string, enabled: boolean, url: string | null, source: PluginSource, }; -export type PluginSource = "bundled" | "filesystem" | "registry"; - export type PluginKeyValue = { model: "plugin_key_value", createdAt: string, updatedAt: string, pluginName: string, key: string, value: string, }; +export type PluginSource = "bundled" | "filesystem" | "registry"; + export type ProxySetting = { "type": "enabled", http: string, https: string, auth: ProxySettingAuth | null, bypass: string, disabled: boolean, } | { "type": "disabled" }; export type ProxySettingAuth = { user: string, password: string, }; @@ -93,7 +111,11 @@ export type WebsocketEventType = "binary" | "close" | "frame" | "open" | "ping" export type WebsocketMessageType = "text" | "binary"; -export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, description: string, headers: Array, message: string, name: string, sortPriority: number, url: string, urlParameters: Array, }; +export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, description: string, headers: Array, message: string, name: string, sortPriority: number, url: string, +/** + * URL parameters used for both path placeholders (`:id`) and query string entries. + */ +urlParameters: Array, }; export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, authentication: Record, authenticationType: string | null, description: string, headers: Array, name: string, encryptionKeyChallenge: string | null, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, settingDnsOverrides: Array, }; diff --git a/crates/yaak-models/migrations/20260418000000_http-response-method-and-request-name.sql b/crates/yaak-models/migrations/20260418000000_http-response-method-and-request-name.sql new file mode 100644 index 000000000..1a2b17732 --- /dev/null +++ b/crates/yaak-models/migrations/20260418000000_http-response-method-and-request-name.sql @@ -0,0 +1,34 @@ +-- Freeze the HTTP method, the originating request's name, and the request +-- body type on each response so history rows don't drift when the request is +-- later edited. +ALTER TABLE http_responses ADD COLUMN method TEXT; +ALTER TABLE http_responses ADD COLUMN request_name TEXT; +ALTER TABLE http_responses ADD COLUMN request_body_type TEXT; + +-- Backfill method from the matching `send_url` event when we have one. +UPDATE http_responses +SET method = ( + SELECT json_extract(e.event, '$.method') + FROM http_response_events e + WHERE e.response_id = http_responses.id + AND json_extract(e.event, '$.type') = 'send_url' + ORDER BY e.created_at ASC + LIMIT 1 +); + +-- Fall back to the current request method for older responses without events. +UPDATE http_responses +SET method = ( + SELECT method FROM http_requests WHERE id = http_responses.request_id +) +WHERE method IS NULL; + +UPDATE http_responses +SET request_name = ( + SELECT name FROM http_requests WHERE id = http_responses.request_id +); + +UPDATE http_responses +SET request_body_type = ( + SELECT body_type FROM http_requests WHERE id = http_responses.request_id +); diff --git a/crates/yaak-models/src/models.rs b/crates/yaak-models/src/models.rs index 9c206b0a5..c890b46a2 100644 --- a/crates/yaak-models/src/models.rs +++ b/crates/yaak-models/src/models.rs @@ -1366,6 +1366,9 @@ pub struct HttpResponse { pub remote_addr: Option, pub request_content_length: Option, pub request_headers: Vec, + pub method: Option, + pub request_name: Option, + pub request_body_type: Option, pub status: i32, pub status_reason: Option, pub state: HttpResponseState, @@ -1414,6 +1417,9 @@ impl UpsertModelInfo for HttpResponse { (Headers, serde_json::to_string(&self.headers)?.into()), (RemoteAddr, self.remote_addr.into()), (RequestHeaders, serde_json::to_string(&self.request_headers)?.into()), + (Method, self.method.into()), + (RequestName, self.request_name.into()), + (RequestBodyType, self.request_body_type.into()), (State, serde_json::to_value(self.state)?.as_str().into()), (Status, self.status.into()), (StatusReason, self.status_reason.into()), @@ -1437,6 +1443,9 @@ impl UpsertModelInfo for HttpResponse { HttpResponseIden::RemoteAddr, HttpResponseIden::RequestContentLength, HttpResponseIden::RequestHeaders, + HttpResponseIden::Method, + HttpResponseIden::RequestName, + HttpResponseIden::RequestBodyType, HttpResponseIden::State, HttpResponseIden::Status, HttpResponseIden::StatusReason, @@ -1477,6 +1486,9 @@ impl UpsertModelInfo for HttpResponse { r.get::<_, String>("request_headers").unwrap_or_default().as_str(), ) .unwrap_or_default(), + method: r.get("method").unwrap_or_default(), + request_name: r.get("request_name").unwrap_or_default(), + request_body_type: r.get("request_body_type").unwrap_or_default(), }) } } diff --git a/crates/yaak-plugins/bindings/gen_events.ts b/crates/yaak-plugins/bindings/gen_events.ts index ba130b7b8..57310aaaf 100644 --- a/crates/yaak-plugins/bindings/gen_events.ts +++ b/crates/yaak-plugins/bindings/gen_events.ts @@ -18,12 +18,12 @@ export type CallHttpAuthenticationActionRequest = { index: number, pluginRefId: export type CallHttpAuthenticationRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, method: string, url: string, headers: Array, }; -export type CallHttpAuthenticationResponse = { +export type CallHttpAuthenticationResponse = { /** * HTTP headers to add to the request. Existing headers will be replaced, while * new headers will be added. */ -setHeaders?: Array, +setHeaders?: Array, /** * Query parameters to add to the request. Existing params will be replaced, while * new params will be added. @@ -78,7 +78,7 @@ export type ExportHttpRequestRequest = { httpRequest: HttpRequest, }; export type ExportHttpRequestResponse = { content: string, }; -export type FileFilter = { name: string, +export type FileFilter = { name: string, /** * File extensions to require */ @@ -100,149 +100,149 @@ export type FormInputAccordion = { label: string, inputs?: Array, hid export type FormInputBanner = { inputs?: Array, hidden?: boolean, color?: Color, }; -export type FormInputBase = { +export type FormInputBase = { /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ description?: string, }; -export type FormInputCheckbox = { +export type FormInputCheckbox = { /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ description?: string, }; -export type FormInputEditor = { +export type FormInputEditor = { /** * Placeholder for the text input */ -placeholder?: string | null, +placeholder?: string | null, /** * Don't show the editor gutter (line numbers, folds, etc.) */ -hideGutter?: boolean, +hideGutter?: boolean, /** * Language for syntax highlighting */ -language?: EditorLanguage, readOnly?: boolean, +language?: EditorLanguage, readOnly?: boolean, /** * Fixed number of visible rows */ -rows?: number, completionOptions?: Array, +rows?: number, completionOptions?: Array, /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ description?: string, }; -export type FormInputFile = { +export type FormInputFile = { /** * The title of the file selection window */ -title: string, +title: string, /** * Allow selecting multiple files */ -multiple?: boolean, directory?: boolean, defaultPath?: string, filters?: Array, +multiple?: boolean, directory?: boolean, defaultPath?: string, filters?: Array, /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ @@ -250,63 +250,63 @@ description?: string, }; export type FormInputHStack = { inputs?: Array, hidden?: boolean, }; -export type FormInputHttpRequest = { +export type FormInputHttpRequest = { /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ description?: string, }; -export type FormInputKeyValue = { +export type FormInputKeyValue = { /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ @@ -314,36 +314,36 @@ description?: string, }; export type FormInputMarkdown = { content: string, hidden?: boolean, }; -export type FormInputSelect = { +export type FormInputSelect = { /** * The options that will be available in the select input */ -options: Array, +options: Array, /** * The name of the input. The value will be stored at this object attribute in the resulting data */ -name: string, +name: string, /** * Whether this input is visible for the given configuration. Use this to * make branching forms. */ -hidden?: boolean, +hidden?: boolean, /** * Whether the user must fill in the argument */ -optional?: boolean, +optional?: boolean, /** * The label of the input */ -label?: string, +label?: string, /** * Visually hide the label of the input */ -hideLabel?: boolean, +hideLabel?: boolean, /** * The default value */ -defaultValue?: string, disabled?: boolean, +defaultValue?: string, disabled?: boolean, /** * Longer description of the input, likely shown in a tooltip */ @@ -351,44 +351,44 @@ description?: string, }; export type FormInputSelectOption = { label: string, value: string, }; -export type FormInputText = { +export type FormInputText = { /** * Placeholder for the text input */ -placeholder?: string | null, +placeholder?: string | null, /** * Placeholder for the text input */ -password?: boolean, +password?: boolean, /** * Whether to allow newlines in the input, like a