From 4ce7b5a460785cc186f8a87b5bf9c93185ac713a Mon Sep 17 00:00:00 2001 From: Larry Date: Tue, 30 Dec 2025 13:02:58 +0800 Subject: [PATCH 1/4] refactor(convex): extract validators into separate files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create dedicated validators.ts files for each model domain - Move Convex validators from types.ts to validators.ts files - Reduces code duplication and improves maintainability - Affected domains: approvals, conversations, customers, documents, email_providers, integrations, organizations, products, tone_of_voice, websites, wf_definitions, wf_executions, wf_step_defs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- services/platform/convex/_generated/api.d.ts | 26 ++ services/platform/convex/conversations.ts | 2 +- services/platform/convex/customers.ts | 2 +- services/platform/convex/documents.ts | 8 +- .../platform/convex/model/approvals/index.ts | 5 +- .../platform/convex/model/approvals/types.ts | 102 +---- .../convex/model/approvals/validators.ts | 56 +++ .../convex/model/conversations/index.ts | 29 +- .../convex/model/conversations/types.ts | 279 ++------------ .../convex/model/conversations/validators.ts | 148 ++++++++ .../platform/convex/model/customers/index.ts | 5 +- .../platform/convex/model/customers/types.ts | 63 +--- .../convex/model/customers/validators.ts | 51 +++ .../platform/convex/model/documents/index.ts | 27 +- .../platform/convex/model/documents/types.ts | 165 ++------ .../convex/model/documents/validators.ts | 103 +++++ .../convex/model/email_providers/index.ts | 1 + .../convex/model/email_providers/types.ts | 77 +--- .../model/email_providers/validators.ts | 36 ++ .../convex/model/integrations/index.ts | 5 +- .../convex/model/integrations/types.ts | 351 +++--------------- .../convex/model/integrations/validators.ts | 200 ++++++++++ .../convex/model/organizations/index.ts | 5 +- .../convex/model/organizations/types.ts | 30 +- .../convex/model/organizations/validators.ts | 28 ++ .../platform/convex/model/products/index.ts | 5 +- .../platform/convex/model/products/types.ts | 149 +------- .../convex/model/products/validators.ts | 78 ++++ .../convex/model/tone_of_voice/index.ts | 5 +- .../convex/model/tone_of_voice/types.ts | 107 +----- .../convex/model/tone_of_voice/validators.ts | 55 +++ .../platform/convex/model/websites/index.ts | 5 +- .../platform/convex/model/websites/types.ts | 61 +-- .../convex/model/websites/validators.ts | 47 +++ .../convex/model/wf_definitions/index.ts | 5 +- .../convex/model/wf_definitions/types.ts | 81 +--- .../convex/model/wf_definitions/validators.ts | 44 +++ .../convex/model/wf_executions/index.ts | 5 +- .../convex/model/wf_executions/types.ts | 85 +---- .../convex/model/wf_executions/validators.ts | 81 ++++ .../convex/model/wf_step_defs/index.ts | 1 + .../convex/model/wf_step_defs/types.ts | 83 +---- .../convex/model/wf_step_defs/validators.ts | 71 ++++ services/platform/convex/websites.ts | 2 +- .../actions/customer/customer_action.ts | 2 +- 45 files changed, 1306 insertions(+), 1470 deletions(-) create mode 100644 services/platform/convex/model/approvals/validators.ts create mode 100644 services/platform/convex/model/conversations/validators.ts create mode 100644 services/platform/convex/model/customers/validators.ts create mode 100644 services/platform/convex/model/documents/validators.ts create mode 100644 services/platform/convex/model/email_providers/validators.ts create mode 100644 services/platform/convex/model/integrations/validators.ts create mode 100644 services/platform/convex/model/organizations/validators.ts create mode 100644 services/platform/convex/model/products/validators.ts create mode 100644 services/platform/convex/model/tone_of_voice/validators.ts create mode 100644 services/platform/convex/model/websites/validators.ts create mode 100644 services/platform/convex/model/wf_definitions/validators.ts create mode 100644 services/platform/convex/model/wf_executions/validators.ts create mode 100644 services/platform/convex/model/wf_step_defs/validators.ts diff --git a/services/platform/convex/_generated/api.d.ts b/services/platform/convex/_generated/api.d.ts index 82dfa4c637..5a00459b99 100644 --- a/services/platform/convex/_generated/api.d.ts +++ b/services/platform/convex/_generated/api.d.ts @@ -147,6 +147,7 @@ import type * as model_approvals_list_pending_approvals_for_execution from "../m import type * as model_approvals_remove_recommended_product from "../model/approvals/remove_recommended_product.js"; import type * as model_approvals_types from "../model/approvals/types.js"; import type * as model_approvals_update_approval_status from "../model/approvals/update_approval_status.js"; +import type * as model_approvals_validators from "../model/approvals/validators.js"; import type * as model_chat_agent_auto_summarize_if_needed from "../model/chat_agent/auto_summarize_if_needed.js"; import type * as model_chat_agent_chat_with_agent from "../model/chat_agent/chat_with_agent.js"; import type * as model_chat_agent_context_management_check_and_summarize from "../model/chat_agent/context_management/check_and_summarize.js"; @@ -187,6 +188,7 @@ import type * as model_conversations_types from "../model/conversations/types.js import type * as model_conversations_update_conversation from "../model/conversations/update_conversation.js"; import type * as model_conversations_update_conversation_message from "../model/conversations/update_conversation_message.js"; import type * as model_conversations_update_conversations from "../model/conversations/update_conversations.js"; +import type * as model_conversations_validators from "../model/conversations/validators.js"; import type * as model_customers_bulk_create_customers from "../model/customers/bulk_create_customers.js"; import type * as model_customers_create_customer from "../model/customers/create_customer.js"; import type * as model_customers_create_customer_public from "../model/customers/create_customer_public.js"; @@ -205,6 +207,7 @@ import type * as model_customers_types from "../model/customers/types.js"; import type * as model_customers_update_customer from "../model/customers/update_customer.js"; import type * as model_customers_update_customer_metadata from "../model/customers/update_customer_metadata.js"; import type * as model_customers_update_customers from "../model/customers/update_customers.js"; +import type * as model_customers_validators from "../model/customers/validators.js"; import type * as model_documents_analyze_pptx from "../model/documents/analyze_pptx.js"; import type * as model_documents_check_membership from "../model/documents/check_membership.js"; import type * as model_documents_create_document from "../model/documents/create_document.js"; @@ -231,6 +234,7 @@ import type * as model_documents_transform_to_document_item from "../model/docum import type * as model_documents_types from "../model/documents/types.js"; import type * as model_documents_update_document from "../model/documents/update_document.js"; import type * as model_documents_upload_base64_to_storage from "../model/documents/upload_base64_to_storage.js"; +import type * as model_documents_validators from "../model/documents/validators.js"; import type * as model_email_providers_create_oauth2_provider_logic from "../model/email_providers/create_oauth2_provider_logic.js"; import type * as model_email_providers_create_provider_internal from "../model/email_providers/create_provider_internal.js"; import type * as model_email_providers_create_provider_logic from "../model/email_providers/create_provider_logic.js"; @@ -257,6 +261,7 @@ import type * as model_email_providers_update_metadata_internal from "../model/e import type * as model_email_providers_update_oauth2_tokens from "../model/email_providers/update_oauth2_tokens.js"; import type * as model_email_providers_update_provider from "../model/email_providers/update_provider.js"; import type * as model_email_providers_update_provider_status from "../model/email_providers/update_provider_status.js"; +import type * as model_email_providers_validators from "../model/email_providers/validators.js"; import type * as model_integrations_create_integration_internal from "../model/integrations/create_integration_internal.js"; import type * as model_integrations_create_integration_logic from "../model/integrations/create_integration_logic.js"; import type * as model_integrations_delete_integration from "../model/integrations/delete_integration.js"; @@ -276,6 +281,7 @@ import type * as model_integrations_types from "../model/integrations/types.js"; import type * as model_integrations_update_integration_internal from "../model/integrations/update_integration_internal.js"; import type * as model_integrations_update_integration_logic from "../model/integrations/update_integration_logic.js"; import type * as model_integrations_update_sync_stats from "../model/integrations/update_sync_stats.js"; +import type * as model_integrations_validators from "../model/integrations/validators.js"; import type * as model_onedrive_create_sync_configs_logic from "../model/onedrive/create_sync_configs_logic.js"; import type * as model_onedrive_get_user_token_logic from "../model/onedrive/get_user_token_logic.js"; import type * as model_onedrive_index from "../model/onedrive/index.js"; @@ -294,6 +300,7 @@ import type * as model_organizations_index from "../model/organizations/index.js import type * as model_organizations_save_default_workflows from "../model/organizations/save_default_workflows.js"; import type * as model_organizations_types from "../model/organizations/types.js"; import type * as model_organizations_update_organization from "../model/organizations/update_organization.js"; +import type * as model_organizations_validators from "../model/organizations/validators.js"; import type * as model_products_create_product from "../model/products/create_product.js"; import type * as model_products_create_product_public from "../model/products/create_product_public.js"; import type * as model_products_delete_product from "../model/products/delete_product.js"; @@ -309,6 +316,7 @@ import type * as model_products_types from "../model/products/types.js"; import type * as model_products_update_product from "../model/products/update_product.js"; import type * as model_products_update_products from "../model/products/update_products.js"; import type * as model_products_upsert_product_translation from "../model/products/upsert_product_translation.js"; +import type * as model_products_validators from "../model/products/validators.js"; import type * as model_threads_create_chat_thread from "../model/threads/create_chat_thread.js"; import type * as model_threads_delete_chat_thread from "../model/threads/delete_chat_thread.js"; import type * as model_threads_get_latest_thread_with_message_count from "../model/threads/get_latest_thread_with_message_count.js"; @@ -331,6 +339,7 @@ import type * as model_tone_of_voice_save_generated_tone from "../model/tone_of_ import type * as model_tone_of_voice_types from "../model/tone_of_voice/types.js"; import type * as model_tone_of_voice_update_example_message from "../model/tone_of_voice/update_example_message.js"; import type * as model_tone_of_voice_upsert_tone_of_voice from "../model/tone_of_voice/upsert_tone_of_voice.js"; +import type * as model_tone_of_voice_validators from "../model/tone_of_voice/validators.js"; import type * as model_trusted_headers_authenticate_create_session_for_trusted_user from "../model/trusted_headers_authenticate/create_session_for_trusted_user.js"; import type * as model_trusted_headers_authenticate_find_or_create_user_from_headers from "../model/trusted_headers_authenticate/find_or_create_user_from_headers.js"; import type * as model_trusted_headers_authenticate_get_user_by_id from "../model/trusted_headers_authenticate/get_user_by_id.js"; @@ -358,6 +367,7 @@ import type * as model_websites_rescan_website from "../model/websites/rescan_we import type * as model_websites_search_websites from "../model/websites/search_websites.js"; import type * as model_websites_types from "../model/websites/types.js"; import type * as model_websites_update_website from "../model/websites/update_website.js"; +import type * as model_websites_validators from "../model/websites/validators.js"; import type * as model_wf_definitions_activate_version from "../model/wf_definitions/activate_version.js"; import type * as model_wf_definitions_create_draft_from_active from "../model/wf_definitions/create_draft_from_active.js"; import type * as model_wf_definitions_create_workflow from "../model/wf_definitions/create_workflow.js"; @@ -382,6 +392,7 @@ import type * as model_wf_definitions_types from "../model/wf_definitions/types. import type * as model_wf_definitions_update_draft from "../model/wf_definitions/update_draft.js"; import type * as model_wf_definitions_update_workflow from "../model/wf_definitions/update_workflow.js"; import type * as model_wf_definitions_update_workflow_status from "../model/wf_definitions/update_workflow_status.js"; +import type * as model_wf_definitions_validators from "../model/wf_definitions/validators.js"; import type * as model_wf_executions_complete_execution from "../model/wf_executions/complete_execution.js"; import type * as model_wf_executions_fail_execution from "../model/wf_executions/fail_execution.js"; import type * as model_wf_executions_get_execution from "../model/wf_executions/get_execution.js"; @@ -398,6 +409,7 @@ import type * as model_wf_executions_types from "../model/wf_executions/types.js import type * as model_wf_executions_update_execution_metadata from "../model/wf_executions/update_execution_metadata.js"; import type * as model_wf_executions_update_execution_status from "../model/wf_executions/update_execution_status.js"; import type * as model_wf_executions_update_execution_variables from "../model/wf_executions/update_execution_variables.js"; +import type * as model_wf_executions_validators from "../model/wf_executions/validators.js"; import type * as model_wf_step_defs_create_step from "../model/wf_step_defs/create_step.js"; import type * as model_wf_step_defs_delete_step from "../model/wf_step_defs/delete_step.js"; import type * as model_wf_step_defs_get_next_step_in_sequence from "../model/wf_step_defs/get_next_step_in_sequence.js"; @@ -410,6 +422,7 @@ import type * as model_wf_step_defs_list_workflow_steps from "../model/wf_step_d import type * as model_wf_step_defs_reorder_steps from "../model/wf_step_defs/reorder_steps.js"; import type * as model_wf_step_defs_types from "../model/wf_step_defs/types.js"; import type * as model_wf_step_defs_update_step from "../model/wf_step_defs/update_step.js"; +import type * as model_wf_step_defs_validators from "../model/wf_step_defs/validators.js"; import type * as model_workflow_processing_records_ast_helpers_extract_comparison from "../model/workflow_processing_records/ast_helpers/extract_comparison.js"; import type * as model_workflow_processing_records_ast_helpers_extract_literal_value from "../model/workflow_processing_records/ast_helpers/extract_literal_value.js"; import type * as model_workflow_processing_records_ast_helpers_get_full_field_path from "../model/workflow_processing_records/ast_helpers/get_full_field_path.js"; @@ -819,6 +832,7 @@ declare const fullApi: ApiFromModules<{ "model/approvals/remove_recommended_product": typeof model_approvals_remove_recommended_product; "model/approvals/types": typeof model_approvals_types; "model/approvals/update_approval_status": typeof model_approvals_update_approval_status; + "model/approvals/validators": typeof model_approvals_validators; "model/chat_agent/auto_summarize_if_needed": typeof model_chat_agent_auto_summarize_if_needed; "model/chat_agent/chat_with_agent": typeof model_chat_agent_chat_with_agent; "model/chat_agent/context_management/check_and_summarize": typeof model_chat_agent_context_management_check_and_summarize; @@ -859,6 +873,7 @@ declare const fullApi: ApiFromModules<{ "model/conversations/update_conversation": typeof model_conversations_update_conversation; "model/conversations/update_conversation_message": typeof model_conversations_update_conversation_message; "model/conversations/update_conversations": typeof model_conversations_update_conversations; + "model/conversations/validators": typeof model_conversations_validators; "model/customers/bulk_create_customers": typeof model_customers_bulk_create_customers; "model/customers/create_customer": typeof model_customers_create_customer; "model/customers/create_customer_public": typeof model_customers_create_customer_public; @@ -877,6 +892,7 @@ declare const fullApi: ApiFromModules<{ "model/customers/update_customer": typeof model_customers_update_customer; "model/customers/update_customer_metadata": typeof model_customers_update_customer_metadata; "model/customers/update_customers": typeof model_customers_update_customers; + "model/customers/validators": typeof model_customers_validators; "model/documents/analyze_pptx": typeof model_documents_analyze_pptx; "model/documents/check_membership": typeof model_documents_check_membership; "model/documents/create_document": typeof model_documents_create_document; @@ -903,6 +919,7 @@ declare const fullApi: ApiFromModules<{ "model/documents/types": typeof model_documents_types; "model/documents/update_document": typeof model_documents_update_document; "model/documents/upload_base64_to_storage": typeof model_documents_upload_base64_to_storage; + "model/documents/validators": typeof model_documents_validators; "model/email_providers/create_oauth2_provider_logic": typeof model_email_providers_create_oauth2_provider_logic; "model/email_providers/create_provider_internal": typeof model_email_providers_create_provider_internal; "model/email_providers/create_provider_logic": typeof model_email_providers_create_provider_logic; @@ -929,6 +946,7 @@ declare const fullApi: ApiFromModules<{ "model/email_providers/update_oauth2_tokens": typeof model_email_providers_update_oauth2_tokens; "model/email_providers/update_provider": typeof model_email_providers_update_provider; "model/email_providers/update_provider_status": typeof model_email_providers_update_provider_status; + "model/email_providers/validators": typeof model_email_providers_validators; "model/integrations/create_integration_internal": typeof model_integrations_create_integration_internal; "model/integrations/create_integration_logic": typeof model_integrations_create_integration_logic; "model/integrations/delete_integration": typeof model_integrations_delete_integration; @@ -948,6 +966,7 @@ declare const fullApi: ApiFromModules<{ "model/integrations/update_integration_internal": typeof model_integrations_update_integration_internal; "model/integrations/update_integration_logic": typeof model_integrations_update_integration_logic; "model/integrations/update_sync_stats": typeof model_integrations_update_sync_stats; + "model/integrations/validators": typeof model_integrations_validators; "model/onedrive/create_sync_configs_logic": typeof model_onedrive_create_sync_configs_logic; "model/onedrive/get_user_token_logic": typeof model_onedrive_get_user_token_logic; "model/onedrive/index": typeof model_onedrive_index; @@ -966,6 +985,7 @@ declare const fullApi: ApiFromModules<{ "model/organizations/save_default_workflows": typeof model_organizations_save_default_workflows; "model/organizations/types": typeof model_organizations_types; "model/organizations/update_organization": typeof model_organizations_update_organization; + "model/organizations/validators": typeof model_organizations_validators; "model/products/create_product": typeof model_products_create_product; "model/products/create_product_public": typeof model_products_create_product_public; "model/products/delete_product": typeof model_products_delete_product; @@ -981,6 +1001,7 @@ declare const fullApi: ApiFromModules<{ "model/products/update_product": typeof model_products_update_product; "model/products/update_products": typeof model_products_update_products; "model/products/upsert_product_translation": typeof model_products_upsert_product_translation; + "model/products/validators": typeof model_products_validators; "model/threads/create_chat_thread": typeof model_threads_create_chat_thread; "model/threads/delete_chat_thread": typeof model_threads_delete_chat_thread; "model/threads/get_latest_thread_with_message_count": typeof model_threads_get_latest_thread_with_message_count; @@ -1003,6 +1024,7 @@ declare const fullApi: ApiFromModules<{ "model/tone_of_voice/types": typeof model_tone_of_voice_types; "model/tone_of_voice/update_example_message": typeof model_tone_of_voice_update_example_message; "model/tone_of_voice/upsert_tone_of_voice": typeof model_tone_of_voice_upsert_tone_of_voice; + "model/tone_of_voice/validators": typeof model_tone_of_voice_validators; "model/trusted_headers_authenticate/create_session_for_trusted_user": typeof model_trusted_headers_authenticate_create_session_for_trusted_user; "model/trusted_headers_authenticate/find_or_create_user_from_headers": typeof model_trusted_headers_authenticate_find_or_create_user_from_headers; "model/trusted_headers_authenticate/get_user_by_id": typeof model_trusted_headers_authenticate_get_user_by_id; @@ -1030,6 +1052,7 @@ declare const fullApi: ApiFromModules<{ "model/websites/search_websites": typeof model_websites_search_websites; "model/websites/types": typeof model_websites_types; "model/websites/update_website": typeof model_websites_update_website; + "model/websites/validators": typeof model_websites_validators; "model/wf_definitions/activate_version": typeof model_wf_definitions_activate_version; "model/wf_definitions/create_draft_from_active": typeof model_wf_definitions_create_draft_from_active; "model/wf_definitions/create_workflow": typeof model_wf_definitions_create_workflow; @@ -1054,6 +1077,7 @@ declare const fullApi: ApiFromModules<{ "model/wf_definitions/update_draft": typeof model_wf_definitions_update_draft; "model/wf_definitions/update_workflow": typeof model_wf_definitions_update_workflow; "model/wf_definitions/update_workflow_status": typeof model_wf_definitions_update_workflow_status; + "model/wf_definitions/validators": typeof model_wf_definitions_validators; "model/wf_executions/complete_execution": typeof model_wf_executions_complete_execution; "model/wf_executions/fail_execution": typeof model_wf_executions_fail_execution; "model/wf_executions/get_execution": typeof model_wf_executions_get_execution; @@ -1070,6 +1094,7 @@ declare const fullApi: ApiFromModules<{ "model/wf_executions/update_execution_metadata": typeof model_wf_executions_update_execution_metadata; "model/wf_executions/update_execution_status": typeof model_wf_executions_update_execution_status; "model/wf_executions/update_execution_variables": typeof model_wf_executions_update_execution_variables; + "model/wf_executions/validators": typeof model_wf_executions_validators; "model/wf_step_defs/create_step": typeof model_wf_step_defs_create_step; "model/wf_step_defs/delete_step": typeof model_wf_step_defs_delete_step; "model/wf_step_defs/get_next_step_in_sequence": typeof model_wf_step_defs_get_next_step_in_sequence; @@ -1082,6 +1107,7 @@ declare const fullApi: ApiFromModules<{ "model/wf_step_defs/reorder_steps": typeof model_wf_step_defs_reorder_steps; "model/wf_step_defs/types": typeof model_wf_step_defs_types; "model/wf_step_defs/update_step": typeof model_wf_step_defs_update_step; + "model/wf_step_defs/validators": typeof model_wf_step_defs_validators; "model/workflow_processing_records/ast_helpers/extract_comparison": typeof model_workflow_processing_records_ast_helpers_extract_comparison; "model/workflow_processing_records/ast_helpers/extract_literal_value": typeof model_workflow_processing_records_ast_helpers_extract_literal_value; "model/workflow_processing_records/ast_helpers/get_full_field_path": typeof model_workflow_processing_records_ast_helpers_get_full_field_path; diff --git a/services/platform/convex/conversations.ts b/services/platform/convex/conversations.ts index 163c071603..d71ab729c3 100644 --- a/services/platform/convex/conversations.ts +++ b/services/platform/convex/conversations.ts @@ -19,7 +19,7 @@ import { conversationWithMessagesValidator, bulkOperationResultValidator, conversationStatusValidator, -} from './model/conversations/types'; +} from './model/conversations/validators'; // ============================================================================= // INTERNAL OPERATIONS (without RLS) diff --git a/services/platform/convex/customers.ts b/services/platform/convex/customers.ts index 38f19cd618..8a290ff6eb 100644 --- a/services/platform/convex/customers.ts +++ b/services/platform/convex/customers.ts @@ -17,7 +17,7 @@ import { customerSourceValidator, customerAddressValidator, customerValidator, -} from './model/customers/types'; +} from './model/customers/validators'; // ============================================================================= // INTERNAL OPERATIONS (without RLS) diff --git a/services/platform/convex/documents.ts b/services/platform/convex/documents.ts index e0c798afac..164c3baa43 100644 --- a/services/platform/convex/documents.ts +++ b/services/platform/convex/documents.ts @@ -22,10 +22,10 @@ import type { Id } from './_generated/dataModel'; // Import model functions and validators import * as DocumentsModel from './model/documents'; import { - DocumentItem, - DocumentListResponseValidator as DocumentListResponse, - DocumentRecord, -} from './model/documents/types'; + documentItemValidator as DocumentItem, + documentListResponseValidator as DocumentListResponse, + documentRecordValidator as DocumentRecord, +} from './model/documents/validators'; import { ragAction } from './workflow/actions/rag/rag_action'; // ============================================================================= diff --git a/services/platform/convex/model/approvals/index.ts b/services/platform/convex/model/approvals/index.ts index 08f4b31ac1..8b2c713a60 100644 --- a/services/platform/convex/model/approvals/index.ts +++ b/services/platform/convex/model/approvals/index.ts @@ -2,7 +2,10 @@ * Central export point for approvals model */ -// Types and validators +// Validators +export * from './validators'; + +// Types export * from './types'; // Internal operations diff --git a/services/platform/convex/model/approvals/types.ts b/services/platform/convex/model/approvals/types.ts index 97d65ba667..330b3a2035 100644 --- a/services/platform/convex/model/approvals/types.ts +++ b/services/platform/convex/model/approvals/types.ts @@ -3,75 +3,28 @@ * @updated for threadId support */ -import { v } from 'convex/values'; -import { Id } from '../../_generated/dataModel'; +import type { Infer } from 'convex/values'; +import type { Id } from '../../_generated/dataModel'; +import { + approvalItemValidator, + approvalPriorityValidator, + approvalResourceTypeValidator, + approvalStatusValidator, +} from './validators'; // ============================================================================= -// VALIDATORS +// INFERRED TYPES (from validators) // ============================================================================= -/** - * Approval status validator - */ -export const approvalStatusValidator = v.union( - v.literal('pending'), - v.literal('approved'), - v.literal('rejected'), -); - -/** - * Approval priority validator - */ -export const approvalPriorityValidator = v.union( - v.literal('low'), - v.literal('medium'), - v.literal('high'), - v.literal('urgent'), -); - -/** - * Approval resource type validator - */ -export const approvalResourceTypeValidator = v.union( - v.literal('conversations'), - v.literal('product_recommendation'), - v.literal('integration_operation'), -); - -/** - * Approval item validator (for responses) - */ -export const approvalItemValidator = v.object({ - _id: v.id('approvals'), - _creationTime: v.number(), - organizationId: v.string(), - wfExecutionId: v.optional(v.id('wfExecutions')), - stepSlug: v.optional(v.string()), - status: approvalStatusValidator, - approvedBy: v.optional(v.string()), - reviewedAt: v.optional(v.number()), - resourceType: approvalResourceTypeValidator, - resourceId: v.string(), - priority: approvalPriorityValidator, - dueDate: v.optional(v.number()), - executedAt: v.optional(v.number()), - executionError: v.optional(v.string()), - metadata: v.optional(v.any()), - threadId: v.optional(v.string()), - messageId: v.optional(v.string()), -}); +export type ApprovalStatus = Infer; +export type ApprovalPriority = Infer; +export type ApprovalResourceType = Infer; +export type ApprovalItem = Infer; // ============================================================================= -// TYPESCRIPT TYPES +// MANUAL TYPES (no corresponding validator) // ============================================================================= -export type ApprovalStatus = 'pending' | 'approved' | 'rejected'; -export type ApprovalPriority = 'low' | 'medium' | 'high' | 'urgent'; -export type ApprovalResourceType = - | 'conversations' - | 'product_recommendation' - | 'integration_operation'; - /** * Metadata for integration operation approvals */ @@ -112,34 +65,7 @@ export interface UpdateApprovalStatusArgs { comments?: string; } -export interface ListApprovalsArgs { - organizationId: string; - resourceType?: ApprovalResourceType; - status?: ApprovalStatus; - wfExecutionId?: Id<'wfExecutions'>; -} - export interface GetApprovalHistoryArgs { resourceType: ApprovalResourceType; resourceId: string; } - -export interface ApprovalItem { - _id: Id<'approvals'>; - _creationTime: number; - organizationId: string; - wfExecutionId?: Id<'wfExecutions'>; - stepSlug?: string; - status: ApprovalStatus; - approvedBy?: string; - reviewedAt?: number; - resourceType: ApprovalResourceType; - resourceId: string; - priority: ApprovalPriority; - dueDate?: number; - executedAt?: number; - executionError?: string; - metadata?: unknown; - threadId?: string; - messageId?: string; -} diff --git a/services/platform/convex/model/approvals/validators.ts b/services/platform/convex/model/approvals/validators.ts new file mode 100644 index 0000000000..cce67cdc14 --- /dev/null +++ b/services/platform/convex/model/approvals/validators.ts @@ -0,0 +1,56 @@ +/** + * Convex validators for approval operations + */ + +import { v } from 'convex/values'; + +/** + * Approval status validator + */ +export const approvalStatusValidator = v.union( + v.literal('pending'), + v.literal('approved'), + v.literal('rejected'), +); + +/** + * Approval priority validator + */ +export const approvalPriorityValidator = v.union( + v.literal('low'), + v.literal('medium'), + v.literal('high'), + v.literal('urgent'), +); + +/** + * Approval resource type validator + */ +export const approvalResourceTypeValidator = v.union( + v.literal('conversations'), + v.literal('product_recommendation'), + v.literal('integration_operation'), +); + +/** + * Approval item validator (for responses) + */ +export const approvalItemValidator = v.object({ + _id: v.id('approvals'), + _creationTime: v.number(), + organizationId: v.string(), + wfExecutionId: v.optional(v.id('wfExecutions')), + stepSlug: v.optional(v.string()), + status: approvalStatusValidator, + approvedBy: v.optional(v.string()), + reviewedAt: v.optional(v.number()), + resourceType: approvalResourceTypeValidator, + resourceId: v.string(), + priority: approvalPriorityValidator, + dueDate: v.optional(v.number()), + executedAt: v.optional(v.number()), + executionError: v.optional(v.string()), + metadata: v.optional(v.any()), + threadId: v.optional(v.string()), + messageId: v.optional(v.string()), +}); diff --git a/services/platform/convex/model/conversations/index.ts b/services/platform/convex/model/conversations/index.ts index 70f4f84cd6..4d7469b6ef 100644 --- a/services/platform/convex/model/conversations/index.ts +++ b/services/platform/convex/model/conversations/index.ts @@ -4,32 +4,11 @@ * Central export point for all conversation model functions */ -// Export types -export type { - CreateConversationArgs, - CreateConversationResult, - UpdateConversationsArgs, - UpdateConversationsResult, - QueryConversationsArgs, - QueryConversationsResult, - MessageInfo, - CustomerInfo, - ConversationItem, - ConversationListResponse, - BulkOperationResult, -} from './types'; - // Export validators -export { - conversationStatusValidator, - conversationPriorityValidator, - messageValidator, - customerInfoValidator, - conversationItemValidator, - conversationListResponseValidator, - conversationWithMessagesValidator, - bulkOperationResultValidator, -} from './types'; +export * from './validators'; + +// Export types +export * from './types'; // Export functions export { createConversation } from './create_conversation'; diff --git a/services/platform/convex/model/conversations/types.ts b/services/platform/convex/model/conversations/types.ts index e5deef760c..c20a8cc919 100644 --- a/services/platform/convex/model/conversations/types.ts +++ b/services/platform/convex/model/conversations/types.ts @@ -2,15 +2,38 @@ * Type definitions for conversation model */ -import { v } from 'convex/values'; -import type { Id, Doc } from '../../_generated/dataModel'; +import type { Infer } from 'convex/values'; +import type { Id } from '../../_generated/dataModel'; import { - approvalItemValidator, - ApprovalResourceType, -} from '../approvals/types'; + bulkOperationResultValidator, + conversationItemValidator, + conversationListResponseValidator, + conversationPriorityValidator, + conversationStatusValidator, + conversationWithMessagesValidator, + customerInfoValidator, + messageValidator, +} from './validators'; // ============================================================================= -// TYPESCRIPT INTERFACES +// INFERRED TYPES (from validators) +// ============================================================================= + +export type ConversationStatus = Infer; +export type ConversationPriority = Infer; +export type MessageInfo = Infer; +export type CustomerInfo = Infer; +export type ConversationItem = Infer; +export type ConversationListResponse = Infer< + typeof conversationListResponseValidator +>; +export type ConversationWithMessages = Infer< + typeof conversationWithMessagesValidator +>; +export type BulkOperationResult = Infer; + +// ============================================================================= +// MANUAL TYPES (no corresponding validator) // ============================================================================= export interface CreateConversationArgs { @@ -18,7 +41,7 @@ export interface CreateConversationArgs { customerId?: Id<'customers'>; externalMessageId?: string; subject?: string; - status?: 'open' | 'closed' | 'spam' | 'archived'; + status?: ConversationStatus; priority?: string; type?: string; channel?: string; @@ -28,15 +51,10 @@ export interface CreateConversationArgs { metadata?: unknown; } -export interface CreateConversationResult { - success: boolean; - conversationId: string; -} - export interface UpdateConversationsArgs { conversationId?: Id<'conversations'>; organizationId?: string; - status?: 'open' | 'closed' | 'spam' | 'archived'; + status?: ConversationStatus; priority?: string; updates: unknown; @@ -50,7 +68,7 @@ export interface UpdateConversationsResult { export interface QueryConversationsArgs { organizationId: string; - status?: 'open' | 'closed' | 'spam' | 'archived'; + status?: ConversationStatus; priority?: string; channel?: string; direction?: 'inbound' | 'outbound'; @@ -61,236 +79,3 @@ export interface QueryConversationsArgs { cursor: string | null; }; } - -export interface QueryConversationsResult { - items: Array>; - isDone: boolean; - continueCursor: string | null; - count: number; -} - -export interface MessageInfo { - id: string; - sender: string; - content: string; - timestamp: string; - isCustomer: boolean; - status: string; - attachment?: unknown; -} - -export interface CustomerInfo { - id: string; - name?: string; - email: string; - locale?: string; - status: string; - source?: string; - created_at: string; -} - -export interface ConversationItem { - _id: Id<'conversations'>; - _creationTime: number; - organizationId: string; - customerId?: Id<'customers'>; - externalMessageId?: string; - subject?: string; - status?: 'open' | 'closed' | 'spam' | 'archived'; - priority?: string; - type?: string; - channel?: string; - - metadata?: unknown; - // Computed fields - id: string; - title: string; - description: string; - customer_id: string; - business_id: string; - message_count: number; - unread_count: number; - last_message_at?: string; - last_read_at?: string; - resolved_at?: string; - resolved_by?: string; - created_at: string; - updated_at: string; - customer: CustomerInfo; - messages: MessageInfo[]; - pendingApproval?: { - _id: Id<'approvals'>; - _creationTime: number; - organizationId: string; - wfExecutionId?: Id<'wfExecutions'>; - stepSlug?: string; - status: 'pending' | 'approved' | 'rejected'; - approvedBy?: string; - reviewedAt?: number; - resourceType: ApprovalResourceType; - resourceId: string; - priority: 'low' | 'medium' | 'high' | 'urgent'; - dueDate?: number; - metadata?: unknown; - }; -} - -export interface ConversationListResponse { - conversations: ConversationItem[]; - total: number; - page: number; - limit: number; - totalPages: number; -} - -export interface BulkOperationResult { - successCount: number; - failedCount: number; - errors: string[]; -} - -// ============================================================================= -// VALIDATORS (for Convex function args/returns) -// ============================================================================= - -/** - * Conversation status validator - */ -export const conversationStatusValidator = v.union( - v.literal('open'), - v.literal('closed'), - v.literal('spam'), - v.literal('archived'), -); - -/** - * Conversation priority validator - */ -export const conversationPriorityValidator = v.union( - v.literal('low'), - v.literal('normal'), - v.literal('high'), - v.literal('urgent'), -); - -/** - * Message validator - */ -export const messageValidator = v.object({ - id: v.string(), - sender: v.string(), - content: v.string(), - timestamp: v.string(), - isCustomer: v.boolean(), - status: v.string(), - attachment: v.optional(v.any()), -}); - -/** - * Customer info validator (for conversation responses) - */ -export const customerInfoValidator = v.object({ - id: v.string(), - name: v.optional(v.string()), - email: v.string(), - locale: v.optional(v.string()), - status: v.string(), - source: v.optional(v.string()), - created_at: v.string(), -}); - -/** - * Conversation item validator (for list responses) - */ -export const conversationItemValidator = v.object({ - _id: v.id('conversations'), - _creationTime: v.number(), - organizationId: v.string(), - customerId: v.optional(v.id('customers')), - externalMessageId: v.optional(v.string()), - subject: v.optional(v.string()), - status: v.optional(conversationStatusValidator), - priority: v.optional(v.string()), - type: v.optional(v.string()), - channel: v.optional(v.string()), - direction: v.optional(v.union(v.literal('inbound'), v.literal('outbound'))), - providerId: v.optional(v.id('emailProviders')), - lastMessageAt: v.optional(v.number()), - - metadata: v.optional(v.any()), - // Computed fields for frontend compatibility - id: v.string(), - title: v.string(), - description: v.string(), - customer_id: v.string(), - business_id: v.string(), - message_count: v.number(), - unread_count: v.number(), - last_message_at: v.optional(v.string()), - last_read_at: v.optional(v.string()), - resolved_at: v.optional(v.string()), - resolved_by: v.optional(v.string()), - created_at: v.string(), - updated_at: v.string(), - customer: customerInfoValidator, - messages: v.array(messageValidator), - pendingApproval: v.optional(approvalItemValidator), -}); - -/** - * Conversation list response validator - */ -export const conversationListResponseValidator = v.object({ - conversations: v.array(conversationItemValidator), - total: v.number(), - page: v.number(), - limit: v.number(), - totalPages: v.number(), -}); - -/** - * Conversation with messages validator - */ -export const conversationWithMessagesValidator = v.object({ - _id: v.id('conversations'), - _creationTime: v.number(), - organizationId: v.string(), - customerId: v.optional(v.id('customers')), - externalMessageId: v.optional(v.string()), - subject: v.optional(v.string()), - status: v.optional(conversationStatusValidator), - priority: v.optional(v.string()), - type: v.optional(v.string()), - channel: v.optional(v.string()), - direction: v.optional(v.union(v.literal('inbound'), v.literal('outbound'))), - providerId: v.optional(v.id('emailProviders')), - lastMessageAt: v.optional(v.number()), - - metadata: v.optional(v.any()), - // Computed fields - id: v.string(), - title: v.string(), - description: v.string(), - customer_id: v.string(), - business_id: v.string(), - message_count: v.number(), - unread_count: v.number(), - last_message_at: v.optional(v.string()), - last_read_at: v.optional(v.string()), - resolved_at: v.optional(v.string()), - resolved_by: v.optional(v.string()), - created_at: v.string(), - updated_at: v.string(), - customer: customerInfoValidator, - messages: v.array(messageValidator), - pendingApproval: v.optional(approvalItemValidator), -}); - -/** - * Bulk operation result validator - */ -export const bulkOperationResultValidator = v.object({ - successCount: v.number(), - failedCount: v.number(), - errors: v.array(v.string()), -}); diff --git a/services/platform/convex/model/conversations/validators.ts b/services/platform/convex/model/conversations/validators.ts new file mode 100644 index 0000000000..186fcfc0f3 --- /dev/null +++ b/services/platform/convex/model/conversations/validators.ts @@ -0,0 +1,148 @@ +/** + * Convex validators for conversation model + */ + +import { v } from 'convex/values'; +import { approvalItemValidator } from '../approvals/validators'; + +/** + * Conversation status validator + */ +export const conversationStatusValidator = v.union( + v.literal('open'), + v.literal('closed'), + v.literal('spam'), + v.literal('archived'), +); + +/** + * Conversation priority validator + */ +export const conversationPriorityValidator = v.union( + v.literal('low'), + v.literal('normal'), + v.literal('high'), + v.literal('urgent'), +); + +/** + * Message validator + */ +export const messageValidator = v.object({ + id: v.string(), + sender: v.string(), + content: v.string(), + timestamp: v.string(), + isCustomer: v.boolean(), + status: v.string(), + attachment: v.optional(v.any()), +}); + +/** + * Customer info validator (for conversation responses) + */ +export const customerInfoValidator = v.object({ + id: v.string(), + name: v.optional(v.string()), + email: v.string(), + locale: v.optional(v.string()), + status: v.string(), + source: v.optional(v.string()), + created_at: v.string(), +}); + +/** + * Conversation item validator (for list responses) + */ +export const conversationItemValidator = v.object({ + _id: v.id('conversations'), + _creationTime: v.number(), + organizationId: v.string(), + customerId: v.optional(v.id('customers')), + externalMessageId: v.optional(v.string()), + subject: v.optional(v.string()), + status: v.optional(conversationStatusValidator), + priority: v.optional(v.string()), + type: v.optional(v.string()), + channel: v.optional(v.string()), + direction: v.optional(v.union(v.literal('inbound'), v.literal('outbound'))), + providerId: v.optional(v.id('emailProviders')), + lastMessageAt: v.optional(v.number()), + + metadata: v.optional(v.any()), + // Computed fields for frontend compatibility + id: v.string(), + title: v.string(), + description: v.string(), + customer_id: v.string(), + business_id: v.string(), + message_count: v.number(), + unread_count: v.number(), + last_message_at: v.optional(v.string()), + last_read_at: v.optional(v.string()), + resolved_at: v.optional(v.string()), + resolved_by: v.optional(v.string()), + created_at: v.string(), + updated_at: v.string(), + customer: customerInfoValidator, + messages: v.array(messageValidator), + pendingApproval: v.optional(approvalItemValidator), +}); + +/** + * Conversation list response validator + */ +export const conversationListResponseValidator = v.object({ + conversations: v.array(conversationItemValidator), + total: v.number(), + page: v.number(), + limit: v.number(), + totalPages: v.number(), +}); + +/** + * Conversation with messages validator + */ +export const conversationWithMessagesValidator = v.object({ + _id: v.id('conversations'), + _creationTime: v.number(), + organizationId: v.string(), + customerId: v.optional(v.id('customers')), + externalMessageId: v.optional(v.string()), + subject: v.optional(v.string()), + status: v.optional(conversationStatusValidator), + priority: v.optional(v.string()), + type: v.optional(v.string()), + channel: v.optional(v.string()), + direction: v.optional(v.union(v.literal('inbound'), v.literal('outbound'))), + providerId: v.optional(v.id('emailProviders')), + lastMessageAt: v.optional(v.number()), + + metadata: v.optional(v.any()), + // Computed fields + id: v.string(), + title: v.string(), + description: v.string(), + customer_id: v.string(), + business_id: v.string(), + message_count: v.number(), + unread_count: v.number(), + last_message_at: v.optional(v.string()), + last_read_at: v.optional(v.string()), + resolved_at: v.optional(v.string()), + resolved_by: v.optional(v.string()), + created_at: v.string(), + updated_at: v.string(), + customer: customerInfoValidator, + messages: v.array(messageValidator), + pendingApproval: v.optional(approvalItemValidator), +}); + +/** + * Bulk operation result validator + */ +export const bulkOperationResultValidator = v.object({ + successCount: v.number(), + failedCount: v.number(), + errors: v.array(v.string()), +}); diff --git a/services/platform/convex/model/customers/index.ts b/services/platform/convex/model/customers/index.ts index 8988aba917..25b4db0788 100644 --- a/services/platform/convex/model/customers/index.ts +++ b/services/platform/convex/model/customers/index.ts @@ -4,7 +4,10 @@ * This file exports all customer business logic functions and types. */ -// Export types and validators +// Export validators +export * from './validators'; + +// Export types export * from './types'; // Export internal business logic functions diff --git a/services/platform/convex/model/customers/types.ts b/services/platform/convex/model/customers/types.ts index 190a3c119f..84ce7a4af2 100644 --- a/services/platform/convex/model/customers/types.ts +++ b/services/platform/convex/model/customers/types.ts @@ -1,62 +1,27 @@ /** - * Type definitions and validators for customer operations + * Type definitions for customer operations */ -import { v } from 'convex/values'; +import type { Infer } from 'convex/values'; import type { Id } from '../../_generated/dataModel'; +import { + customerAddressValidator, + customerSourceValidator, + customerStatusValidator, + customerValidator, +} from './validators'; // ============================================================================= -// VALIDATORS +// INFERRED TYPES (from validators) // ============================================================================= -/** - * Customer status validator - */ -export const customerStatusValidator = v.union( - v.literal('active'), - v.literal('churned'), - v.literal('potential'), -); - -/** - * Customer source validator - */ -export const customerSourceValidator = v.union( - v.literal('manual_import'), - v.literal('file_upload'), - v.literal('circuly'), -); - -/** - * Customer address validator - */ -export const customerAddressValidator = v.object({ - street: v.optional(v.string()), - city: v.optional(v.string()), - state: v.optional(v.string()), - country: v.optional(v.string()), - postalCode: v.optional(v.string()), -}); - -/** - * Customer document validator (matches schema) - */ -export const customerValidator = v.object({ - _id: v.id('customers'), - _creationTime: v.number(), - organizationId: v.string(), - name: v.optional(v.string()), - email: v.optional(v.string()), - externalId: v.optional(v.union(v.string(), v.number())), - status: v.optional(customerStatusValidator), - source: customerSourceValidator, - locale: v.optional(v.string()), - address: v.optional(customerAddressValidator), - metadata: v.optional(v.any()), -}); +export type CustomerStatus = Infer; +export type CustomerSource = Infer; +export type CustomerAddress = Infer; +export type Customer = Infer; // ============================================================================= -// TYPESCRIPT TYPES +// MANUAL TYPES (no corresponding validator) // ============================================================================= /** diff --git a/services/platform/convex/model/customers/validators.ts b/services/platform/convex/model/customers/validators.ts new file mode 100644 index 0000000000..2f65ebf21d --- /dev/null +++ b/services/platform/convex/model/customers/validators.ts @@ -0,0 +1,51 @@ +/** + * Convex validators for customer operations + */ + +import { v } from 'convex/values'; + +/** + * Customer status validator + */ +export const customerStatusValidator = v.union( + v.literal('active'), + v.literal('churned'), + v.literal('potential'), +); + +/** + * Customer source validator + */ +export const customerSourceValidator = v.union( + v.literal('manual_import'), + v.literal('file_upload'), + v.literal('circuly'), +); + +/** + * Customer address validator + */ +export const customerAddressValidator = v.object({ + street: v.optional(v.string()), + city: v.optional(v.string()), + state: v.optional(v.string()), + country: v.optional(v.string()), + postalCode: v.optional(v.string()), +}); + +/** + * Customer document validator (matches schema) + */ +export const customerValidator = v.object({ + _id: v.id('customers'), + _creationTime: v.number(), + organizationId: v.string(), + name: v.optional(v.string()), + email: v.optional(v.string()), + externalId: v.optional(v.union(v.string(), v.number())), + status: v.optional(customerStatusValidator), + source: customerSourceValidator, + locale: v.optional(v.string()), + address: v.optional(customerAddressValidator), + metadata: v.optional(v.any()), +}); diff --git a/services/platform/convex/model/documents/index.ts b/services/platform/convex/model/documents/index.ts index 3c955e6876..4658bf51c2 100644 --- a/services/platform/convex/model/documents/index.ts +++ b/services/platform/convex/model/documents/index.ts @@ -4,30 +4,11 @@ * Central export point for all document model functions */ -// Export types -export type { - CreateDocumentArgs, - CreateDocumentResult, - QueryDocumentsArgs, - QueryDocumentsResult, - DocumentItemResponse, - DocumentListResponse, - CheckMembershipArgs, - MembershipResult, - DocumentSourceType, - DocumentOutputFormat, - GenerateDocumentArgs, - GenerateDocumentResult, - ListDocumentsByExtensionArgs, - ListDocumentsByExtensionResult, -} from './types'; - // Export validators -export { - DocumentItem, - DocumentListResponseValidator, - DocumentRecord, -} from './types'; +export * from './validators'; + +// Export types +export * from './types'; // Export functions export { createDocument } from './create_document'; diff --git a/services/platform/convex/model/documents/types.ts b/services/platform/convex/model/documents/types.ts index cce6d672fe..facec4e201 100644 --- a/services/platform/convex/model/documents/types.ts +++ b/services/platform/convex/model/documents/types.ts @@ -2,8 +2,29 @@ * Type definitions for document model */ -import { v } from 'convex/values'; +import type { Infer } from 'convex/values'; import type { Id } from '../../_generated/dataModel'; +import { + documentItemValidator, + documentListResponseValidator, + documentRecordValidator, + ragStatusValidator, + sourceProviderValidator, +} from './validators'; + +// ============================================================================= +// INFERRED TYPES (from validators) +// ============================================================================= + +export type RagStatus = Infer; +export type SourceProvider = Infer; +export type DocumentItemResponse = Infer; +export type DocumentListResponse = Infer; +export type DocumentRecord = Infer; + +// ============================================================================= +// MANUAL TYPES (no corresponding validator) +// ============================================================================= export interface CreateDocumentArgs { organizationId: string; @@ -14,7 +35,7 @@ export interface CreateDocumentArgs { mimeType?: string; extension?: string; metadata?: unknown; - sourceProvider?: 'onedrive' | 'upload'; + sourceProvider?: SourceProvider; externalItemId?: string; } @@ -25,71 +46,13 @@ export interface CreateDocumentResult { export interface QueryDocumentsArgs { organizationId: string; - - sourceProvider?: 'onedrive' | 'upload'; - + sourceProvider?: SourceProvider; paginationOpts: { numItems: number; cursor: string | null; }; } -export interface QueryDocumentsResult { - items: Array<{ - _id: Id<'documents'>; - _creationTime: number; - organizationId: string; - title?: string; - - content?: string; - fileId?: Id<'_storage'>; - mimeType?: string; - extension?: string; - metadata?: unknown; - sourceProvider?: 'onedrive' | 'upload'; - externalItemId?: string; - }>; - isDone: boolean; - continueCursor: string | null; - count: number; -} - -/** RAG ingestion status for a document */ -export type RagStatus = 'pending' | 'queued' | 'running' | 'completed' | 'failed' | 'not_indexed' | 'stale'; - -export interface DocumentItemResponse { - id: string; - name?: string; - type: 'file' | 'folder'; - size?: number; - mimeType?: string; - extension?: string; - storagePath?: string; - sourceProvider?: 'onedrive' | 'upload'; - sourceMode?: 'auto' | 'manual'; - lastModified?: number; - syncConfigId?: string; - isDirectlySelected?: boolean; - url?: string; - ragStatus?: RagStatus; - /** Timestamp (in seconds) when the document was indexed */ - ragIndexedAt?: number; - /** Error message (for failed status) */ - ragError?: string; -} - -export interface DocumentListResponse { - success: boolean; - items: DocumentItemResponse[]; - totalItems: number; - pagination?: { - hasNextPage: boolean; - currentPage: number; - pageSize: number; - }; - error?: string; -} - export interface CheckMembershipArgs { organizationId: string; userId: string; @@ -176,83 +139,3 @@ export interface GenerateDocumentResult { /** File extension (e.g. pdf, png, jpeg) */ extension: string; } - -// ============================================================================= -// VALIDATORS (for Convex function args/returns) -// ============================================================================= - -/** - * RAG status validator - */ -export const RagStatusValidator = v.optional( - v.union( - v.literal('pending'), - v.literal('queued'), - v.literal('running'), - v.literal('completed'), - v.literal('failed'), - v.literal('not_indexed'), - v.literal('stale'), - ), -); - -/** - * Document item validator (for public API responses) - */ -export const DocumentItem = v.object({ - id: v.string(), - name: v.optional(v.string()), - type: v.union(v.literal('file'), v.literal('folder')), - size: v.optional(v.number()), - mimeType: v.optional(v.string()), - extension: v.optional(v.string()), - storagePath: v.optional(v.string()), - sourceProvider: v.optional( - v.union(v.literal('onedrive'), v.literal('upload')), - ), - sourceMode: v.optional(v.union(v.literal('auto'), v.literal('manual'))), - lastModified: v.optional(v.number()), - syncConfigId: v.optional(v.string()), - isDirectlySelected: v.optional(v.boolean()), - url: v.optional(v.string()), - ragStatus: RagStatusValidator, - ragIndexedAt: v.optional(v.number()), - ragError: v.optional(v.string()), -}); - -/** - * Document list response validator - */ -export const DocumentListResponseValidator = v.object({ - success: v.boolean(), - items: v.array(DocumentItem), - totalItems: v.number(), - pagination: v.optional( - v.object({ - hasNextPage: v.boolean(), - currentPage: v.number(), - pageSize: v.number(), - }), - ), - error: v.optional(v.string()), -}); - -/** - * Document record validator (raw database document) - */ -export const DocumentRecord = v.object({ - _id: v.id('documents'), - _creationTime: v.number(), - organizationId: v.string(), - title: v.optional(v.string()), - - content: v.optional(v.string()), - fileId: v.optional(v.id('_storage')), - mimeType: v.optional(v.string()), - extension: v.optional(v.string()), - metadata: v.optional(v.any()), - sourceProvider: v.optional( - v.union(v.literal('onedrive'), v.literal('upload')), - ), - externalItemId: v.optional(v.string()), -}); diff --git a/services/platform/convex/model/documents/validators.ts b/services/platform/convex/model/documents/validators.ts new file mode 100644 index 0000000000..516f2a24f4 --- /dev/null +++ b/services/platform/convex/model/documents/validators.ts @@ -0,0 +1,103 @@ +/** + * Convex validators for document model + */ + +import { v } from 'convex/values'; + +/** + * RAG status validator + */ +export const ragStatusValidator = v.union( + v.literal('pending'), + v.literal('queued'), + v.literal('running'), + v.literal('completed'), + v.literal('failed'), + v.literal('not_indexed'), + v.literal('stale'), +); + +/** + * Source provider validator + */ +export const sourceProviderValidator = v.union( + v.literal('onedrive'), + v.literal('upload'), +); + +/** + * Source mode validator + */ +export const sourceModeValidator = v.union( + v.literal('auto'), + v.literal('manual'), +); + +/** + * Document type validator + */ +export const documentTypeValidator = v.union( + v.literal('file'), + v.literal('folder'), +); + +/** + * Document item validator (for public API responses) + */ +export const documentItemValidator = v.object({ + id: v.string(), + name: v.optional(v.string()), + type: documentTypeValidator, + size: v.optional(v.number()), + mimeType: v.optional(v.string()), + extension: v.optional(v.string()), + storagePath: v.optional(v.string()), + sourceProvider: v.optional(sourceProviderValidator), + sourceMode: v.optional(sourceModeValidator), + lastModified: v.optional(v.number()), + syncConfigId: v.optional(v.string()), + isDirectlySelected: v.optional(v.boolean()), + url: v.optional(v.string()), + ragStatus: v.optional(ragStatusValidator), + ragIndexedAt: v.optional(v.number()), + ragError: v.optional(v.string()), +}); + +/** + * Pagination validator + */ +export const paginationValidator = v.object({ + hasNextPage: v.boolean(), + currentPage: v.number(), + pageSize: v.number(), +}); + +/** + * Document list response validator + */ +export const documentListResponseValidator = v.object({ + success: v.boolean(), + items: v.array(documentItemValidator), + totalItems: v.number(), + pagination: v.optional(paginationValidator), + error: v.optional(v.string()), +}); + +/** + * Document record validator (raw database document) + */ +export const documentRecordValidator = v.object({ + _id: v.id('documents'), + _creationTime: v.number(), + organizationId: v.string(), + title: v.optional(v.string()), + + content: v.optional(v.string()), + fileId: v.optional(v.id('_storage')), + mimeType: v.optional(v.string()), + extension: v.optional(v.string()), + metadata: v.optional(v.any()), + sourceProvider: v.optional(sourceProviderValidator), + externalItemId: v.optional(v.string()), +}); + diff --git a/services/platform/convex/model/email_providers/index.ts b/services/platform/convex/model/email_providers/index.ts index b5bf08e8c1..e09dd428af 100644 --- a/services/platform/convex/model/email_providers/index.ts +++ b/services/platform/convex/model/email_providers/index.ts @@ -2,6 +2,7 @@ * Email providers model - central export point */ +export * from './validators'; export * from './types'; export * from './update_provider_status'; export * from './get_provider_by_id'; diff --git a/services/platform/convex/model/email_providers/types.ts b/services/platform/convex/model/email_providers/types.ts index a10527ce6c..7739ddd831 100644 --- a/services/platform/convex/model/email_providers/types.ts +++ b/services/platform/convex/model/email_providers/types.ts @@ -1,34 +1,32 @@ /** - * Types and validators for email providers + * Type definitions for email providers */ -import { v } from 'convex/values'; +import type { Infer } from 'convex/values'; import type { Doc } from '../../_generated/dataModel'; +import { + emailProviderAuthMethodValidator, + emailProviderStatusValidator, + emailProviderVendorValidator, + imapConfigValidator, + smtpConfigValidator, +} from './validators'; // ============================================================================= -// TypeScript Types +// INFERRED TYPES (from validators) // ============================================================================= -export type EmailProviderVendor = - | 'gmail' - | 'outlook' - | 'smtp' - | 'resend' - | 'other'; -export type EmailProviderAuthMethod = 'password' | 'oauth2'; -export type EmailProviderStatus = 'active' | 'error' | 'testing'; +export type EmailProviderVendor = Infer; +export type EmailProviderAuthMethod = Infer< + typeof emailProviderAuthMethodValidator +>; +export type EmailProviderStatus = Infer; +export type SmtpConfig = Infer; +export type ImapConfig = Infer; -export interface SmtpConfig { - host: string; - port: number; - secure: boolean; -} - -export interface ImapConfig { - host: string; - port: number; - secure: boolean; -} +// ============================================================================= +// MANUAL TYPES (no corresponding validator) +// ============================================================================= export interface UpdateProviderStatusArgs { providerId: Doc<'emailProviders'>['_id']; @@ -40,38 +38,3 @@ export interface UpdateProviderStatusArgs { export interface GetProviderByIdArgs { providerId: Doc<'emailProviders'>['_id']; } - -// ============================================================================= -// Convex Validators -// ============================================================================= - -export const emailProviderVendorValidator = v.union( - v.literal('gmail'), - v.literal('outlook'), - v.literal('smtp'), - v.literal('resend'), - v.literal('other'), -); - -export const emailProviderAuthMethodValidator = v.union( - v.literal('password'), - v.literal('oauth2'), -); - -export const emailProviderStatusValidator = v.union( - v.literal('active'), - v.literal('error'), - v.literal('testing'), -); - -export const smtpConfigValidator = v.object({ - host: v.string(), - port: v.number(), - secure: v.boolean(), -}); - -export const imapConfigValidator = v.object({ - host: v.string(), - port: v.number(), - secure: v.boolean(), -}); diff --git a/services/platform/convex/model/email_providers/validators.ts b/services/platform/convex/model/email_providers/validators.ts new file mode 100644 index 0000000000..11b4ab5ac5 --- /dev/null +++ b/services/platform/convex/model/email_providers/validators.ts @@ -0,0 +1,36 @@ +/** + * Convex validators for email providers + */ + +import { v } from 'convex/values'; + +export const emailProviderVendorValidator = v.union( + v.literal('gmail'), + v.literal('outlook'), + v.literal('smtp'), + v.literal('resend'), + v.literal('other'), +); + +export const emailProviderAuthMethodValidator = v.union( + v.literal('password'), + v.literal('oauth2'), +); + +export const emailProviderStatusValidator = v.union( + v.literal('active'), + v.literal('error'), + v.literal('testing'), +); + +export const smtpConfigValidator = v.object({ + host: v.string(), + port: v.number(), + secure: v.boolean(), +}); + +export const imapConfigValidator = v.object({ + host: v.string(), + port: v.number(), + secure: v.boolean(), +}); diff --git a/services/platform/convex/model/integrations/index.ts b/services/platform/convex/model/integrations/index.ts index 2df1e6c908..5fe3aee912 100644 --- a/services/platform/convex/model/integrations/index.ts +++ b/services/platform/convex/model/integrations/index.ts @@ -2,7 +2,10 @@ * Central export point for integrations model */ -// Types and validators +// Validators +export * from './validators'; + +// Types export * from './types'; // Connection testing diff --git a/services/platform/convex/model/integrations/types.ts b/services/platform/convex/model/integrations/types.ts index 2c9afa73fc..3185f34dae 100644 --- a/services/platform/convex/model/integrations/types.ts +++ b/services/platform/convex/model/integrations/types.ts @@ -2,322 +2,57 @@ * Type definitions for integration operations */ -import { v } from 'convex/values'; +import type { Infer } from 'convex/values'; +import { + apiKeyAuthEncryptedValidator, + apiKeyAuthValidator, + authMethodValidator, + basicAuthEncryptedValidator, + basicAuthValidator, + capabilitiesValidator, + connectionConfigValidator, + connectorConfigValidator, + connectorOperationValidator, + integrationTypeValidator, + oauth2AuthEncryptedValidator, + oauth2AuthValidator, + operationTypeValidator, + sqlConnectionConfigValidator, + sqlEngineValidator, + sqlOperationValidator, + statusValidator, + testConnectionResultValidator, +} from './validators'; // ============================================================================= -// VALIDATORS +// INFERRED TYPES (from validators) // ============================================================================= -/** - * Integration type validator - */ -export const integrationTypeValidator = v.union( - v.literal('rest_api'), - v.literal('sql'), -); - -/** - * Auth method validator - */ -export const authMethodValidator = v.union( - v.literal('api_key'), - v.literal('bearer_token'), - v.literal('basic_auth'), - v.literal('oauth2'), -); - -/** - * Status validator - */ -export const statusValidator = v.union( - v.literal('active'), - v.literal('inactive'), - v.literal('error'), - v.literal('testing'), -); - -/** - * API key auth validator - */ -export const apiKeyAuthValidator = v.object({ - key: v.string(), - keyPrefix: v.optional(v.string()), -}); - -/** - * API key auth encrypted validator - */ -export const apiKeyAuthEncryptedValidator = v.object({ - keyEncrypted: v.string(), - keyPrefix: v.optional(v.string()), -}); - -/** - * Basic auth validator - */ -export const basicAuthValidator = v.object({ - username: v.string(), - password: v.string(), -}); - -/** - * Basic auth encrypted validator - */ -export const basicAuthEncryptedValidator = v.object({ - username: v.string(), - passwordEncrypted: v.string(), -}); - -/** - * OAuth2 auth validator - */ -export const oauth2AuthValidator = v.object({ - accessToken: v.string(), - refreshToken: v.optional(v.string()), - tokenExpiry: v.optional(v.number()), - scopes: v.optional(v.array(v.string())), -}); - -/** - * OAuth2 auth encrypted validator - */ -export const oauth2AuthEncryptedValidator = v.object({ - accessTokenEncrypted: v.string(), - refreshTokenEncrypted: v.optional(v.string()), - tokenExpiry: v.optional(v.number()), - scopes: v.optional(v.array(v.string())), -}); - -/** - * Connection config validator - */ -export const connectionConfigValidator = v.object({ - domain: v.optional(v.string()), - apiVersion: v.optional(v.string()), - apiEndpoint: v.optional(v.string()), - timeout: v.optional(v.number()), - rateLimit: v.optional(v.number()), -}); - -/** - * Capabilities validator - */ -export const capabilitiesValidator = v.object({ - canSync: v.optional(v.boolean()), - canPush: v.optional(v.boolean()), - canWebhook: v.optional(v.boolean()), - syncFrequency: v.optional(v.string()), -}); - -/** - * Operation type validator (read vs write) - */ -export const operationTypeValidator = v.union( - v.literal('read'), - v.literal('write'), -); - -/** - * Connector operation validator - */ -export const connectorOperationValidator = v.object({ - name: v.string(), - title: v.optional(v.string()), - description: v.optional(v.string()), - parametersSchema: v.optional(v.any()), - // Operation type: 'read' or 'write' - defaults to 'read' if not specified - operationType: v.optional(operationTypeValidator), - // Whether this operation requires user approval before execution - // Defaults to true for write operations, false for read operations - requiresApproval: v.optional(v.boolean()), -}); - -/** - * Connector config validator - */ -export const connectorConfigValidator = v.object({ - code: v.string(), - version: v.number(), - operations: v.array(connectorOperationValidator), - secretBindings: v.array(v.string()), - allowedHosts: v.optional(v.array(v.string())), - timeoutMs: v.optional(v.number()), -}); - -/** - * SQL engine validator - */ -export const sqlEngineValidator = v.union( - v.literal('mssql'), - v.literal('postgres'), - v.literal('mysql'), -); - -/** - * SQL connection config validator - */ -export const sqlConnectionConfigValidator = v.object({ - engine: sqlEngineValidator, - server: v.string(), - port: v.optional(v.number()), - database: v.string(), - readOnly: v.optional(v.boolean()), - options: v.optional( - v.object({ - encrypt: v.optional(v.boolean()), - trustServerCertificate: v.optional(v.boolean()), - connectionTimeout: v.optional(v.number()), - requestTimeout: v.optional(v.number()), - }), - ), - security: v.optional( - v.object({ - maxResultRows: v.optional(v.number()), - queryTimeoutMs: v.optional(v.number()), - maxConnectionPoolSize: v.optional(v.number()), - }), - ), -}); - -/** - * SQL operation validator - */ -export const sqlOperationValidator = v.object({ - name: v.string(), - title: v.optional(v.string()), - description: v.optional(v.string()), - query: v.string(), - parametersSchema: v.optional(v.any()), - // Operation type: 'read' or 'write' - defaults to 'read' if not specified - operationType: v.optional(operationTypeValidator), - // Whether this operation requires user approval before execution - // Defaults to true for write operations, false for read operations - requiresApproval: v.optional(v.boolean()), -}); - -/** - * Test connection result validator - */ -export const testConnectionResultValidator = v.object({ - success: v.boolean(), - message: v.string(), -}); +export type IntegrationType = Infer; +export type AuthMethod = Infer; +export type Status = Infer; +export type OperationType = Infer; +export type SqlEngine = Infer; + +export type ApiKeyAuth = Infer; +export type ApiKeyAuthEncrypted = Infer; +export type BasicAuth = Infer; +export type BasicAuthEncrypted = Infer; +export type OAuth2Auth = Infer; +export type OAuth2AuthEncrypted = Infer; + +export type ConnectionConfig = Infer; +export type Capabilities = Infer; +export type ConnectorOperation = Infer; +export type ConnectorConfig = Infer; +export type SqlConnectionConfig = Infer; +export type SqlOperation = Infer; +export type TestConnectionResult = Infer; // ============================================================================= -// TYPESCRIPT TYPES +// MANUAL TYPES (no corresponding validator) // ============================================================================= -export type IntegrationType = 'rest_api' | 'sql'; -export type SqlEngine = 'mssql' | 'postgres' | 'mysql'; -export type AuthMethod = 'api_key' | 'bearer_token' | 'basic_auth' | 'oauth2'; -export type Status = 'active' | 'inactive' | 'error' | 'testing'; -export type OperationType = 'read' | 'write'; - -export interface ApiKeyAuth { - key: string; - keyPrefix?: string; -} - -export interface ApiKeyAuthEncrypted { - keyEncrypted: string; - keyPrefix?: string; -} - -export interface BasicAuth { - username: string; - password: string; -} - -export interface BasicAuthEncrypted { - username: string; - passwordEncrypted: string; -} - -export interface OAuth2Auth { - accessToken: string; - refreshToken?: string; - tokenExpiry?: number; - scopes?: string[]; -} - -export interface OAuth2AuthEncrypted { - accessTokenEncrypted: string; - refreshTokenEncrypted?: string; - tokenExpiry?: number; - scopes?: string[]; -} - -export interface ConnectionConfig { - domain?: string; - apiVersion?: string; - apiEndpoint?: string; - timeout?: number; - rateLimit?: number; -} - -export interface Capabilities { - canSync?: boolean; - canPush?: boolean; - canWebhook?: boolean; - syncFrequency?: string; -} - -export interface ConnectorOperation { - name: string; - title?: string; - description?: string; - parametersSchema?: unknown; - /** Operation type: 'read' or 'write' - defaults to 'read' if not specified */ - operationType?: OperationType; - /** Whether this operation requires user approval before execution */ - requiresApproval?: boolean; -} - -export interface ConnectorConfig { - code: string; - version: number; - operations: ConnectorOperation[]; - secretBindings: string[]; - allowedHosts?: string[]; - timeoutMs?: number; -} - -export interface SqlConnectionConfig { - engine: SqlEngine; - server: string; - port?: number; - database: string; - readOnly?: boolean; - options?: { - encrypt?: boolean; - trustServerCertificate?: boolean; - connectionTimeout?: number; - requestTimeout?: number; - }; - security?: { - maxResultRows?: number; - queryTimeoutMs?: number; - maxConnectionPoolSize?: number; - }; -} - -export interface SqlOperation { - name: string; - title?: string; - description?: string; - query: string; - parametersSchema?: unknown; - /** Operation type: 'read' or 'write' - defaults to 'read' if not specified */ - operationType?: OperationType; - /** Whether this operation requires user approval before execution */ - requiresApproval?: boolean; -} - -export interface TestConnectionResult { - success: boolean; - message: string; -} - export interface DecryptedCredentials { name: string; connectionConfig?: ConnectionConfig; diff --git a/services/platform/convex/model/integrations/validators.ts b/services/platform/convex/model/integrations/validators.ts new file mode 100644 index 0000000000..bc44ab747b --- /dev/null +++ b/services/platform/convex/model/integrations/validators.ts @@ -0,0 +1,200 @@ +/** + * Convex validators for integration operations + */ + +import { v } from 'convex/values'; + +/** + * Integration type validator + */ +export const integrationTypeValidator = v.union( + v.literal('rest_api'), + v.literal('sql'), +); + +/** + * Auth method validator + */ +export const authMethodValidator = v.union( + v.literal('api_key'), + v.literal('bearer_token'), + v.literal('basic_auth'), + v.literal('oauth2'), +); + +/** + * Status validator + */ +export const statusValidator = v.union( + v.literal('active'), + v.literal('inactive'), + v.literal('error'), + v.literal('testing'), +); + +/** + * API key auth validator + */ +export const apiKeyAuthValidator = v.object({ + key: v.string(), + keyPrefix: v.optional(v.string()), +}); + +/** + * API key auth encrypted validator + */ +export const apiKeyAuthEncryptedValidator = v.object({ + keyEncrypted: v.string(), + keyPrefix: v.optional(v.string()), +}); + +/** + * Basic auth validator + */ +export const basicAuthValidator = v.object({ + username: v.string(), + password: v.string(), +}); + +/** + * Basic auth encrypted validator + */ +export const basicAuthEncryptedValidator = v.object({ + username: v.string(), + passwordEncrypted: v.string(), +}); + +/** + * OAuth2 auth validator + */ +export const oauth2AuthValidator = v.object({ + accessToken: v.string(), + refreshToken: v.optional(v.string()), + tokenExpiry: v.optional(v.number()), + scopes: v.optional(v.array(v.string())), +}); + +/** + * OAuth2 auth encrypted validator + */ +export const oauth2AuthEncryptedValidator = v.object({ + accessTokenEncrypted: v.string(), + refreshTokenEncrypted: v.optional(v.string()), + tokenExpiry: v.optional(v.number()), + scopes: v.optional(v.array(v.string())), +}); + +/** + * Connection config validator + */ +export const connectionConfigValidator = v.object({ + domain: v.optional(v.string()), + apiVersion: v.optional(v.string()), + apiEndpoint: v.optional(v.string()), + timeout: v.optional(v.number()), + rateLimit: v.optional(v.number()), +}); + +/** + * Capabilities validator + */ +export const capabilitiesValidator = v.object({ + canSync: v.optional(v.boolean()), + canPush: v.optional(v.boolean()), + canWebhook: v.optional(v.boolean()), + syncFrequency: v.optional(v.string()), +}); + +/** + * Operation type validator (read vs write) + */ +export const operationTypeValidator = v.union( + v.literal('read'), + v.literal('write'), +); + +/** + * Connector operation validator + */ +export const connectorOperationValidator = v.object({ + name: v.string(), + title: v.optional(v.string()), + description: v.optional(v.string()), + parametersSchema: v.optional(v.any()), + // Operation type: 'read' or 'write' - defaults to 'read' if not specified + operationType: v.optional(operationTypeValidator), + // Whether this operation requires user approval before execution + // Defaults to true for write operations, false for read operations + requiresApproval: v.optional(v.boolean()), +}); + +/** + * Connector config validator + */ +export const connectorConfigValidator = v.object({ + code: v.string(), + version: v.number(), + operations: v.array(connectorOperationValidator), + secretBindings: v.array(v.string()), + allowedHosts: v.optional(v.array(v.string())), + timeoutMs: v.optional(v.number()), +}); + +/** + * SQL engine validator + */ +export const sqlEngineValidator = v.union( + v.literal('mssql'), + v.literal('postgres'), + v.literal('mysql'), +); + +/** + * SQL connection config validator + */ +export const sqlConnectionConfigValidator = v.object({ + engine: sqlEngineValidator, + server: v.string(), + port: v.optional(v.number()), + database: v.string(), + readOnly: v.optional(v.boolean()), + options: v.optional( + v.object({ + encrypt: v.optional(v.boolean()), + trustServerCertificate: v.optional(v.boolean()), + connectionTimeout: v.optional(v.number()), + requestTimeout: v.optional(v.number()), + }), + ), + security: v.optional( + v.object({ + maxResultRows: v.optional(v.number()), + queryTimeoutMs: v.optional(v.number()), + maxConnectionPoolSize: v.optional(v.number()), + }), + ), +}); + +/** + * SQL operation validator + */ +export const sqlOperationValidator = v.object({ + name: v.string(), + title: v.optional(v.string()), + description: v.optional(v.string()), + query: v.string(), + parametersSchema: v.optional(v.any()), + // Operation type: 'read' or 'write' - defaults to 'read' if not specified + operationType: v.optional(operationTypeValidator), + // Whether this operation requires user approval before execution + // Defaults to true for write operations, false for read operations + requiresApproval: v.optional(v.boolean()), +}); + +/** + * Test connection result validator + */ +export const testConnectionResultValidator = v.object({ + success: v.boolean(), + message: v.string(), +}); diff --git a/services/platform/convex/model/organizations/index.ts b/services/platform/convex/model/organizations/index.ts index 3ef6c4ee48..a7f273657a 100644 --- a/services/platform/convex/model/organizations/index.ts +++ b/services/platform/convex/model/organizations/index.ts @@ -2,7 +2,10 @@ * Central export point for organizations model */ -// Types and validators +// Validators +export * from './validators'; + +// Types export * from './types'; // Model operations diff --git a/services/platform/convex/model/organizations/types.ts b/services/platform/convex/model/organizations/types.ts index 4e003bcd2f..25c18a2c5d 100644 --- a/services/platform/convex/model/organizations/types.ts +++ b/services/platform/convex/model/organizations/types.ts @@ -1,28 +1,12 @@ /** - * Type definitions and validators for organizations + * Type definitions for organizations */ -import { v } from 'convex/values'; +import type { Infer } from 'convex/values'; +import { memberRoleValidator } from './validators'; -/** - * Organization validator - */ -export const organizationValidator = v.object({ - _id: v.string(), - _creationTime: v.number(), - name: v.string(), - slug: v.optional(v.string()), - logoId: v.optional(v.id('_storage')), - metadata: v.optional(v.any()), -}); +// ============================================================================= +// INFERRED TYPES (from validators) +// ============================================================================= -/** - * Member role validator - */ -export const memberRoleValidator = v.union( - v.literal('Disabled'), - v.literal('Admin'), - v.literal('Developer'), - v.literal('Editor'), - v.literal('Member'), -); +export type MemberRole = Infer; diff --git a/services/platform/convex/model/organizations/validators.ts b/services/platform/convex/model/organizations/validators.ts new file mode 100644 index 0000000000..9067b78d17 --- /dev/null +++ b/services/platform/convex/model/organizations/validators.ts @@ -0,0 +1,28 @@ +/** + * Convex validators for organizations + */ + +import { v } from 'convex/values'; + +/** + * Organization validator + */ +export const organizationValidator = v.object({ + _id: v.string(), + _creationTime: v.number(), + name: v.string(), + slug: v.optional(v.string()), + logoId: v.optional(v.id('_storage')), + metadata: v.optional(v.any()), +}); + +/** + * Member role validator + */ +export const memberRoleValidator = v.union( + v.literal('Disabled'), + v.literal('Admin'), + v.literal('Developer'), + v.literal('Editor'), + v.literal('Member'), +); diff --git a/services/platform/convex/model/products/index.ts b/services/platform/convex/model/products/index.ts index 9b53fd0425..e22baf5af7 100644 --- a/services/platform/convex/model/products/index.ts +++ b/services/platform/convex/model/products/index.ts @@ -2,7 +2,10 @@ * Central export point for products model */ -// Types and validators +// Validators +export * from './validators'; + +// Types export * from './types'; // Internal operations diff --git a/services/platform/convex/model/products/types.ts b/services/platform/convex/model/products/types.ts index 6eef34be0e..5e7b5ce00b 100644 --- a/services/platform/convex/model/products/types.ts +++ b/services/platform/convex/model/products/types.ts @@ -2,118 +2,32 @@ * Type definitions for product operations */ -import { v } from 'convex/values'; -import { Id } from '../../_generated/dataModel'; +import type { Infer } from 'convex/values'; +import type { Id } from '../../_generated/dataModel'; +import { + productItemValidator, + productListResponseValidator, + productSortByValidator, + productStatusValidator, + productTranslationValidator, + sortOrderValidator, +} from './validators'; // ============================================================================= -// VALIDATORS +// INFERRED TYPES (from validators) // ============================================================================= -/** - * Product translation validator - */ -export const productTranslationValidator = v.object({ - language: v.string(), - name: v.optional(v.string()), - description: v.optional(v.string()), - category: v.optional(v.string()), - tags: v.optional(v.array(v.string())), - metadata: v.optional(v.any()), - createdAt: v.optional(v.number()), - lastUpdated: v.number(), -}); - -/** - * Product status validator - */ -export const productStatusValidator = v.union( - v.literal('active'), - v.literal('inactive'), - v.literal('draft'), - v.literal('archived'), -); - -/** - * Product relationship type validator - */ - -/** - * Product sort field validator - */ -export const productSortByValidator = v.union( - v.literal('name'), - v.literal('createdAt'), - v.literal('lastUpdated'), - v.literal('stock'), - v.literal('price'), -); - -/** - * Sort order validator - */ -export const sortOrderValidator = v.union(v.literal('asc'), v.literal('desc')); - -/** - * Product item validator (for responses) - */ -export const productItemValidator = v.object({ - id: v.string(), - name: v.string(), - description: v.optional(v.string()), - imageUrl: v.optional(v.string()), - stock: v.optional(v.number()), - price: v.optional(v.number()), - currency: v.optional(v.string()), - category: v.optional(v.string()), - tags: v.optional(v.array(v.string())), - status: v.optional(productStatusValidator), - lastUpdated: v.number(), - createdAt: v.number(), - relatedProductsCount: v.optional(v.number()), - translations: v.optional(v.array(productTranslationValidator)), - metadata: v.optional(v.record(v.string(), v.any())), -}); - -/** - * Product list response validator - */ -export const productListResponseValidator = v.object({ - products: v.array(productItemValidator), - total: v.number(), - hasNextPage: v.boolean(), - currentPage: v.number(), - pageSize: v.number(), - error: v.optional(v.string()), -}); - -/** - * Product relationship validator - */ +export type ProductStatus = Infer; +export type ProductSortBy = Infer; +export type SortOrder = Infer; +export type ProductTranslation = Infer; +export type ProductItem = Infer; +export type ProductListResponse = Infer; // ============================================================================= -// TYPESCRIPT TYPES +// MANUAL TYPES (no corresponding validator) // ============================================================================= -export type ProductStatus = 'active' | 'inactive' | 'draft' | 'archived'; -export type ProductSortBy = - | 'name' - | 'createdAt' - | 'lastUpdated' - | 'stock' - | 'price'; -export type SortOrder = 'asc' | 'desc'; - -export interface ProductTranslation { - language: string; - name?: string; - description?: string; - category?: string; - tags?: Array; - metadata?: unknown; - createdAt?: number; - lastUpdated: number; -} - export interface CreateProductResult { success: boolean; productId: Id<'products'>; @@ -124,30 +38,3 @@ export interface UpdateProductsResult { updatedCount: number; updatedIds: Array>; } - -export interface ProductItem { - id: string; - name: string; - description?: string; - imageUrl?: string; - stock?: number; - price?: number; - currency?: string; - category?: string; - tags?: Array; - status?: ProductStatus; - lastUpdated: number; - createdAt: number; - relatedProductsCount?: number; - translations?: Array; - metadata?: Record; -} - -export interface ProductListResponse { - products: Array; - total: number; - hasNextPage: boolean; - currentPage: number; - pageSize: number; - error?: string; -} diff --git a/services/platform/convex/model/products/validators.ts b/services/platform/convex/model/products/validators.ts new file mode 100644 index 0000000000..333bad226d --- /dev/null +++ b/services/platform/convex/model/products/validators.ts @@ -0,0 +1,78 @@ +/** + * Convex validators for product operations + */ + +import { v } from 'convex/values'; + +/** + * Product translation validator + */ +export const productTranslationValidator = v.object({ + language: v.string(), + name: v.optional(v.string()), + description: v.optional(v.string()), + category: v.optional(v.string()), + tags: v.optional(v.array(v.string())), + metadata: v.optional(v.any()), + createdAt: v.optional(v.number()), + lastUpdated: v.number(), +}); + +/** + * Product status validator + */ +export const productStatusValidator = v.union( + v.literal('active'), + v.literal('inactive'), + v.literal('draft'), + v.literal('archived'), +); + +/** + * Product sort field validator + */ +export const productSortByValidator = v.union( + v.literal('name'), + v.literal('createdAt'), + v.literal('lastUpdated'), + v.literal('stock'), + v.literal('price'), +); + +/** + * Sort order validator + */ +export const sortOrderValidator = v.union(v.literal('asc'), v.literal('desc')); + +/** + * Product item validator (for responses) + */ +export const productItemValidator = v.object({ + id: v.string(), + name: v.string(), + description: v.optional(v.string()), + imageUrl: v.optional(v.string()), + stock: v.optional(v.number()), + price: v.optional(v.number()), + currency: v.optional(v.string()), + category: v.optional(v.string()), + tags: v.optional(v.array(v.string())), + status: v.optional(productStatusValidator), + lastUpdated: v.number(), + createdAt: v.number(), + relatedProductsCount: v.optional(v.number()), + translations: v.optional(v.array(productTranslationValidator)), + metadata: v.optional(v.record(v.string(), v.any())), +}); + +/** + * Product list response validator + */ +export const productListResponseValidator = v.object({ + products: v.array(productItemValidator), + total: v.number(), + hasNextPage: v.boolean(), + currentPage: v.number(), + pageSize: v.number(), + error: v.optional(v.string()), +}); diff --git a/services/platform/convex/model/tone_of_voice/index.ts b/services/platform/convex/model/tone_of_voice/index.ts index 28d2f0a71f..ac2067371d 100644 --- a/services/platform/convex/model/tone_of_voice/index.ts +++ b/services/platform/convex/model/tone_of_voice/index.ts @@ -2,7 +2,10 @@ * Central export point for tone of voice model */ -// Types and validators +// Validators +export * from './validators'; + +// Types export * from './types'; // Query operations diff --git a/services/platform/convex/model/tone_of_voice/types.ts b/services/platform/convex/model/tone_of_voice/types.ts index 92a8716b6d..dd5fcb00cd 100644 --- a/services/platform/convex/model/tone_of_voice/types.ts +++ b/services/platform/convex/model/tone_of_voice/types.ts @@ -2,98 +2,23 @@ * Type definitions for tone of voice operations */ -import { v } from 'convex/values'; -import { Id } from '../../_generated/dataModel'; +import type { Infer } from 'convex/values'; +import { + exampleMessageContentValidator, + exampleMessageValidator, + generateToneResponseValidator, + toneOfVoiceValidator, + toneOfVoiceWithExamplesValidator, +} from './validators'; // ============================================================================= -// VALIDATORS +// INFERRED TYPES (from validators) // ============================================================================= -/** - * Tone of voice validator - */ -export const toneOfVoiceValidator = v.object({ - _id: v.id('toneOfVoice'), - _creationTime: v.number(), - organizationId: v.string(), - generatedTone: v.optional(v.string()), - lastUpdated: v.number(), - metadata: v.optional(v.any()), -}); - -/** - * Example message validator - */ -export const exampleMessageValidator = v.object({ - _id: v.id('exampleMessages'), - _creationTime: v.number(), - organizationId: v.string(), - toneOfVoiceId: v.id('toneOfVoice'), - content: v.string(), - createdAt: v.number(), - updatedAt: v.number(), - metadata: v.optional(v.any()), -}); - -/** - * Tone of voice with examples validator - */ -export const toneOfVoiceWithExamplesValidator = v.object({ - toneOfVoice: toneOfVoiceValidator, - examples: v.array(exampleMessageValidator), -}); - -/** - * Example message content validator (for AI processing) - */ -export const exampleMessageContentValidator = v.object({ - content: v.string(), -}); - -/** - * Generate tone response validator - */ -export const generateToneResponseValidator = v.object({ - success: v.boolean(), - tone: v.optional(v.string()), - error: v.optional(v.string()), -}); - -// ============================================================================= -// TYPESCRIPT TYPES -// ============================================================================= - -export interface ToneOfVoice { - _id: Id<'toneOfVoice'>; - _creationTime: number; - organizationId: string; - generatedTone?: string; - lastUpdated: number; - metadata?: unknown; -} - -export interface ExampleMessage { - _id: Id<'exampleMessages'>; - _creationTime: number; - organizationId: string; - toneOfVoiceId: Id<'toneOfVoice'>; - content: string; - createdAt: number; - updatedAt: number; - metadata?: unknown; -} - -export interface ToneOfVoiceWithExamples { - toneOfVoice: ToneOfVoice; - examples: Array; -} - -export interface ExampleMessageContent { - content: string; -} - -export interface GenerateToneResponse { - success: boolean; - tone?: string; - error?: string; -} +export type ToneOfVoice = Infer; +export type ExampleMessage = Infer; +export type ToneOfVoiceWithExamples = Infer< + typeof toneOfVoiceWithExamplesValidator +>; +export type ExampleMessageContent = Infer; +export type GenerateToneResponse = Infer; diff --git a/services/platform/convex/model/tone_of_voice/validators.ts b/services/platform/convex/model/tone_of_voice/validators.ts new file mode 100644 index 0000000000..bbe24a1cd4 --- /dev/null +++ b/services/platform/convex/model/tone_of_voice/validators.ts @@ -0,0 +1,55 @@ +/** + * Convex validators for tone of voice operations + */ + +import { v } from 'convex/values'; + +/** + * Tone of voice validator + */ +export const toneOfVoiceValidator = v.object({ + _id: v.id('toneOfVoice'), + _creationTime: v.number(), + organizationId: v.string(), + generatedTone: v.optional(v.string()), + lastUpdated: v.number(), + metadata: v.optional(v.any()), +}); + +/** + * Example message validator + */ +export const exampleMessageValidator = v.object({ + _id: v.id('exampleMessages'), + _creationTime: v.number(), + organizationId: v.string(), + toneOfVoiceId: v.id('toneOfVoice'), + content: v.string(), + createdAt: v.number(), + updatedAt: v.number(), + metadata: v.optional(v.any()), +}); + +/** + * Tone of voice with examples validator + */ +export const toneOfVoiceWithExamplesValidator = v.object({ + toneOfVoice: toneOfVoiceValidator, + examples: v.array(exampleMessageValidator), +}); + +/** + * Example message content validator (for AI processing) + */ +export const exampleMessageContentValidator = v.object({ + content: v.string(), +}); + +/** + * Generate tone response validator + */ +export const generateToneResponseValidator = v.object({ + success: v.boolean(), + tone: v.optional(v.string()), + error: v.optional(v.string()), +}); diff --git a/services/platform/convex/model/websites/index.ts b/services/platform/convex/model/websites/index.ts index f7ba3da16b..0041bd0ff1 100644 --- a/services/platform/convex/model/websites/index.ts +++ b/services/platform/convex/model/websites/index.ts @@ -4,7 +4,10 @@ * This file exports all website business logic functions and types. */ -// Export types and validators +// Validators +export * from './validators'; + +// Types export * from './types'; // Export business logic functions diff --git a/services/platform/convex/model/websites/types.ts b/services/platform/convex/model/websites/types.ts index c328a7be3f..29baa8502b 100644 --- a/services/platform/convex/model/websites/types.ts +++ b/services/platform/convex/model/websites/types.ts @@ -1,58 +1,25 @@ /** - * Type definitions and validators for website operations + * Type definitions for website operations */ -import { v } from 'convex/values'; +import type { Infer } from 'convex/values'; import type { Id } from '../../_generated/dataModel'; +import { + websitePageValidator, + websiteStatusValidator, + websiteValidator, +} from './validators'; // ============================================================================= -// VALIDATORS +// INFERRED TYPES (from validators) // ============================================================================= -/** - * Website status validator - */ -export const websiteStatusValidator = v.union( - v.literal('active'), - v.literal('inactive'), - v.literal('error'), -); - -/** - * Website document validator (matches schema) - */ -export const websiteValidator = v.object({ - _id: v.id('websites'), - _creationTime: v.number(), - organizationId: v.string(), - domain: v.string(), - title: v.optional(v.string()), - description: v.optional(v.string()), - scanInterval: v.string(), - lastScannedAt: v.optional(v.number()), - status: v.optional(websiteStatusValidator), - metadata: v.optional(v.any()), -}); - -/** - * Website page document validator (matches schema) - */ -export const websitePageValidator = v.object({ - _id: v.id('websitePages'), - _creationTime: v.number(), - organizationId: v.string(), - websiteId: v.id('websites'), - url: v.string(), - title: v.optional(v.string()), - content: v.optional(v.string()), - wordCount: v.optional(v.number()), - lastCrawledAt: v.number(), - metadata: v.optional(v.record(v.string(), v.any())), - structuredData: v.optional(v.record(v.string(), v.any())), -}); +export type WebsiteStatus = Infer; +export type Website = Infer; +export type WebsitePage = Infer; // ============================================================================= -// TYPESCRIPT TYPES +// MANUAL TYPES (no corresponding validator) // ============================================================================= /** @@ -68,7 +35,7 @@ export interface GetWebsitesResult { description?: string; scanInterval: string; lastScannedAt?: number; - status?: 'active' | 'inactive' | 'error'; + status?: WebsiteStatus; metadata?: unknown; }>; isDone: boolean; @@ -96,7 +63,7 @@ export interface BulkWebsiteData { title?: string; description?: string; scanInterval: string; - status?: 'active' | 'inactive' | 'error'; + status?: WebsiteStatus; metadata?: unknown; } diff --git a/services/platform/convex/model/websites/validators.ts b/services/platform/convex/model/websites/validators.ts new file mode 100644 index 0000000000..bdfa6101d7 --- /dev/null +++ b/services/platform/convex/model/websites/validators.ts @@ -0,0 +1,47 @@ +/** + * Convex validators for website operations + */ + +import { v } from 'convex/values'; + +/** + * Website status validator + */ +export const websiteStatusValidator = v.union( + v.literal('active'), + v.literal('inactive'), + v.literal('error'), +); + +/** + * Website document validator (matches schema) + */ +export const websiteValidator = v.object({ + _id: v.id('websites'), + _creationTime: v.number(), + organizationId: v.string(), + domain: v.string(), + title: v.optional(v.string()), + description: v.optional(v.string()), + scanInterval: v.string(), + lastScannedAt: v.optional(v.number()), + status: v.optional(websiteStatusValidator), + metadata: v.optional(v.any()), +}); + +/** + * Website page document validator (matches schema) + */ +export const websitePageValidator = v.object({ + _id: v.id('websitePages'), + _creationTime: v.number(), + organizationId: v.string(), + websiteId: v.id('websites'), + url: v.string(), + title: v.optional(v.string()), + content: v.optional(v.string()), + wordCount: v.optional(v.number()), + lastCrawledAt: v.number(), + metadata: v.optional(v.record(v.string(), v.any())), + structuredData: v.optional(v.record(v.string(), v.any())), +}); diff --git a/services/platform/convex/model/wf_definitions/index.ts b/services/platform/convex/model/wf_definitions/index.ts index 449baa3fbc..a4341cf2ea 100644 --- a/services/platform/convex/model/wf_definitions/index.ts +++ b/services/platform/convex/model/wf_definitions/index.ts @@ -2,7 +2,10 @@ * Central export point for wf_definitions model */ -// Types and validators +// Validators +export * from './validators'; + +// Types export * from './types'; // Internal operations diff --git a/services/platform/convex/model/wf_definitions/types.ts b/services/platform/convex/model/wf_definitions/types.ts index 59d7ecfd2e..eb1a8c94b7 100644 --- a/services/platform/convex/model/wf_definitions/types.ts +++ b/services/platform/convex/model/wf_definitions/types.ts @@ -1,36 +1,28 @@ /** - * Types and validators for workflow definitions + * Type definitions for workflow definitions */ -import { v } from 'convex/values'; +import type { Infer } from 'convex/values'; import type { Doc, Id } from '../../_generated/dataModel'; +import { + workflowConfigValidator, + workflowStatusValidator, + workflowTypeValidator, +} from './validators'; // ============================================================================= -// TypeScript Types +// INFERRED TYPES (from validators) // ============================================================================= -export type WorkflowDefinition = Doc<'wfDefinitions'>; - -export type WorkflowStatus = 'draft' | 'active' | 'archived'; +export type WorkflowStatus = Infer; +export type WorkflowType = Infer; +export type WorkflowConfig = Infer; -export type WorkflowType = 'predefined'; +// ============================================================================= +// MANUAL TYPES (no corresponding validator) +// ============================================================================= -export interface WorkflowConfig { - timeout?: number; - retryPolicy?: { - maxRetries: number; - backoffMs: number; - }; - variables?: Record; - secrets?: Record< - string, - { - kind: 'inlineEncrypted'; - cipherText: string; - keyId?: string; - } - >; -} +export type WorkflowDefinition = Doc<'wfDefinitions'>; export interface WorkflowDefinitionWithFirstStep extends WorkflowDefinition { firstStepSlug: string | null; @@ -44,46 +36,3 @@ export interface ActivateVersionResult { activeVersionId: Id<'wfDefinitions'>; newDraftId: Id<'wfDefinitions'>; } - -// ============================================================================= -// Convex Validators -// ============================================================================= - -export const workflowStatusValidator = v.union( - v.literal('draft'), - v.literal('active'), - v.literal('archived'), -); - -export const workflowTypeValidator = v.literal('predefined'); - -export const workflowConfigValidator = v.object({ - timeout: v.optional(v.number()), - retryPolicy: v.optional( - v.object({ - maxRetries: v.number(), - backoffMs: v.number(), - }), - ), - variables: v.optional(v.record(v.string(), v.any())), - secrets: v.optional( - v.record( - v.string(), - v.object({ - kind: v.literal('inlineEncrypted'), - cipherText: v.string(), - keyId: v.optional(v.string()), - }), - ), - ), -}); - -export const workflowUpdateValidator = v.object({ - name: v.optional(v.string()), - description: v.optional(v.string()), - version: v.optional(v.string()), - status: v.optional(v.string()), - workflowType: v.optional(workflowTypeValidator), - config: v.optional(workflowConfigValidator), - metadata: v.optional(v.any()), -}); diff --git a/services/platform/convex/model/wf_definitions/validators.ts b/services/platform/convex/model/wf_definitions/validators.ts new file mode 100644 index 0000000000..769c944c8b --- /dev/null +++ b/services/platform/convex/model/wf_definitions/validators.ts @@ -0,0 +1,44 @@ +/** + * Convex validators for workflow definitions + */ + +import { v } from 'convex/values'; + +export const workflowStatusValidator = v.union( + v.literal('draft'), + v.literal('active'), + v.literal('archived'), +); + +export const workflowTypeValidator = v.literal('predefined'); + +export const workflowConfigValidator = v.object({ + timeout: v.optional(v.number()), + retryPolicy: v.optional( + v.object({ + maxRetries: v.number(), + backoffMs: v.number(), + }), + ), + variables: v.optional(v.record(v.string(), v.any())), + secrets: v.optional( + v.record( + v.string(), + v.object({ + kind: v.literal('inlineEncrypted'), + cipherText: v.string(), + keyId: v.optional(v.string()), + }), + ), + ), +}); + +export const workflowUpdateValidator = v.object({ + name: v.optional(v.string()), + description: v.optional(v.string()), + version: v.optional(v.string()), + status: v.optional(v.string()), + workflowType: v.optional(workflowTypeValidator), + config: v.optional(workflowConfigValidator), + metadata: v.optional(v.any()), +}); diff --git a/services/platform/convex/model/wf_executions/index.ts b/services/platform/convex/model/wf_executions/index.ts index fa050b131d..9a639d69d3 100644 --- a/services/platform/convex/model/wf_executions/index.ts +++ b/services/platform/convex/model/wf_executions/index.ts @@ -3,7 +3,10 @@ * Central export point for all workflow execution operations */ -// Export types +// Validators +export * from './validators'; + +// Types export * from './types'; // Export business logic functions diff --git a/services/platform/convex/model/wf_executions/types.ts b/services/platform/convex/model/wf_executions/types.ts index a889c2940b..cb72df59cb 100644 --- a/services/platform/convex/model/wf_executions/types.ts +++ b/services/platform/convex/model/wf_executions/types.ts @@ -1,12 +1,11 @@ /** - * Workflow Execution Types and Validators + * Type definitions for workflow executions */ -import { v } from 'convex/values'; import type { Doc, Id } from '../../_generated/dataModel'; // ============================================================================= -// TypeScript Types +// MANUAL TYPES (these rely on Doc types, not validators) // ============================================================================= export type WorkflowExecution = Doc<'wfExecutions'>; @@ -109,83 +108,3 @@ export interface PaginatedExecutionsResult { hasNextPage: boolean; hasPreviousPage: boolean; } - -// ============================================================================= -// Convex Validators -// ============================================================================= - -export const updateExecutionStatusArgsValidator = { - executionId: v.id('wfExecutions'), - status: v.string(), - currentStepSlug: v.optional(v.string()), - waitingFor: v.optional(v.string()), - error: v.optional(v.string()), -}; - -export const completeExecutionArgsValidator = { - executionId: v.id('wfExecutions'), - output: v.any(), - variablesSerialized: v.optional(v.string()), - variablesStorageId: v.optional(v.id('_storage')), -}; - -export const failExecutionArgsValidator = { - executionId: v.id('wfExecutions'), - error: v.string(), -}; - -export const patchExecutionArgsValidator = { - executionId: v.id('wfExecutions'), - updates: v.object({ - threadId: v.optional(v.string()), - currentStepSlug: v.optional(v.string()), - variables: v.optional(v.string()), - metadata: v.optional(v.string()), - }), -}; - -export const resumeExecutionArgsValidator = { - executionId: v.id('wfExecutions'), - variablesSerialized: v.optional(v.string()), - variablesStorageId: v.optional(v.id('_storage')), - metadata: v.optional(v.record(v.string(), v.any())), -}; - -export const setComponentWorkflowArgsValidator = { - executionId: v.id('wfExecutions'), - componentWorkflowId: v.string(), -}; - -export const updateExecutionMetadataArgsValidator = { - executionId: v.id('wfExecutions'), - metadata: v.record(v.string(), v.any()), -}; - -export const updateExecutionVariablesArgsValidator = { - executionId: v.id('wfExecutions'), - variablesSerialized: v.optional(v.string()), - variablesStorageId: v.optional(v.id('_storage')), -}; - -export const listExecutionsArgsValidator = { - wfDefinitionId: v.id('wfDefinitions'), - status: v.optional(v.string()), - limit: v.optional(v.number()), - search: v.optional(v.string()), - triggeredBy: v.optional(v.string()), - dateFrom: v.optional(v.string()), - dateTo: v.optional(v.string()), -}; - -export const listExecutionsPaginatedArgsValidator = { - wfDefinitionId: v.id('wfDefinitions'), - currentPage: v.optional(v.number()), - pageSize: v.optional(v.number()), - searchTerm: v.optional(v.string()), - status: v.optional(v.array(v.string())), - triggeredBy: v.optional(v.array(v.string())), - dateFrom: v.optional(v.string()), - dateTo: v.optional(v.string()), - sortField: v.optional(v.string()), - sortOrder: v.optional(v.union(v.literal('asc'), v.literal('desc'))), -}; diff --git a/services/platform/convex/model/wf_executions/validators.ts b/services/platform/convex/model/wf_executions/validators.ts new file mode 100644 index 0000000000..9223d27772 --- /dev/null +++ b/services/platform/convex/model/wf_executions/validators.ts @@ -0,0 +1,81 @@ +/** + * Convex validators for workflow executions + */ + +import { v } from 'convex/values'; + +export const updateExecutionStatusArgsValidator = { + executionId: v.id('wfExecutions'), + status: v.string(), + currentStepSlug: v.optional(v.string()), + waitingFor: v.optional(v.string()), + error: v.optional(v.string()), +}; + +export const completeExecutionArgsValidator = { + executionId: v.id('wfExecutions'), + output: v.any(), + variablesSerialized: v.optional(v.string()), + variablesStorageId: v.optional(v.id('_storage')), +}; + +export const failExecutionArgsValidator = { + executionId: v.id('wfExecutions'), + error: v.string(), +}; + +export const patchExecutionArgsValidator = { + executionId: v.id('wfExecutions'), + updates: v.object({ + threadId: v.optional(v.string()), + currentStepSlug: v.optional(v.string()), + variables: v.optional(v.string()), + metadata: v.optional(v.string()), + }), +}; + +export const resumeExecutionArgsValidator = { + executionId: v.id('wfExecutions'), + variablesSerialized: v.optional(v.string()), + variablesStorageId: v.optional(v.id('_storage')), + metadata: v.optional(v.record(v.string(), v.any())), +}; + +export const setComponentWorkflowArgsValidator = { + executionId: v.id('wfExecutions'), + componentWorkflowId: v.string(), +}; + +export const updateExecutionMetadataArgsValidator = { + executionId: v.id('wfExecutions'), + metadata: v.record(v.string(), v.any()), +}; + +export const updateExecutionVariablesArgsValidator = { + executionId: v.id('wfExecutions'), + variablesSerialized: v.optional(v.string()), + variablesStorageId: v.optional(v.id('_storage')), +}; + +export const listExecutionsArgsValidator = { + wfDefinitionId: v.id('wfDefinitions'), + status: v.optional(v.string()), + limit: v.optional(v.number()), + search: v.optional(v.string()), + triggeredBy: v.optional(v.string()), + dateFrom: v.optional(v.string()), + dateTo: v.optional(v.string()), +}; + +export const listExecutionsPaginatedArgsValidator = { + wfDefinitionId: v.id('wfDefinitions'), + currentPage: v.optional(v.number()), + pageSize: v.optional(v.number()), + searchTerm: v.optional(v.string()), + status: v.optional(v.array(v.string())), + triggeredBy: v.optional(v.array(v.string())), + dateFrom: v.optional(v.string()), + dateTo: v.optional(v.string()), + sortField: v.optional(v.string()), + sortOrder: v.optional(v.union(v.literal('asc'), v.literal('desc'))), +}; diff --git a/services/platform/convex/model/wf_step_defs/index.ts b/services/platform/convex/model/wf_step_defs/index.ts index 930625c3b5..fbf25f8084 100644 --- a/services/platform/convex/model/wf_step_defs/index.ts +++ b/services/platform/convex/model/wf_step_defs/index.ts @@ -2,6 +2,7 @@ * Workflow step definitions model - central export point */ +export * from './validators'; export * from './types'; export * from './create_step'; export * from './delete_step'; diff --git a/services/platform/convex/model/wf_step_defs/types.ts b/services/platform/convex/model/wf_step_defs/types.ts index d7378ec97c..42e03ae4cf 100644 --- a/services/platform/convex/model/wf_step_defs/types.ts +++ b/services/platform/convex/model/wf_step_defs/types.ts @@ -1,15 +1,20 @@ /** - * Types and validators for workflow step definitions + * Type definitions for workflow step definitions */ -import { v } from 'convex/values'; +import type { Infer } from 'convex/values'; import type { Doc } from '../../_generated/dataModel'; +import { stepTypeValidator } from './validators'; // ============================================================================= -// TypeScript Types +// INFERRED TYPES (from validators) // ============================================================================= -export type StepType = 'trigger' | 'llm' | 'condition' | 'action' | 'loop'; +export type StepType = Infer; + +// ============================================================================= +// MANUAL TYPES (no corresponding validator) +// ============================================================================= export interface CreateStepArgs { wfDefinitionId: Doc<'wfDefinitions'>['_id']; @@ -66,73 +71,3 @@ export interface UpdateStepArgs { stepRecordId: Doc<'wfStepDefs'>['_id']; updates: unknown; } - -// ============================================================================= -// Convex Validators -// ============================================================================= - -export const stepTypeValidator = v.union( - v.literal('trigger'), - v.literal('llm'), - v.literal('condition'), - v.literal('action'), - v.literal('loop'), -); - -export const createStepArgsValidator = { - wfDefinitionId: v.id('wfDefinitions'), - stepSlug: v.string(), - name: v.string(), - stepType: stepTypeValidator, - order: v.number(), - config: v.any(), - nextSteps: v.record(v.string(), v.string()), - organizationId: v.string(), -}; - -export const deleteStepArgsValidator = { - stepRecordId: v.id('wfStepDefs'), -}; - -export const getNextStepInSequenceArgsValidator = { - wfDefinitionId: v.id('wfDefinitions'), - currentOrder: v.number(), -}; - -export const getOrderedStepsArgsValidator = { - wfDefinitionId: v.id('wfDefinitions'), -}; - -export const getStepByOrderArgsValidator = { - wfDefinitionId: v.id('wfDefinitions'), - order: v.number(), -}; - -export const getStepDefinitionArgsValidator = { - wfDefinitionId: v.id('wfDefinitions'), - stepSlug: v.string(), -}; - -export const getStepsByTypeArgsValidator = { - wfDefinitionId: v.id('wfDefinitions'), - stepType: v.string(), -}; - -export const listWorkflowStepsArgsValidator = { - wfDefinitionId: v.id('wfDefinitions'), -}; - -export const reorderStepsArgsValidator = { - wfDefinitionId: v.id('wfDefinitions'), - stepOrders: v.array( - v.object({ - stepRecordId: v.id('wfStepDefs'), - newOrder: v.number(), - }), - ), -}; - -export const updateStepArgsValidator = { - stepRecordId: v.id('wfStepDefs'), - updates: v.any(), -}; diff --git a/services/platform/convex/model/wf_step_defs/validators.ts b/services/platform/convex/model/wf_step_defs/validators.ts new file mode 100644 index 0000000000..e864d08e04 --- /dev/null +++ b/services/platform/convex/model/wf_step_defs/validators.ts @@ -0,0 +1,71 @@ +/** + * Convex validators for workflow step definitions + */ + +import { v } from 'convex/values'; + +export const stepTypeValidator = v.union( + v.literal('trigger'), + v.literal('llm'), + v.literal('condition'), + v.literal('action'), + v.literal('loop'), +); + +export const createStepArgsValidator = { + wfDefinitionId: v.id('wfDefinitions'), + stepSlug: v.string(), + name: v.string(), + stepType: stepTypeValidator, + order: v.number(), + config: v.any(), + nextSteps: v.record(v.string(), v.string()), + organizationId: v.string(), +}; + +export const deleteStepArgsValidator = { + stepRecordId: v.id('wfStepDefs'), +}; + +export const getNextStepInSequenceArgsValidator = { + wfDefinitionId: v.id('wfDefinitions'), + currentOrder: v.number(), +}; + +export const getOrderedStepsArgsValidator = { + wfDefinitionId: v.id('wfDefinitions'), +}; + +export const getStepByOrderArgsValidator = { + wfDefinitionId: v.id('wfDefinitions'), + order: v.number(), +}; + +export const getStepDefinitionArgsValidator = { + wfDefinitionId: v.id('wfDefinitions'), + stepSlug: v.string(), +}; + +export const getStepsByTypeArgsValidator = { + wfDefinitionId: v.id('wfDefinitions'), + stepType: v.string(), +}; + +export const listWorkflowStepsArgsValidator = { + wfDefinitionId: v.id('wfDefinitions'), +}; + +export const reorderStepsArgsValidator = { + wfDefinitionId: v.id('wfDefinitions'), + stepOrders: v.array( + v.object({ + stepRecordId: v.id('wfStepDefs'), + newOrder: v.number(), + }), + ), +}; + +export const updateStepArgsValidator = { + stepRecordId: v.id('wfStepDefs'), + updates: v.any(), +}; diff --git a/services/platform/convex/websites.ts b/services/platform/convex/websites.ts index bf4282c840..52ff5fee2a 100644 --- a/services/platform/convex/websites.ts +++ b/services/platform/convex/websites.ts @@ -25,7 +25,7 @@ import { websiteStatusValidator, websiteValidator, websitePageValidator, -} from './model/websites/types'; +} from './model/websites/validators'; // ============================================================================= // PUBLIC OPERATIONS (with RLS) diff --git a/services/platform/convex/workflow/actions/customer/customer_action.ts b/services/platform/convex/workflow/actions/customer/customer_action.ts index 7b67aef5d5..fa85f46036 100644 --- a/services/platform/convex/workflow/actions/customer/customer_action.ts +++ b/services/platform/convex/workflow/actions/customer/customer_action.ts @@ -36,7 +36,7 @@ import type { QueryResult } from '../conversation/helpers/types'; import { customerStatusValidator, customerAddressValidator, -} from '../../../model/customers/types'; +} from '../../../model/customers/validators'; // Type definitions for customer operations type CreateCustomerResult = { From e4b31d3eb00bc3f8c4ff8346303d39902221c86a Mon Sep 17 00:00:00 2001 From: Larry Date: Tue, 30 Dec 2025 14:18:53 +0800 Subject: [PATCH 2/4] fix(convex): address CodeRabbit validator review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use v.id('organizations') instead of v.string() for _id in organizationValidator - Use workflowStatusValidator for status field in workflowUpdateValidator - Use stepTypeValidator for stepType field in getStepsByTypeArgsValidator 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- services/platform/convex/model/organizations/validators.ts | 2 +- services/platform/convex/model/wf_definitions/validators.ts | 2 +- services/platform/convex/model/wf_step_defs/validators.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/platform/convex/model/organizations/validators.ts b/services/platform/convex/model/organizations/validators.ts index 9067b78d17..4723dde833 100644 --- a/services/platform/convex/model/organizations/validators.ts +++ b/services/platform/convex/model/organizations/validators.ts @@ -8,7 +8,7 @@ import { v } from 'convex/values'; * Organization validator */ export const organizationValidator = v.object({ - _id: v.string(), + _id: v.id('organizations'), _creationTime: v.number(), name: v.string(), slug: v.optional(v.string()), diff --git a/services/platform/convex/model/wf_definitions/validators.ts b/services/platform/convex/model/wf_definitions/validators.ts index 769c944c8b..3030b74455 100644 --- a/services/platform/convex/model/wf_definitions/validators.ts +++ b/services/platform/convex/model/wf_definitions/validators.ts @@ -37,7 +37,7 @@ export const workflowUpdateValidator = v.object({ name: v.optional(v.string()), description: v.optional(v.string()), version: v.optional(v.string()), - status: v.optional(v.string()), + status: v.optional(workflowStatusValidator), workflowType: v.optional(workflowTypeValidator), config: v.optional(workflowConfigValidator), metadata: v.optional(v.any()), diff --git a/services/platform/convex/model/wf_step_defs/validators.ts b/services/platform/convex/model/wf_step_defs/validators.ts index e864d08e04..d62ab0dd1d 100644 --- a/services/platform/convex/model/wf_step_defs/validators.ts +++ b/services/platform/convex/model/wf_step_defs/validators.ts @@ -48,7 +48,7 @@ export const getStepDefinitionArgsValidator = { export const getStepsByTypeArgsValidator = { wfDefinitionId: v.id('wfDefinitions'), - stepType: v.string(), + stepType: stepTypeValidator, }; export const listWorkflowStepsArgsValidator = { From e582b18357fcc21abf3ad6a2a92a5dcedec61c23 Mon Sep 17 00:00:00 2001 From: Larry Date: Tue, 30 Dec 2025 14:36:44 +0800 Subject: [PATCH 3/4] fix(convex): add missing lastMessageAt field to conversation return validators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The conversation return validators in conversations.ts were missing the lastMessageAt field which is part of the schema (added for efficient sorting by last message time). This caused ReturnsValidationError when workflow actions returned conversation objects with this field populated. Affected validators: - getConversationById - getConversationByExternalMessageId - queryConversations - getConversation (RLS) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- services/platform/convex/conversations.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/platform/convex/conversations.ts b/services/platform/convex/conversations.ts index d71ab729c3..bc569593d5 100644 --- a/services/platform/convex/conversations.ts +++ b/services/platform/convex/conversations.ts @@ -109,6 +109,7 @@ export const getConversationById = internalQuery({ v.union(v.literal('inbound'), v.literal('outbound')), ), providerId: v.optional(v.id('emailProviders')), + lastMessageAt: v.optional(v.number()), metadata: v.optional(v.any()), }), @@ -143,6 +144,7 @@ export const getConversationByExternalMessageId = internalQuery({ v.union(v.literal('inbound'), v.literal('outbound')), ), providerId: v.optional(v.id('emailProviders')), + lastMessageAt: v.optional(v.number()), metadata: v.optional(v.any()), }), @@ -221,6 +223,7 @@ export const queryConversations = internalQuery({ v.union(v.literal('inbound'), v.literal('outbound')), ), providerId: v.optional(v.id('emailProviders')), + lastMessageAt: v.optional(v.number()), metadata: v.optional(v.any()), }), ), @@ -462,6 +465,7 @@ export const getConversation = queryWithRLS({ v.union(v.literal('inbound'), v.literal('outbound')), ), providerId: v.optional(v.id('emailProviders')), + lastMessageAt: v.optional(v.number()), metadata: v.optional(v.any()), }), From 6c7932b19e597293cb28a89d74110bc8bbe26ddc Mon Sep 17 00:00:00 2001 From: Larry Date: Tue, 30 Dec 2025 15:47:22 +0800 Subject: [PATCH 4/4] fix(automations): resolve infinite loop in ReactFlow workflow editor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ReactFlow's StoreUpdater component was detecting data changes on every render because callback functions were included in node.data and edge.data objects. Since function references change on each render, this triggered an infinite update loop. The fix: - Create AutomationCallbacksContext to pass callbacks via React context - Remove callback functions from node.data and edge.data objects - Use stable stepsKey for memoization instead of callback dependencies - Update AutomationStep to get callbacks from context 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../automation-callbacks-context.tsx | 56 +++ .../components/automation-step.tsx | 7 +- .../components/automation-steps.tsx | 325 +++++++++--------- 3 files changed, 226 insertions(+), 162 deletions(-) create mode 100644 services/platform/app/(app)/dashboard/[id]/automations/components/automation-callbacks-context.tsx diff --git a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-callbacks-context.tsx b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-callbacks-context.tsx new file mode 100644 index 0000000000..7582984e31 --- /dev/null +++ b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-callbacks-context.tsx @@ -0,0 +1,56 @@ +'use client'; + +import { createContext, useContext, type ReactNode } from 'react'; + +/** + * Context for automation workflow callbacks + * + * IMPORTANT: This context exists to prevent infinite render loops in ReactFlow. + * ReactFlow's StoreUpdater component watches for changes in node.data objects. + * If callback functions are included in node.data, they create new object + * references on each render, triggering an infinite loop of: + * render -> new data -> StoreUpdater detects change -> setNodes -> render + * + * By passing callbacks through context instead of node.data, we maintain + * stable object references and avoid the infinite loop. + */ + +interface AutomationCallbacksContextType { + /** Called when a node is clicked to open the side panel */ + onNodeClick: (stepSlug: string) => void; + /** Called when the add step button on a leaf node is clicked */ + onAddStep: (stepSlug: string) => void; + /** Called when adding a step on an edge */ + onAddStepOnEdge: (sourceId: string, targetId: string) => void; + /** Called when deleting an edge */ + onDeleteEdge: (edgeId: string) => void; +} + +const AutomationCallbacksContext = + createContext(null); + +export function AutomationCallbacksProvider({ + children, + onNodeClick, + onAddStep, + onAddStepOnEdge, + onDeleteEdge, +}: AutomationCallbacksContextType & { children: ReactNode }) { + return ( + + {children} + + ); +} + +export function useAutomationCallbacks(): AutomationCallbacksContextType { + const context = useContext(AutomationCallbacksContext); + if (!context) { + throw new Error( + 'useAutomationCallbacks must be used within AutomationCallbacksProvider', + ); + } + return context; +} diff --git a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-step.tsx b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-step.tsx index 364ede9ac5..2715b5112e 100644 --- a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-step.tsx +++ b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-step.tsx @@ -7,6 +7,7 @@ import { Doc } from '@/convex/_generated/dataModel'; import { PickaxeIcon, Repeat } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { useT } from '@/lib/i18n'; +import { useAutomationCallbacks } from './automation-callbacks-context'; interface AutomationStepProps { data: { @@ -14,10 +15,8 @@ interface AutomationStepProps { description?: string; stepType: Doc<'wfStepDefs'>['stepType']; stepSlug: string; - onNodeClick?: (stepSlug: string) => void; isLeafNode?: boolean; isTerminalNode?: boolean; - onAddStep?: () => void; hasNextSteps?: boolean; target?: string; incomingCount?: number; @@ -29,6 +28,8 @@ interface AutomationStepProps { export default function AutomationStep({ data }: AutomationStepProps) { const { t } = useT('automations'); + const { onNodeClick } = useAutomationCallbacks(); + // Determine handle positions based on whether each edge (top/bottom) has bidirectional connections // Only offset if there are connections in both directions at that specific edge const topTargetLeft = data.hasBidirectionalTop ? '45%' : '50%'; @@ -85,7 +86,7 @@ export default function AutomationStep({ data }: AutomationStepProps) { ? 'border-dashed border-2 border-muted-foreground/50' : 'border-border', )} - onClick={() => data.onNodeClick?.(data.stepSlug)} + onClick={() => onNodeClick(data.stepSlug)} >
{/* Icon on left */} diff --git a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-steps.tsx b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-steps.tsx index 195c2bf66d..f94501e5b9 100644 --- a/services/platform/app/(app)/dashboard/[id]/automations/components/automation-steps.tsx +++ b/services/platform/app/(app)/dashboard/[id]/automations/components/automation-steps.tsx @@ -42,13 +42,8 @@ import AutomationSidePanel from './automation-sidepanel'; import AutomationGroupNode from './automation-group-node'; import AutomationLoopContainer from './automation-loop-container'; import AutomationEdge from './automation-edge'; +import { AutomationCallbacksProvider } from './automation-callbacks-context'; import { Doc, Id } from '@/convex/_generated/dataModel'; -import { - Tooltip, - TooltipProvider, - TooltipTrigger, - TooltipContent, -} from '@/components/ui/tooltip'; import { getLayoutedElements } from '../utils/dagre-layout'; interface AutomationStepsProps { @@ -87,13 +82,18 @@ function AutomationStepsInner({ const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); const [isCreateStepDialogOpen, setIsCreateStepDialogOpen] = useState(false); + + // Memoize URL state definitions to prevent infinite re-renders + // The definitions object must be stable to avoid triggering useUrlState recalculation + const urlStateDefinitions = useMemo(() => ({ + panel: { default: isDraft ? 'ai-chat' : null }, + step: { default: null }, + }), [isDraft]); + // URL-based state for side panel mode and selected step // This persists the panel state in URL params for bookmarkability and page reload support const { state: panelState, setStates: setPanelStates, clearAll: clearPanelState } = useUrlState({ - definitions: { - panel: { default: isDraft ? 'ai-chat' : null }, - step: { default: null }, - }, + definitions: urlStateDefinitions, }); // Derive sidePanelMode from URL state @@ -190,6 +190,15 @@ function AutomationStepsInner({ [], ); + // Handle adding step after a leaf node + const handleAddStep = useCallback( + (stepSlug: string) => { + setParentStepForNewStep(stepSlug); + setIsCreateStepDialogOpen(true); + }, + [], + ); + // Handle deleting edge const handleDeleteEdge = useCallback(async (_edgeId: string) => { // NOTE: Editing connections is currently disabled until public Convex @@ -201,7 +210,19 @@ function AutomationStepsInner({ return; }, [t]); + // Stable key for step array to detect actual changes + // This creates a deterministic string based on step content (not object identity) + const stepsKey = useMemo(() => { + if (!steps || steps.length === 0) return ''; + return steps + .map((s) => `${s.stepSlug}:${s.order}:${JSON.stringify(s.nextSteps)}`) + .sort() + .join('|'); + }, [steps]); + // Convert steps to nodes and edges using Dagre layout + // IMPORTANT: Only include primitive values in node.data to avoid infinite loops + // Callbacks are passed via context or refs to prevent object identity changes const { initialNodes, initialEdges } = useMemo(() => { if (!steps || steps.length === 0) { return { initialNodes: [], initialEdges: [] }; @@ -445,6 +466,9 @@ function AutomationStepsInner({ } // Build node configuration + // IMPORTANT: Do NOT include callback functions in data - they cause infinite loops + // because new function references trigger ReactFlow's StoreUpdater to re-render + // Callbacks should be accessed via refs or context in the node components const nodeConfig: Partial = { id: step.stepSlug, type: isLoopNode ? 'loopContainer' : 'custom', @@ -454,17 +478,10 @@ function AutomationStepsInner({ label: step.name, stepType: step.stepType, stepSlug: step.stepSlug, - onNodeClick: handleNodeClick, isLeafNode: leafStepSlugs.has(step.stepSlug), isTerminalNode: leafStepSlugs.has(step.stepSlug), rank: step.order, // Use order as rank for automatic positioning isLoopBodyNode: !!parentLoopId, // Flag to identify loop body nodes - onAddStep: leafStepSlugs.has(step.stepSlug) - ? () => { - setParentStepForNewStep(step.stepSlug); - setIsCreateStepDialogOpen(true); - } - : undefined, }, }; @@ -702,6 +719,8 @@ function AutomationStepsInner({ return; } + // IMPORTANT: Do NOT include callback functions in edge data - they cause infinite loops + // Callbacks should be accessed via refs or context in the edge components edges.push({ id: `e${step.stepSlug}-${targetStepSlug}-${key}`, type: edgeType, @@ -743,8 +762,6 @@ function AutomationStepsInner({ animated: !isBackwardConnection, // Animate forward edges to show direction data: { isBackward: isBackwardConnection, - onAddStepOnEdge: handleAddStepOnEdge, - onDeleteEdge: handleDeleteEdge, label: edgeLabel, labelStyle: edgeLabel ? { @@ -856,7 +873,8 @@ function AutomationStepsInner({ initialNodes: layouted.nodes, initialEdges: layouted.edges, }; - }, [steps, handleNodeClick, handleAddStepOnEdge, handleDeleteEdge]); + // eslint-disable-next-line react-hooks/exhaustive-deps -- stepsKey is a stable derived key + }, [stepsKey]); // Update nodes and edges when steps change useEffect(() => { @@ -938,70 +956,76 @@ function AutomationStepsInner({ }; return ( -
- - {/* Main automation canvas */} -
- + .react-flow__minimap-mask { + fill: hsl(var(--muted) / 0.6) !important; + } + `} + {/* Main automation canvas */} +
+ { @@ -1092,88 +1116,71 @@ function AutomationStepsInner({
{/* Action Tools */}
- - - - - - {t('steps.toolbar.focus')} - - - - - - - - - {t('steps.toolbar.aiAssistant')} - - - - - - - - - {t('steps.toolbar.testAutomation')} - - + + + + +
-
-
- - {/* Right side panel for step details, AI chat, or test panel */} - {sidePanelMode && ( - +
+ + {/* Right side panel for step details, AI chat, or test panel */} + {sidePanelMode && ( + + )} + {/* Create Step Dialog */} + { + setIsCreateStepDialogOpen(open); + if (!open) { + setParentStepForNewStep(null); + setEdgeToInsertStep(null); + } + }} + onCreateStep={handleCreateStep} /> - )} - {/* Create Step Dialog */} - { - setIsCreateStepDialogOpen(open); - if (!open) { - setParentStepForNewStep(null); - setEdgeToInsertStep(null); - } - }} - onCreateStep={handleCreateStep} - /> -
+
+ ); }