diff --git a/CHANGELOG.md b/CHANGELOG.md index cc2a07dda09b1..986b85b0682ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,48 @@ +# [1.53.0](https://github.com/n8n-io/n8n/compare/n8n@1.52.0...n8n@1.53.0) (2024-07-31) + + +### Bug Fixes + +* Better error message when calling data transformation functions on a null value ([#10210](https://github.com/n8n-io/n8n/issues/10210)) ([1718125](https://github.com/n8n-io/n8n/commit/1718125c6d8589cf24dc8d34f6808dd6f1802691)) +* **core:** Fix missing successful items on continueErrorOutput with multiple outputs ([#10218](https://github.com/n8n-io/n8n/issues/10218)) ([1a7713e](https://github.com/n8n-io/n8n/commit/1a7713ef263680da43f08b6c8a15aee7a0341493)) +* **core:** Flush instance stopped event immediately ([#10238](https://github.com/n8n-io/n8n/issues/10238)) ([d6770b5](https://github.com/n8n-io/n8n/commit/d6770b5fcaec6438d677b918aaeb1669ad7424c2)) +* **core:** Restore log event `n8n.workflow.failed` ([#10253](https://github.com/n8n-io/n8n/issues/10253)) ([3e96b29](https://github.com/n8n-io/n8n/commit/3e96b293329525c9d4b2fcef87b3803e458c8e7f)) +* **core:** Upgrade @n8n/vm2 to address CVE‑2023‑37466 ([#10265](https://github.com/n8n-io/n8n/issues/10265)) ([2a09a03](https://github.com/n8n-io/n8n/commit/2a09a036d2e916acff7ee50904f1d011a93758e1)) +* **editor:** Defer `User saved credentials` telemetry event for OAuth credentials ([#10215](https://github.com/n8n-io/n8n/issues/10215)) ([40a5226](https://github.com/n8n-io/n8n/commit/40a5226e24448a4428143e69d80ebc78238365a1)) +* **editor:** Fix custom API call notice ([#10227](https://github.com/n8n-io/n8n/issues/10227)) ([5b47c8b](https://github.com/n8n-io/n8n/commit/5b47c8b57b25528cd2d6f97bc6d98707d47f35bc)) +* **editor:** Fix issue with existing credential not opening in HTTP agent tool ([#10167](https://github.com/n8n-io/n8n/issues/10167)) ([906b4c3](https://github.com/n8n-io/n8n/commit/906b4c3c7b2919111cf23eaa12b3c4d507969179)) +* **editor:** Fix parameter input glitch when there was an error loading remote options ([#10209](https://github.com/n8n-io/n8n/issues/10209)) ([c0e3743](https://github.com/n8n-io/n8n/commit/c0e37439a87105a0e66c8ebced42c06dab30dc5e)) +* **editor:** Fix workflow execution list scrolling after filter change ([#10226](https://github.com/n8n-io/n8n/issues/10226)) ([7e64358](https://github.com/n8n-io/n8n/commit/7e643589c67adc0218216ec4b89a95f0edfedbee)) +* **Google BigQuery Node:** Send timeoutMs in query, pagination support ([#10205](https://github.com/n8n-io/n8n/issues/10205)) ([f5722e8](https://github.com/n8n-io/n8n/commit/f5722e8823ccd2bc2b5f43ba3c849797d5690a93)) +* **Google Sheets Node:** Add column names row if sheet is empty ([#10200](https://github.com/n8n-io/n8n/issues/10200)) ([82eba9f](https://github.com/n8n-io/n8n/commit/82eba9fc5ff49b8e2a9db93c10b253fb67a8c644)) +* **Google Sheets Node:** Do not insert row_number as a new column, do not checkForSchemaChanges in update operation ([#10201](https://github.com/n8n-io/n8n/issues/10201)) ([5136d10](https://github.com/n8n-io/n8n/commit/5136d10ca3492f92af67d4a1d4abc774419580cc)) +* **Google Sheets Node:** Fix Google Sheet URL regex ([#10195](https://github.com/n8n-io/n8n/issues/10195)) ([e6fd996](https://github.com/n8n-io/n8n/commit/e6fd996973d4f40facf0ebf1eea3cc26acd0603d)) +* **HTTP Request Node:** Resolve max pages expression ([#10192](https://github.com/n8n-io/n8n/issues/10192)) ([bfc8e1b](https://github.com/n8n-io/n8n/commit/bfc8e1b56f7714e1f52aae747d58d686b86e60f0)) +* **LinkedIn Node:** Fix issue with some characters cutting off posts early ([#10185](https://github.com/n8n-io/n8n/issues/10185)) ([361b5e7](https://github.com/n8n-io/n8n/commit/361b5e7c37ba49b68dcf5b8122621aad4d8d96e0)) +* **Postgres Node:** Expressions in query parameters for Postgres executeQuery operation ([#10217](https://github.com/n8n-io/n8n/issues/10217)) ([519fc4d](https://github.com/n8n-io/n8n/commit/519fc4d75325a80b84cc4dcacf52d6f4c02e3a44)) +* **Postgres Node:** Option to treat query parameters enclosed in single quotas as text ([#10214](https://github.com/n8n-io/n8n/issues/10214)) ([00ec253](https://github.com/n8n-io/n8n/commit/00ec2533374d3def465efee718592fc4001d5602)) +* **Read/Write Files from Disk Node:** Notice update in file selector, replace backslashes with forward slashes if windows path ([#10186](https://github.com/n8n-io/n8n/issues/10186)) ([3eac673](https://github.com/n8n-io/n8n/commit/3eac673b17986c5c74bd2adb5ad589ba0ca55319)) +* **Text Classifier Node:** Use proper documentation URL and respect continueOnFail ([#10216](https://github.com/n8n-io/n8n/issues/10216)) ([452f52c](https://github.com/n8n-io/n8n/commit/452f52c124017e002e86c547ba42b1633b14beed)) +* **Trello Node:** Use body for POST requests ([#10189](https://github.com/n8n-io/n8n/issues/10189)) ([7775d50](https://github.com/n8n-io/n8n/commit/7775d5059b7f69d9af22e7ad7d12c6cf9092a4e5)) +* **Wait Node:** Authentication fix ([#10236](https://github.com/n8n-io/n8n/issues/10236)) ([f87854f](https://github.com/n8n-io/n8n/commit/f87854f8db360b7b870583753fcfb4af95adab8c)) + + +### Features + +* **Calendly Trigger Node:** Add OAuth Credentials Support ([#10251](https://github.com/n8n-io/n8n/issues/10251)) ([326c983](https://github.com/n8n-io/n8n/commit/326c983915a2c382e32398358e7dcadd022c0b77)) +* **core:** Allow filtering workflows by project and transferring workflows in Public API ([#10231](https://github.com/n8n-io/n8n/issues/10231)) ([d719899](https://github.com/n8n-io/n8n/commit/d719899223907b20a17883a35e4ef637a3453532)) +* **editor:** Show new executions as `Queued` in the UI, until they actually start ([#10204](https://github.com/n8n-io/n8n/issues/10204)) ([44728d7](https://github.com/n8n-io/n8n/commit/44728d72423f5549dda09589f4a618ebd80899cb)) +* **HTTP Request Node:** Add option to disable lowercase headers ([#10154](https://github.com/n8n-io/n8n/issues/10154)) ([5aba69b](https://github.com/n8n-io/n8n/commit/5aba69bcf4d232d9860f3cd9fe57cb8839a2f96f)) +* **Information Extractor Node:** Add new simplified AI-node for information extraction ([#10149](https://github.com/n8n-io/n8n/issues/10149)) ([3d235b0](https://github.com/n8n-io/n8n/commit/3d235b0b2df756df35ac60e3dcd87ad183a07167)) +* Introduce Google Cloud Platform as external secrets provider ([#10146](https://github.com/n8n-io/n8n/issues/10146)) ([3ccb9df](https://github.com/n8n-io/n8n/commit/3ccb9df2f902e46f8cbb9c46c0727f29d752a773)) +* **n8n Form Trigger Node:** Improvements ([#10092](https://github.com/n8n-io/n8n/issues/10092)) ([711b667](https://github.com/n8n-io/n8n/commit/711b667ebefe55740e5eb39f1f0f24ceee10e7b0)) +* Recovery option for jsonParse helper ([#10182](https://github.com/n8n-io/n8n/issues/10182)) ([d165b33](https://github.com/n8n-io/n8n/commit/d165b33ceac4d24d0fc290bffe63b5f551204e38)) +* **Sentiment Analysis Node:** Implement Sentiment Analysis node ([#10184](https://github.com/n8n-io/n8n/issues/10184)) ([8ef0a0c](https://github.com/n8n-io/n8n/commit/8ef0a0c58ac2a84aad649ccbe72aa907d005cc44)) +* **Shopify Node:** Update Shopify API version ([#10155](https://github.com/n8n-io/n8n/issues/10155)) ([e2ee915](https://github.com/n8n-io/n8n/commit/e2ee91569a382bfbf787cf45204c72c821a860a0)) +* Support create, read, delete variables in Public API ([#10241](https://github.com/n8n-io/n8n/issues/10241)) ([af695eb](https://github.com/n8n-io/n8n/commit/af695ebf934526d926ea87fe87df61aa73d70979)) + + + # [1.52.0](https://github.com/n8n-io/n8n/compare/n8n@1.51.0...n8n@1.52.0) (2024-07-24) diff --git a/cypress/constants.ts b/cypress/constants.ts index 8d5762bc7e97a..6f7e7b978d317 100644 --- a/cypress/constants.ts +++ b/cypress/constants.ts @@ -58,6 +58,7 @@ export const AI_TOOL_CODE_NODE_NAME = 'Code Tool'; export const AI_TOOL_WIKIPEDIA_NODE_NAME = 'Wikipedia'; export const AI_TOOL_HTTP_NODE_NAME = 'HTTP Request Tool'; export const AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME = 'OpenAI Chat Model'; +export const AI_MEMORY_POSTGRES_NODE_NAME = 'Postgres Chat Memory'; export const AI_OUTPUT_PARSER_AUTO_FIXING_NODE_NAME = 'Auto-fixing Output Parser'; export const WEBHOOK_NODE_NAME = 'Webhook'; diff --git a/cypress/e2e/20-workflow-executions.cy.ts b/cypress/e2e/20-workflow-executions.cy.ts index 075923e940b4e..59f08c570b0ff 100644 --- a/cypress/e2e/20-workflow-executions.cy.ts +++ b/cypress/e2e/20-workflow-executions.cy.ts @@ -183,6 +183,50 @@ describe('Current Workflow Executions', () => { .invoke('attr', 'title') .should('eq', newWorkflowName); }); + + it('should load items and auto scroll after filter change', () => { + createMockExecutions(); + createMockExecutions(); + cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions'); + + executionsTab.actions.switchToExecutionsTab(); + + cy.wait(['@getExecutions']); + + executionsTab.getters.executionsList().scrollTo(0, 500).wait(0); + + executionsTab.getters.executionListItems().eq(10).click(); + + cy.getByTestId('executions-filter-button').click(); + cy.getByTestId('executions-filter-status-select').should('be.visible').click(); + getVisibleSelect().find('li:contains("Error")').click(); + + executionsTab.getters.executionListItems().should('have.length', 5); + executionsTab.getters.successfulExecutionListItems().should('have.length', 1); + executionsTab.getters.failedExecutionListItems().should('have.length', 4); + + cy.getByTestId('executions-filter-button').click(); + cy.getByTestId('executions-filter-status-select').should('be.visible').click(); + getVisibleSelect().find('li:contains("Success")').click(); + + // check if the list is scrolled + executionsTab.getters.executionListItems().eq(10).should('be.visible'); + executionsTab.getters.executionsList().then(($el) => { + const { scrollTop, scrollHeight, clientHeight } = $el[0]; + expect(scrollTop).to.be.greaterThan(0); + expect(scrollTop + clientHeight).to.be.lessThan(scrollHeight); + + // scroll to the bottom + $el[0].scrollTo(0, scrollHeight); + executionsTab.getters.executionListItems().should('have.length', 18); + executionsTab.getters.successfulExecutionListItems().should('have.length', 18); + executionsTab.getters.failedExecutionListItems().should('have.length', 0); + }); + + cy.getByTestId('executions-filter-button').click(); + cy.getByTestId('executions-filter-reset-button').should('be.visible').click(); + executionsTab.getters.executionListItems().eq(11).should('be.visible'); + }); }); const createMockExecutions = () => { diff --git a/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts b/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts new file mode 100644 index 0000000000000..4c733df90dc48 --- /dev/null +++ b/cypress/e2e/233-AI-switch-to-logs-on-error.cy.ts @@ -0,0 +1,279 @@ +import type { ExecutionError } from 'n8n-workflow/src'; +import { NDV, WorkflowPage as WorkflowPageClass } from '../pages'; +import { + addLanguageModelNodeToParent, + addMemoryNodeToParent, + addNodeToCanvas, + addToolNodeToParent, + navigateToNewWorkflowPage, + openNode, +} from '../composables/workflow'; +import { + AGENT_NODE_NAME, + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + AI_MEMORY_POSTGRES_NODE_NAME, + AI_TOOL_CALCULATOR_NODE_NAME, + CHAT_TRIGGER_NODE_DISPLAY_NAME, + MANUAL_CHAT_TRIGGER_NODE_NAME, + MANUAL_TRIGGER_NODE_DISPLAY_NAME, + MANUAL_TRIGGER_NODE_NAME, +} from '../constants'; +import { + clickCreateNewCredential, + clickExecuteNode, + clickGetBackToCanvas, +} from '../composables/ndv'; +import { setCredentialValues } from '../composables/modals/credential-modal'; +import { + closeManualChatModal, + getManualChatMessages, + getManualChatModalLogs, + getManualChatModalLogsEntries, + sendManualChatMessage, +} from '../composables/modals/chat-modal'; +import { createMockNodeExecutionData, getVisibleSelect, runMockWorkflowExecution } from '../utils'; + +const ndv = new NDV(); +const WorkflowPage = new WorkflowPageClass(); + +function createRunDataWithError(inputMessage: string) { + return [ + createMockNodeExecutionData(MANUAL_CHAT_TRIGGER_NODE_NAME, { + jsonData: { + main: { input: inputMessage }, + }, + }), + createMockNodeExecutionData(AI_MEMORY_POSTGRES_NODE_NAME, { + jsonData: { + ai_memory: { + json: { + action: 'loadMemoryVariables', + values: { + input: inputMessage, + system_message: 'You are a helpful assistant', + formatting_instructions: + 'IMPORTANT: Always call `format_final_response` to format your final response!', + }, + }, + }, + }, + inputOverride: { + ai_memory: [ + [ + { + json: { + action: 'loadMemoryVariables', + values: { + input: inputMessage, + system_message: 'You are a helpful assistant', + formatting_instructions: + 'IMPORTANT: Always call `format_final_response` to format your final response!', + }, + }, + }, + ], + ], + }, + error: { + message: 'Internal error', + timestamp: 1722591723244, + name: 'NodeOperationError', + description: 'Internal error', + context: {}, + cause: { + name: 'error', + severity: 'FATAL', + code: '3D000', + file: 'postinit.c', + line: '885', + routine: 'InitPostgres', + } as unknown as Error, + } as ExecutionError, + }), + createMockNodeExecutionData(AGENT_NODE_NAME, { + executionStatus: 'error', + error: { + level: 'error', + tags: { + packageName: 'workflow', + }, + context: {}, + functionality: 'configuration-node', + name: 'NodeOperationError', + timestamp: 1722591723244, + node: { + parameters: { + notice: '', + sessionIdType: 'fromInput', + tableName: 'n8n_chat_histories', + }, + id: '6b9141da-0135-4e9d-94d1-2d658cbf48b5', + name: 'Postgres Chat Memory', + type: '@n8n/n8n-nodes-langchain.memoryPostgresChat', + typeVersion: 1, + position: [1140, 500], + credentials: { + postgres: { + id: 'RkyZetVpGsSfEAhQ', + name: 'Postgres account', + }, + }, + }, + messages: ['database "chat11" does not exist'], + description: 'Internal error', + message: 'Internal error', + } as unknown as ExecutionError, + metadata: { + subRun: [ + { + node: 'Postgres Chat Memory', + runIndex: 0, + }, + ], + }, + }), + ]; +} + +function setupTestWorkflow(chatTrigger: boolean = false) { + // Setup test workflow with AI Agent, Postgres Memory Node (source of error), Calculator Tool, and OpenAI Chat Model + if (chatTrigger) { + addNodeToCanvas(MANUAL_CHAT_TRIGGER_NODE_NAME, true); + } else { + addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME, true); + } + + addNodeToCanvas(AGENT_NODE_NAME, true); + + if (!chatTrigger) { + // Remove chat trigger + WorkflowPage.getters + .canvasNodeByName(CHAT_TRIGGER_NODE_DISPLAY_NAME) + .find('[data-test-id="delete-node-button"]') + .click({ force: true }); + + // Set manual trigger to output standard pinned data + openNode(MANUAL_TRIGGER_NODE_DISPLAY_NAME); + ndv.actions.editPinnedData(); + ndv.actions.savePinnedData(); + ndv.actions.close(); + } + + // Calculator is added just to make OpenAI Chat Model work (tools can not be empty with OpenAI model) + addToolNodeToParent(AI_TOOL_CALCULATOR_NODE_NAME, AGENT_NODE_NAME); + clickGetBackToCanvas(); + + addMemoryNodeToParent(AI_MEMORY_POSTGRES_NODE_NAME, AGENT_NODE_NAME); + + clickCreateNewCredential(); + setCredentialValues({ + password: 'testtesttest', + }); + + ndv.getters.parameterInput('sessionIdType').click(); + getVisibleSelect().contains('Define below').click(); + ndv.getters.parameterInput('sessionKey').type('asdasd'); + + clickGetBackToCanvas(); + + addLanguageModelNodeToParent( + AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME, + AGENT_NODE_NAME, + true, + ); + + clickCreateNewCredential(); + setCredentialValues({ + apiKey: 'sk_test_123', + }); + clickGetBackToCanvas(); + + WorkflowPage.actions.zoomToFit(); +} + +function checkMessages(inputMessage: string, outputMessage: string) { + const messages = getManualChatMessages(); + messages.should('have.length', 2); + messages.should('contain', inputMessage); + messages.should('contain', outputMessage); + + getManualChatModalLogs().should('exist'); + getManualChatModalLogsEntries() + .should('have.length', 1) + .should('contain', AI_MEMORY_POSTGRES_NODE_NAME); +} + +describe("AI-233 Make root node's logs pane active in case of an error in sub-nodes", () => { + beforeEach(() => { + navigateToNewWorkflowPage(); + }); + + it('should open logs tab by default when there was an error', () => { + setupTestWorkflow(true); + + openNode(AGENT_NODE_NAME); + + const inputMessage = 'Test the code tool'; + + clickExecuteNode(); + runMockWorkflowExecution({ + trigger: () => sendManualChatMessage(inputMessage), + runData: createRunDataWithError(inputMessage), + lastNodeExecuted: AGENT_NODE_NAME, + }); + + checkMessages(inputMessage, '[ERROR: Internal error]'); + closeManualChatModal(); + + // Open the AI Agent node to see the logs + openNode(AGENT_NODE_NAME); + + // Finally check that logs pane is opened by default + ndv.getters.outputDataContainer().should('be.visible'); + + ndv.getters.aiOutputModeToggle().should('be.visible'); + ndv.getters + .aiOutputModeToggle() + .find('[role="radio"]') + .should('have.length', 2) + .eq(1) + .should('have.attr', 'aria-checked', 'true'); + + ndv.getters + .outputPanel() + .findChildByTestId('node-error-message') + .should('be.visible') + .should('contain', 'Error in sub-node'); + }); + + it('should switch to logs tab on error, when NDV is already opened', () => { + setupTestWorkflow(false); + + openNode(AGENT_NODE_NAME); + + const inputMessage = 'Test the code tool'; + + runMockWorkflowExecution({ + trigger: () => clickExecuteNode(), + runData: createRunDataWithError(inputMessage), + lastNodeExecuted: AGENT_NODE_NAME, + }); + + // Check that logs pane is opened by default + ndv.getters.outputDataContainer().should('be.visible'); + + ndv.getters.aiOutputModeToggle().should('be.visible'); + ndv.getters + .aiOutputModeToggle() + .find('[role="radio"]') + .should('have.length', 2) + .eq(1) + .should('have.attr', 'aria-checked', 'true'); + + ndv.getters + .outputPanel() + .findChildByTestId('node-error-message') + .should('be.visible') + .should('contain', 'Error in sub-node'); + }); +}); diff --git a/cypress/package.json b/cypress/package.json index 7740b5483f01b..0e1ce734c02cf 100644 --- a/cypress/package.json +++ b/cypress/package.json @@ -14,7 +14,7 @@ "start": "cd ..; pnpm start" }, "devDependencies": { - "@types/lodash": "^4.14.195", + "@types/lodash": "catalog:", "eslint-plugin-cypress": "^3.3.0", "n8n-workflow": "workspace:*" }, @@ -24,8 +24,8 @@ "cypress": "^13.11.0", "cypress-otp": "^1.0.3", "cypress-real-events": "^1.12.0", - "lodash": "4.17.21", - "nanoid": "3.3.6", + "lodash": "catalog:", + "nanoid": "catalog:", "start-server-and-test": "^2.0.3" } } diff --git a/cypress/pages/ndv.ts b/cypress/pages/ndv.ts index 018ec43a5de9c..24e1922663371 100644 --- a/cypress/pages/ndv.ts +++ b/cypress/pages/ndv.ts @@ -24,6 +24,7 @@ export class NDV extends BasePage { editPinnedDataButton: () => cy.getByTestId('ndv-edit-pinned-data'), pinnedDataEditor: () => this.getters.outputPanel().find('.cm-editor .cm-scroller .cm-content'), runDataPaneHeader: () => cy.getByTestId('run-data-pane-header'), + aiOutputModeToggle: () => cy.getByTestId('ai-output-mode-select'), nodeOutputHint: () => cy.getByTestId('ndv-output-run-node-hint'), savePinnedDataButton: () => this.getters.runDataPaneHeader().find('button').filter(':visible').contains('Save'), diff --git a/package.json b/package.json index 59bae47e99169..e05eeb9845f1b 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,19 @@ { "name": "n8n-monorepo", - "version": "1.52.0", + "version": "1.53.0", "private": true, "engines": { - "node": ">=18.10", - "pnpm": ">=9.1" + "node": ">=20.15", + "pnpm": ">=9.5" }, - "packageManager": "pnpm@9.1.4", + "packageManager": "pnpm@9.6.0", "scripts": { "preinstall": "node scripts/block-npm-install.js", "build": "turbo run build", "build:backend": "turbo run build:backend", "build:frontend": "turbo run build:frontend", "build:nodes": "turbo run build:nodes", - "typecheck": "turbo --filter=!n8n typecheck", + "typecheck": "turbo typecheck", "dev": "turbo run dev --parallel --env-mode=loose --filter=!n8n-design-system --filter=!@n8n/chat", "dev:ai": "turbo run dev --parallel --env-mode=loose --filter=@n8n/nodes-langchain --filter=n8n --filter=n8n-core", "clean": "turbo run clean --parallel", @@ -40,7 +40,6 @@ "@n8n_io/eslint-config": "workspace:*", "@types/jest": "^29.5.3", "@types/supertest": "^6.0.2", - "@vitest/coverage-v8": "^1.6.0", "jest": "^29.6.2", "jest-environment-jsdom": "^29.6.2", "jest-expect-message": "^1.1.3", @@ -56,11 +55,7 @@ "tsc-alias": "^1.8.7", "tsc-watch": "^6.0.4", "turbo": "2.0.6", - "typescript": "*", - "vite": "^5.2.12", - "vitest": "^1.6.0", - "vitest-mock-extended": "^1.3.1", - "vue-tsc": "^2.0.19" + "typescript": "*" }, "pnpm": { "onlyBuiltDependencies": [ diff --git a/packages/@n8n/chat/package.json b/packages/@n8n/chat/package.json index 25b9212b3eb79..5ba8a1c39f201 100644 --- a/packages/@n8n/chat/package.json +++ b/packages/@n8n/chat/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/chat", - "version": "0.21.0", + "version": "0.22.0", "scripts": { "dev": "pnpm run storybook", "build": "pnpm build:vite && pnpm build:bundle", @@ -38,16 +38,20 @@ "@vueuse/core": "^10.11.0", "highlight.js": "^11.8.0", "markdown-it-link-attributes": "^4.0.1", - "uuid": "^8.3.2", - "vue": "^3.4.21", + "uuid": "catalog:", + "vue": "catalog:frontend", "vue-markdown-render": "^2.1.1" }, "devDependencies": { "@iconify-json/mdi": "^1.1.54", "@n8n/storybook": "workspace:*", "@types/markdown-it": "^12.2.3", + "@vitest/coverage-v8": "catalog:frontend", "unplugin-icons": "^0.19.0", - "vite-plugin-dts": "^3.9.1" + "vite": "catalog:frontend", + "vitest": "catalog:frontend", + "vite-plugin-dts": "^3.9.1", + "vue-tsc": "catalog:frontend" }, "files": [ "README.md", diff --git a/packages/@n8n/client-oauth2/package.json b/packages/@n8n/client-oauth2/package.json index 38ce65e8eedce..7ec91b98390b1 100644 --- a/packages/@n8n/client-oauth2/package.json +++ b/packages/@n8n/client-oauth2/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/client-oauth2", - "version": "0.19.0", + "version": "0.20.0", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", @@ -20,6 +20,6 @@ "dist/**/*" ], "dependencies": { - "axios": "1.6.7" + "axios": "catalog:" } } diff --git a/packages/@n8n/config/package.json b/packages/@n8n/config/package.json index d992cc5aee077..a4abfa677adc1 100644 --- a/packages/@n8n/config/package.json +++ b/packages/@n8n/config/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/config", - "version": "1.2.0", + "version": "1.3.0", "scripts": { "clean": "rimraf dist .turbo", "dev": "pnpm watch", @@ -21,6 +21,6 @@ ], "dependencies": { "reflect-metadata": "0.2.2", - "typedi": "0.10.0" + "typedi": "catalog:" } } diff --git a/packages/@n8n/config/src/configs/cache.ts b/packages/@n8n/config/src/configs/cache.ts new file mode 100644 index 0000000000000..8a24bdc18beff --- /dev/null +++ b/packages/@n8n/config/src/configs/cache.ts @@ -0,0 +1,36 @@ +import { Config, Env, Nested } from '../decorators'; + +@Config +class MemoryConfig { + /** Max size of memory cache in bytes */ + @Env('N8N_CACHE_MEMORY_MAX_SIZE') + maxSize = 3 * 1024 * 1024; // 3 MiB + + /** Time to live (in milliseconds) for data cached in memory. */ + @Env('N8N_CACHE_MEMORY_TTL') + ttl = 3600 * 1000; // 1 hour +} + +@Config +class RedisConfig { + /** Prefix for cache keys in Redis. */ + @Env('N8N_CACHE_REDIS_KEY_PREFIX') + prefix = 'redis'; + + /** Time to live (in milliseconds) for data cached in Redis. 0 for no TTL. */ + @Env('N8N_CACHE_REDIS_TTL') + ttl = 3600 * 1000; // 1 hour +} + +@Config +export class CacheConfig { + /** Backend to use for caching. */ + @Env('N8N_CACHE_BACKEND') + backend: 'memory' | 'redis' | 'auto' = 'auto'; + + @Nested + memory: MemoryConfig; + + @Nested + redis: RedisConfig; +} diff --git a/packages/@n8n/config/src/configs/credentials.ts b/packages/@n8n/config/src/configs/credentials.ts index 9659061c05e20..ee5f78a681ae6 100644 --- a/packages/@n8n/config/src/configs/credentials.ts +++ b/packages/@n8n/config/src/configs/credentials.ts @@ -7,19 +7,19 @@ class CredentialsOverwrite { * Format: { CREDENTIAL_NAME: { PARAMETER: VALUE }} */ @Env('CREDENTIALS_OVERWRITE_DATA') - readonly data: string = '{}'; + data = '{}'; /** Internal API endpoint to fetch overwritten credential types from. */ @Env('CREDENTIALS_OVERWRITE_ENDPOINT') - readonly endpoint: string = ''; + endpoint = ''; } @Config export class CredentialsConfig { /** Default name for credentials */ @Env('CREDENTIALS_DEFAULT_NAME') - readonly defaultName: string = 'My credentials'; + defaultName = 'My credentials'; @Nested - readonly overwrite: CredentialsOverwrite; + overwrite: CredentialsOverwrite; } diff --git a/packages/@n8n/config/src/configs/database.ts b/packages/@n8n/config/src/configs/database.ts index 384ecb1fb065c..06a3f85465929 100644 --- a/packages/@n8n/config/src/configs/database.ts +++ b/packages/@n8n/config/src/configs/database.ts @@ -4,19 +4,19 @@ import { Config, Env, Nested } from '../decorators'; class LoggingConfig { /** Whether database logging is enabled. */ @Env('DB_LOGGING_ENABLED') - readonly enabled: boolean = false; + enabled = false; /** * Database logging level. Requires `DB_LOGGING_MAX_EXECUTION_TIME` to be higher than `0`. */ @Env('DB_LOGGING_OPTIONS') - readonly options: 'query' | 'error' | 'schema' | 'warn' | 'info' | 'log' | 'all' = 'error'; + options: 'query' | 'error' | 'schema' | 'warn' | 'info' | 'log' | 'all' = 'error'; /** * Only queries that exceed this time (ms) will be logged. Set `0` to disable. */ @Env('DB_LOGGING_MAX_EXECUTION_TIME') - readonly maxQueryExecutionTime: number = 0; + maxQueryExecutionTime = 0; } @Config @@ -26,97 +26,97 @@ class PostgresSSLConfig { * If `DB_POSTGRESDB_SSL_CA`, `DB_POSTGRESDB_SSL_CERT`, or `DB_POSTGRESDB_SSL_KEY` are defined, `DB_POSTGRESDB_SSL_ENABLED` defaults to `true`. */ @Env('DB_POSTGRESDB_SSL_ENABLED') - readonly enabled: boolean = false; + enabled = false; /** SSL certificate authority */ @Env('DB_POSTGRESDB_SSL_CA') - readonly ca: string = ''; + ca = ''; /** SSL certificate */ @Env('DB_POSTGRESDB_SSL_CERT') - readonly cert: string = ''; + cert = ''; /** SSL key */ @Env('DB_POSTGRESDB_SSL_KEY') - readonly key: string = ''; + key = ''; /** If unauthorized SSL connections should be rejected */ @Env('DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED') - readonly rejectUnauthorized: boolean = true; + rejectUnauthorized = true; } @Config class PostgresConfig { /** Postgres database name */ @Env('DB_POSTGRESDB_DATABASE') - database: string = 'n8n'; + database = 'n8n'; /** Postgres database host */ @Env('DB_POSTGRESDB_HOST') - readonly host: string = 'localhost'; + host = 'localhost'; /** Postgres database password */ @Env('DB_POSTGRESDB_PASSWORD') - readonly password: string = ''; + password = ''; /** Postgres database port */ @Env('DB_POSTGRESDB_PORT') - readonly port: number = 5432; + port: number = 5432; /** Postgres database user */ @Env('DB_POSTGRESDB_USER') - readonly user: string = 'postgres'; + user = 'postgres'; /** Postgres database schema */ @Env('DB_POSTGRESDB_SCHEMA') - readonly schema: string = 'public'; + schema = 'public'; /** Postgres database pool size */ @Env('DB_POSTGRESDB_POOL_SIZE') - readonly poolSize = 2; + poolSize = 2; @Nested - readonly ssl: PostgresSSLConfig; + ssl: PostgresSSLConfig; } @Config class MysqlConfig { /** @deprecated MySQL database name */ @Env('DB_MYSQLDB_DATABASE') - database: string = 'n8n'; + database = 'n8n'; /** MySQL database host */ @Env('DB_MYSQLDB_HOST') - readonly host: string = 'localhost'; + host = 'localhost'; /** MySQL database password */ @Env('DB_MYSQLDB_PASSWORD') - readonly password: string = ''; + password = ''; /** MySQL database port */ @Env('DB_MYSQLDB_PORT') - readonly port: number = 3306; + port: number = 3306; /** MySQL database user */ @Env('DB_MYSQLDB_USER') - readonly user: string = 'root'; + user = 'root'; } @Config class SqliteConfig { /** SQLite database file name */ @Env('DB_SQLITE_DATABASE') - readonly database: string = 'database.sqlite'; + database = 'database.sqlite'; /** SQLite database pool size. Set to `0` to disable pooling. */ @Env('DB_SQLITE_POOL_SIZE') - readonly poolSize: number = 0; + poolSize: number = 0; /** * Enable SQLite WAL mode. */ @Env('DB_SQLITE_ENABLE_WAL') - readonly enableWAL: boolean = this.poolSize > 1; + enableWAL = this.poolSize > 1; /** * Run `VACUUM` on startup to rebuild the database, reducing file size and optimizing indexes. @@ -124,7 +124,7 @@ class SqliteConfig { * @warning Long-running blocking operation that will increase startup time. */ @Env('DB_SQLITE_VACUUM_ON_STARTUP') - readonly executeVacuumOnStartup: boolean = false; + executeVacuumOnStartup = false; } @Config @@ -135,17 +135,17 @@ export class DatabaseConfig { /** Prefix for table names */ @Env('DB_TABLE_PREFIX') - readonly tablePrefix: string = ''; + tablePrefix = ''; @Nested - readonly logging: LoggingConfig; + logging: LoggingConfig; @Nested - readonly postgresdb: PostgresConfig; + postgresdb: PostgresConfig; @Nested - readonly mysqldb: MysqlConfig; + mysqldb: MysqlConfig; @Nested - readonly sqlite: SqliteConfig; + sqlite: SqliteConfig; } diff --git a/packages/@n8n/config/src/configs/email.ts b/packages/@n8n/config/src/configs/email.ts index 318c35238070f..f0e130c3b48be 100644 --- a/packages/@n8n/config/src/configs/email.ts +++ b/packages/@n8n/config/src/configs/email.ts @@ -4,75 +4,75 @@ import { Config, Env, Nested } from '../decorators'; export class SmtpAuth { /** SMTP login username */ @Env('N8N_SMTP_USER') - readonly user: string = ''; + user = ''; /** SMTP login password */ @Env('N8N_SMTP_PASS') - readonly pass: string = ''; + pass = ''; /** SMTP OAuth Service Client */ @Env('N8N_SMTP_OAUTH_SERVICE_CLIENT') - readonly serviceClient: string = ''; + serviceClient = ''; /** SMTP OAuth Private Key */ @Env('N8N_SMTP_OAUTH_PRIVATE_KEY') - readonly privateKey: string = ''; + privateKey = ''; } @Config export class SmtpConfig { /** SMTP server host */ @Env('N8N_SMTP_HOST') - readonly host: string = ''; + host = ''; /** SMTP server port */ @Env('N8N_SMTP_PORT') - readonly port: number = 465; + port: number = 465; /** Whether to use SSL for SMTP */ @Env('N8N_SMTP_SSL') - readonly secure: boolean = true; + secure: boolean = true; /** Whether to use STARTTLS for SMTP when SSL is disabled */ @Env('N8N_SMTP_STARTTLS') - readonly startTLS: boolean = true; + startTLS: boolean = true; /** How to display sender name */ @Env('N8N_SMTP_SENDER') - readonly sender: string = ''; + sender = ''; @Nested - readonly auth: SmtpAuth; + auth: SmtpAuth; } @Config export class TemplateConfig { /** Overrides default HTML template for inviting new people (use full path) */ @Env('N8N_UM_EMAIL_TEMPLATES_INVITE') - readonly invite: string = ''; + invite = ''; /** Overrides default HTML template for resetting password (use full path) */ @Env('N8N_UM_EMAIL_TEMPLATES_PWRESET') - readonly passwordReset: string = ''; + passwordReset = ''; /** Overrides default HTML template for notifying that a workflow was shared (use full path) */ @Env('N8N_UM_EMAIL_TEMPLATES_WORKFLOW_SHARED') - readonly workflowShared: string = ''; + workflowShared = ''; /** Overrides default HTML template for notifying that credentials were shared (use full path) */ @Env('N8N_UM_EMAIL_TEMPLATES_CREDENTIALS_SHARED') - readonly credentialsShared: string = ''; + credentialsShared = ''; } @Config export class EmailConfig { /** How to send emails */ @Env('N8N_EMAIL_MODE') - readonly mode: '' | 'smtp' = 'smtp'; + mode: '' | 'smtp' = 'smtp'; @Nested - readonly smtp: SmtpConfig; + smtp: SmtpConfig; @Nested - readonly template: TemplateConfig; + template: TemplateConfig; } diff --git a/packages/@n8n/config/src/configs/endpoints.ts b/packages/@n8n/config/src/configs/endpoints.ts new file mode 100644 index 0000000000000..4957c5afa58d6 --- /dev/null +++ b/packages/@n8n/config/src/configs/endpoints.ts @@ -0,0 +1,102 @@ +import { Config, Env, Nested } from '../decorators'; + +@Config +class PrometheusMetricsConfig { + /** Whether to enable the `/metrics` endpoint to expose Prometheus metrics. */ + @Env('N8N_METRICS') + enable = false; + + /** Prefix for Prometheus metric names. */ + @Env('N8N_METRICS_PREFIX') + prefix = 'n8n_'; + + /** Whether to expose system and Node.js metrics. See: https://www.npmjs.com/package/prom-client */ + @Env('N8N_METRICS_INCLUDE_DEFAULT_METRICS') + includeDefaultMetrics = true; + + /** Whether to include a label for workflow ID on workflow metrics. */ + @Env('N8N_METRICS_INCLUDE_WORKFLOW_ID_LABEL') + includeWorkflowIdLabel = false; + + /** Whether to include a label for node type on node metrics. */ + @Env('N8N_METRICS_INCLUDE_NODE_TYPE_LABEL') + includeNodeTypeLabel = false; + + /** Whether to include a label for credential type on credential metrics. */ + @Env('N8N_METRICS_INCLUDE_CREDENTIAL_TYPE_LABEL') + includeCredentialTypeLabel = false; + + /** Whether to expose metrics for API endpoints. See: https://www.npmjs.com/package/express-prom-bundle */ + @Env('N8N_METRICS_INCLUDE_API_ENDPOINTS') + includeApiEndpoints = false; + + /** Whether to include a label for the path of API endpoint calls. */ + @Env('N8N_METRICS_INCLUDE_API_PATH_LABEL') + includeApiPathLabel = false; + + /** Whether to include a label for the HTTP method of API endpoint calls. */ + @Env('N8N_METRICS_INCLUDE_API_METHOD_LABEL') + includeApiMethodLabel = false; + + /** Whether to include a label for the status code of API endpoint calls. */ + @Env('N8N_METRICS_INCLUDE_API_STATUS_CODE_LABEL') + includeApiStatusCodeLabel = false; + + /** Whether to include metrics for cache hits and misses. */ + @Env('N8N_METRICS_INCLUDE_CACHE_METRICS') + includeCacheMetrics = false; + + /** Whether to include metrics derived from n8n's internal events */ + @Env('N8N_METRICS_INCLUDE_MESSAGE_EVENT_BUS_METRICS') + includeMessageEventBusMetrics = false; +} + +@Config +export class EndpointsConfig { + /** Max payload size in MiB */ + @Env('N8N_PAYLOAD_SIZE_MAX') + payloadSizeMax: number = 16; + + @Nested + metrics: PrometheusMetricsConfig; + + /** Path segment for REST API endpoints. */ + @Env('N8N_ENDPOINT_REST') + rest = 'rest'; + + /** Path segment for form endpoints. */ + @Env('N8N_ENDPOINT_FORM') + form = 'form'; + + /** Path segment for test form endpoints. */ + @Env('N8N_ENDPOINT_FORM_TEST') + formTest = 'form-test'; + + /** Path segment for waiting form endpoints. */ + @Env('N8N_ENDPOINT_FORM_WAIT') + formWaiting = 'form-waiting'; + + /** Path segment for webhook endpoints. */ + @Env('N8N_ENDPOINT_WEBHOOK') + webhook = 'webhook'; + + /** Path segment for test webhook endpoints. */ + @Env('N8N_ENDPOINT_WEBHOOK_TEST') + webhookTest = 'webhook-test'; + + /** Path segment for waiting webhook endpoints. */ + @Env('N8N_ENDPOINT_WEBHOOK_WAIT') + webhookWaiting = 'webhook-waiting'; + + /** Whether to disable n8n's UI (frontend). */ + @Env('N8N_DISABLE_UI') + disableUi = false; + + /** Whether to disable production webhooks on the main process, when using webhook-specific processes. */ + @Env('N8N_DISABLE_PRODUCTION_MAIN_PROCESS') + disableProductionWebhooksOnMainProcess = false; + + /** Colon-delimited list of additional endpoints to not open the UI on. */ + @Env('N8N_ADDITIONAL_NON_UI_ROUTES') + additionalNonUIRoutes = ''; +} diff --git a/packages/@n8n/config/src/configs/event-bus.ts b/packages/@n8n/config/src/configs/event-bus.ts index ed1226fa923ac..87db613e63968 100644 --- a/packages/@n8n/config/src/configs/event-bus.ts +++ b/packages/@n8n/config/src/configs/event-bus.ts @@ -2,30 +2,30 @@ import { Config, Env, Nested } from '../decorators'; @Config class LogWriterConfig { - /** Number of event log files to keep */ + /* of event log files to keep */ @Env('N8N_EVENTBUS_LOGWRITER_KEEPLOGCOUNT') - readonly keepLogCount: number = 3; + keepLogCount = 3; /** Max size (in KB) of an event log file before a new one is started */ @Env('N8N_EVENTBUS_LOGWRITER_MAXFILESIZEINKB') - readonly maxFileSizeInKB: number = 10240; // 10 MB + maxFileSizeInKB = 10240; // 10 MB /** Basename of event log file */ @Env('N8N_EVENTBUS_LOGWRITER_LOGBASENAME') - readonly logBaseName: string = 'n8nEventLog'; + logBaseName = 'n8nEventLog'; } @Config export class EventBusConfig { /** How often (in ms) to check for unsent event messages. Can in rare cases cause a message to be sent twice. `0` to disable */ @Env('N8N_EVENTBUS_CHECKUNSENTINTERVAL') - readonly checkUnsentInterval: number = 0; + checkUnsentInterval = 0; /** Endpoint to retrieve n8n version information from */ @Nested - readonly logWriter: LogWriterConfig; + logWriter: LogWriterConfig; /** Whether to recover execution details after a crash or only mark status executions as crashed. */ @Env('N8N_EVENTBUS_RECOVERY_MODE') - readonly crashRecoveryMode: 'simple' | 'extensive' = 'extensive'; + crashRecoveryMode: 'simple' | 'extensive' = 'extensive'; } diff --git a/packages/@n8n/config/src/configs/external-secrets.ts b/packages/@n8n/config/src/configs/external-secrets.ts index a5310d675e373..2e51be87bc6c0 100644 --- a/packages/@n8n/config/src/configs/external-secrets.ts +++ b/packages/@n8n/config/src/configs/external-secrets.ts @@ -4,9 +4,9 @@ import { Config, Env } from '../decorators'; export class ExternalSecretsConfig { /** How often (in seconds) to check for secret updates */ @Env('N8N_EXTERNAL_SECRETS_UPDATE_INTERVAL') - readonly updateInterval: number = 300; + updateInterval = 300; /** Whether to prefer GET over LIST when fetching secrets from Hashicorp Vault */ @Env('N8N_EXTERNAL_SECRETS_PREFER_GET') - readonly preferGet: boolean = false; + preferGet = false; } diff --git a/packages/@n8n/config/src/configs/external-storage.ts b/packages/@n8n/config/src/configs/external-storage.ts index c876e0ee34465..3dd1448b44e38 100644 --- a/packages/@n8n/config/src/configs/external-storage.ts +++ b/packages/@n8n/config/src/configs/external-storage.ts @@ -4,39 +4,39 @@ import { Config, Env, Nested } from '../decorators'; class S3BucketConfig { /** Name of the n8n bucket in S3-compatible external storage */ @Env('N8N_EXTERNAL_STORAGE_S3_BUCKET_NAME') - readonly name: string = ''; + name = ''; /** Region of the n8n bucket in S3-compatible external storage @example "us-east-1" */ @Env('N8N_EXTERNAL_STORAGE_S3_BUCKET_REGION') - readonly region: string = ''; + region = ''; } @Config class S3CredentialsConfig { /** Access key in S3-compatible external storage */ @Env('N8N_EXTERNAL_STORAGE_S3_ACCESS_KEY') - readonly accessKey: string = ''; + accessKey = ''; /** Access secret in S3-compatible external storage */ @Env('N8N_EXTERNAL_STORAGE_S3_ACCESS_SECRET') - readonly accessSecret: string = ''; + accessSecret = ''; } @Config class S3Config { /** Host of the n8n bucket in S3-compatible external storage @example "s3.us-east-1.amazonaws.com" */ @Env('N8N_EXTERNAL_STORAGE_S3_HOST') - readonly host: string = ''; + host = ''; @Nested - readonly bucket: S3BucketConfig; + bucket: S3BucketConfig; @Nested - readonly credentials: S3CredentialsConfig; + credentials: S3CredentialsConfig; } @Config export class ExternalStorageConfig { @Nested - readonly s3: S3Config; + s3: S3Config; } diff --git a/packages/@n8n/config/src/configs/nodes.ts b/packages/@n8n/config/src/configs/nodes.ts index f845607a8c0f0..3837b0a99b258 100644 --- a/packages/@n8n/config/src/configs/nodes.ts +++ b/packages/@n8n/config/src/configs/nodes.ts @@ -25,22 +25,26 @@ class CommunityPackagesConfig { /** Whether to enable community packages */ @Env('N8N_COMMUNITY_PACKAGES_ENABLED') enabled: boolean = true; + + /** Whether to reinstall any missing community packages */ + @Env('N8N_REINSTALL_MISSING_PACKAGES') + reinstallMissing: boolean = false; } @Config export class NodesConfig { /** Node types to load. Includes all if unspecified. @example '["n8n-nodes-base.hackerNews"]' */ @Env('NODES_INCLUDE') - readonly include: JsonStringArray = []; + include: JsonStringArray = []; /** Node types not to load. Excludes none if unspecified. @example '["n8n-nodes-base.hackerNews"]' */ @Env('NODES_EXCLUDE') - readonly exclude: JsonStringArray = []; + exclude: JsonStringArray = []; /** Node type to use as error trigger */ @Env('NODES_ERROR_TRIGGER_TYPE') - readonly errorTriggerType: string = 'n8n-nodes-base.errorTrigger'; + errorTriggerType = 'n8n-nodes-base.errorTrigger'; @Nested - readonly communityPackages: CommunityPackagesConfig; + communityPackages: CommunityPackagesConfig; } diff --git a/packages/@n8n/config/src/configs/public-api.ts b/packages/@n8n/config/src/configs/public-api.ts index 33e3bf3fc38bc..b62cac68c7834 100644 --- a/packages/@n8n/config/src/configs/public-api.ts +++ b/packages/@n8n/config/src/configs/public-api.ts @@ -4,13 +4,13 @@ import { Config, Env } from '../decorators'; export class PublicApiConfig { /** Whether to disable the Public API */ @Env('N8N_PUBLIC_API_DISABLED') - readonly disabled: boolean = false; + disabled = false; /** Path segment for the Public API */ @Env('N8N_PUBLIC_API_ENDPOINT') - readonly path: string = 'api'; + path = 'api'; /** Whether to disable the Swagger UI for the Public API */ @Env('N8N_PUBLIC_API_SWAGGERUI_DISABLED') - readonly swaggerUiDisabled: boolean = false; + swaggerUiDisabled = false; } diff --git a/packages/@n8n/config/src/configs/templates.ts b/packages/@n8n/config/src/configs/templates.ts index 3e10c892b3469..3b05048b36301 100644 --- a/packages/@n8n/config/src/configs/templates.ts +++ b/packages/@n8n/config/src/configs/templates.ts @@ -4,9 +4,9 @@ import { Config, Env } from '../decorators'; export class TemplatesConfig { /** Whether to load workflow templates. */ @Env('N8N_TEMPLATES_ENABLED') - readonly enabled: boolean = true; + enabled = true; /** Host to retrieve workflow templates from endpoints. */ @Env('N8N_TEMPLATES_HOST') - readonly host: string = 'https://api.n8n.io/api/'; + host = 'https://api.n8n.io/api/'; } diff --git a/packages/@n8n/config/src/configs/version-notifications.ts b/packages/@n8n/config/src/configs/version-notifications.ts index 1aa693228d494..5fe495ed6c02e 100644 --- a/packages/@n8n/config/src/configs/version-notifications.ts +++ b/packages/@n8n/config/src/configs/version-notifications.ts @@ -4,13 +4,13 @@ import { Config, Env } from '../decorators'; export class VersionNotificationsConfig { /** Whether to request notifications about new n8n versions */ @Env('N8N_VERSION_NOTIFICATIONS_ENABLED') - readonly enabled: boolean = true; + enabled = true; /** Endpoint to retrieve n8n version information from */ @Env('N8N_VERSION_NOTIFICATIONS_ENDPOINT') - readonly endpoint: string = 'https://api.n8n.io/api/versions/'; + endpoint = 'https://api.n8n.io/api/versions/'; /** URL for versions panel to page instructing user on how to update n8n instance */ @Env('N8N_VERSION_NOTIFICATIONS_INFO_URL') - readonly infoUrl: string = 'https://docs.n8n.io/hosting/installation/updating/'; + infoUrl = 'https://docs.n8n.io/hosting/installation/updating/'; } diff --git a/packages/@n8n/config/src/configs/workflows.ts b/packages/@n8n/config/src/configs/workflows.ts index b19f4bc95d7e0..9ca004c886b58 100644 --- a/packages/@n8n/config/src/configs/workflows.ts +++ b/packages/@n8n/config/src/configs/workflows.ts @@ -4,17 +4,14 @@ import { Config, Env } from '../decorators'; export class WorkflowsConfig { /** Default name for workflow */ @Env('WORKFLOWS_DEFAULT_NAME') - readonly defaultName: string = 'My workflow'; + defaultName = 'My workflow'; /** Show onboarding flow in new workflow */ @Env('N8N_ONBOARDING_FLOW_DISABLED') - readonly onboardingFlowDisabled: boolean = false; + onboardingFlowDisabled = false; /** Default option for which workflows may call the current workflow */ @Env('N8N_WORKFLOW_CALLER_POLICY_DEFAULT_OPTION') - readonly callerPolicyDefaultOption: - | 'any' - | 'none' - | 'workflowsFromAList' - | 'workflowsFromSameOwner' = 'workflowsFromSameOwner'; + callerPolicyDefaultOption: 'any' | 'none' | 'workflowsFromAList' | 'workflowsFromSameOwner' = + 'workflowsFromSameOwner'; } diff --git a/packages/@n8n/config/src/index.ts b/packages/@n8n/config/src/index.ts index fee6718c6acdb..33d3e676550ba 100644 --- a/packages/@n8n/config/src/index.ts +++ b/packages/@n8n/config/src/index.ts @@ -10,6 +10,8 @@ import { EventBusConfig } from './configs/event-bus'; import { NodesConfig } from './configs/nodes'; import { ExternalStorageConfig } from './configs/external-storage'; import { WorkflowsConfig } from './configs/workflows'; +import { EndpointsConfig } from './configs/endpoints'; +import { CacheConfig } from './configs/cache'; @Config class UserManagementConfig { @@ -71,4 +73,10 @@ export class GlobalConfig { /** HTTP Protocol via which n8n can be reached */ @Env('N8N_PROTOCOL') readonly protocol: 'http' | 'https' = 'http'; + + @Nested + readonly endpoints: EndpointsConfig; + + @Nested + readonly cache: CacheConfig; } diff --git a/packages/@n8n/config/test/config.test.ts b/packages/@n8n/config/test/config.test.ts index 97afb0cda405e..e077e233b7494 100644 --- a/packages/@n8n/config/test/config.test.ts +++ b/packages/@n8n/config/test/config.test.ts @@ -108,6 +108,7 @@ describe('GlobalConfig', () => { nodes: { communityPackages: { enabled: true, + reinstallMissing: false, }, errorTriggerType: 'n8n-nodes-base.errorTrigger', include: [], @@ -145,12 +146,55 @@ describe('GlobalConfig', () => { onboardingFlowDisabled: false, callerPolicyDefaultOption: 'workflowsFromSameOwner', }, + endpoints: { + metrics: { + enable: false, + prefix: 'n8n_', + includeWorkflowIdLabel: false, + includeDefaultMetrics: true, + includeMessageEventBusMetrics: false, + includeNodeTypeLabel: false, + includeCacheMetrics: false, + includeApiEndpoints: false, + includeApiPathLabel: false, + includeApiMethodLabel: false, + includeCredentialTypeLabel: false, + includeApiStatusCodeLabel: false, + }, + additionalNonUIRoutes: '', + disableProductionWebhooksOnMainProcess: false, + disableUi: false, + form: 'form', + formTest: 'form-test', + formWaiting: 'form-waiting', + payloadSizeMax: 16, + rest: 'rest', + webhook: 'webhook', + webhookTest: 'webhook-test', + webhookWaiting: 'webhook-waiting', + }, + cache: { + backend: 'auto', + memory: { + maxSize: 3145728, + ttl: 3600000, + }, + redis: { + prefix: 'redis', + ttl: 3600000, + }, + }, }; it('should use all default values when no env variables are defined', () => { process.env = {}; const config = Container.get(GlobalConfig); - expect(config).toEqual(defaultConfig); + + // deepCopy for diff to show plain objects + // eslint-disable-next-line n8n-local-rules/no-json-parse-json-stringify + const deepCopy = (obj: T): T => JSON.parse(JSON.stringify(obj)); + + expect(deepCopy(config)).toEqual(defaultConfig); expect(mockFs.readFileSync).not.toHaveBeenCalled(); }); diff --git a/packages/@n8n/nodes-langchain/nodes/chains/TextClassifier/TextClassifier.node.ts b/packages/@n8n/nodes-langchain/nodes/chains/TextClassifier/TextClassifier.node.ts index 6a433eaac31a0..0033361a07396 100644 --- a/packages/@n8n/nodes-langchain/nodes/chains/TextClassifier/TextClassifier.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/chains/TextClassifier/TextClassifier.node.ts @@ -46,7 +46,7 @@ export class TextClassifier implements INodeType { resources: { primaryDocumentation: [ { - url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.chainllm/', + url: 'https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.text-classifier/', }, ], }, @@ -203,20 +203,27 @@ export class TextClassifier implements INodeType { discard: 'If there is not a very fitting category, select none of the categories.', }[fallback]; - const systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate( - `${options.systemPromptTemplate ?? SYSTEM_PROMPT_TEMPLATE} -{format_instructions} -${multiClassPrompt} -${fallbackPrompt}`, - ); - const returnData: INodeExecutionData[][] = Array.from( { length: categories.length + (fallback === 'other' ? 1 : 0) }, (_) => [], ); for (let itemIdx = 0; itemIdx < items.length; itemIdx++) { + const item = items[itemIdx]; + item.pairedItem = { item: itemIdx }; const input = this.getNodeParameter('inputText', itemIdx) as string; const inputPrompt = new HumanMessage(input); + + const systemPromptTemplateOpt = this.getNodeParameter( + 'options.systemPromptTemplate', + itemIdx, + ) as string; + const systemPromptTemplate = SystemMessagePromptTemplate.fromTemplate( + `${systemPromptTemplateOpt ?? SYSTEM_PROMPT_TEMPLATE} +{format_instructions} +${multiClassPrompt} +${fallbackPrompt}`, + ); + const messages = [ await systemPromptTemplate.format({ categories: categories.map((cat) => cat.category).join(', '), @@ -227,13 +234,27 @@ ${fallbackPrompt}`, const prompt = ChatPromptTemplate.fromMessages(messages); const chain = prompt.pipe(llm).pipe(parser).withConfig(getTracingConfig(this)); - const output = await chain.invoke(messages); - categories.forEach((cat, idx) => { - if (output[cat.category]) returnData[idx].push(items[itemIdx]); - }); - if (fallback === 'other' && output.fallback) - returnData[returnData.length - 1].push(items[itemIdx]); + try { + const output = await chain.invoke(messages); + + categories.forEach((cat, idx) => { + if (output[cat.category]) returnData[idx].push(item); + }); + if (fallback === 'other' && output.fallback) returnData[returnData.length - 1].push(item); + } catch (error) { + if (this.continueOnFail(error)) { + returnData[0].push({ + json: { error: error.message }, + pairedItem: { item: itemIdx }, + }); + + continue; + } + + throw error; + } } + return returnData; } } diff --git a/packages/@n8n/nodes-langchain/nodes/tools/ToolCode/ToolCode.node.ts b/packages/@n8n/nodes-langchain/nodes/tools/ToolCode/ToolCode.node.ts index 492284b190c78..6ae5a8807539b 100644 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolCode/ToolCode.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/tools/ToolCode/ToolCode.node.ts @@ -6,6 +6,7 @@ import type { SupplyData, ExecutionError, } from 'n8n-workflow'; + import { NodeConnectionType, NodeOperationError } from 'n8n-workflow'; import type { Sandbox } from 'n8n-nodes-base/dist/nodes/Code/Sandbox'; import { getSandboxContext } from 'n8n-nodes-base/dist/nodes/Code/Sandbox'; @@ -208,7 +209,7 @@ export class ToolCode implements INodeType { try { response = await runFunction(query); } catch (error: unknown) { - executionError = error as ExecutionError; + executionError = new NodeOperationError(this.getNode(), error as ExecutionError); response = `There was an error: "${executionError.message}"`; } @@ -229,6 +230,7 @@ export class ToolCode implements INodeType { } else { void this.addOutputData(NodeConnectionType.AiTool, index, [[{ json: { response } }]]); } + return response; }, }), diff --git a/packages/@n8n/nodes-langchain/package.json b/packages/@n8n/nodes-langchain/package.json index 9c10d94c495d9..3a328af0ae880 100644 --- a/packages/@n8n/nodes-langchain/package.json +++ b/packages/@n8n/nodes-langchain/package.json @@ -1,6 +1,6 @@ { "name": "@n8n/n8n-nodes-langchain", - "version": "1.52.0", + "version": "1.53.0", "description": "", "main": "index.js", "scripts": { @@ -121,7 +121,7 @@ ] }, "devDependencies": { - "@types/basic-auth": "^1.1.3", + "@types/basic-auth": "catalog:", "@types/cheerio": "^0.22.15", "@types/html-to-text": "^9.0.1", "@types/json-schema": "^7.0.15", @@ -153,24 +153,24 @@ "@langchain/textsplitters": "0.0.3", "@mozilla/readability": "^0.5.0", "@n8n/typeorm": "0.3.20-10", - "@n8n/vm2": "3.9.20", + "@n8n/vm2": "3.9.24", "@pinecone-database/pinecone": "3.0.0", "@qdrant/js-client-rest": "1.9.0", "@supabase/supabase-js": "2.43.4", "@types/pg": "^8.11.3", "@xata.io/client": "0.28.4", - "basic-auth": "2.0.1", + "basic-auth": "catalog:", "cheerio": "1.0.0-rc.12", "cohere-ai": "7.10.1", "d3-dsv": "2.0.0", "epub2": "3.0.2", - "form-data": "4.0.0", + "form-data": "catalog:", "generate-schema": "2.6.0", "html-to-text": "9.0.5", "jsdom": "^23.0.1", "json-schema-to-zod": "2.1.0", "langchain": "0.2.11", - "lodash": "4.17.21", + "lodash": "catalog:", "mammoth": "1.7.2", "n8n-nodes-base": "workspace:*", "n8n-workflow": "workspace:*", diff --git a/packages/@n8n_io/eslint-config/local-rules.js b/packages/@n8n_io/eslint-config/local-rules.js index c9b533a032e1e..152415e14c8d0 100644 --- a/packages/@n8n_io/eslint-config/local-rules.js +++ b/packages/@n8n_io/eslint-config/local-rules.js @@ -448,6 +448,36 @@ module.exports = { }; }, }, + + 'no-type-unsafe-event-emitter': { + meta: { + type: 'problem', + docs: { + description: 'Disallow extending from `EventEmitter`, which is not type-safe.', + recommended: 'error', + }, + messages: { + noExtendsEventEmitter: 'Extend from the type-safe `TypedEmitter` class instead.', + }, + }, + create(context) { + return { + ClassDeclaration(node) { + if ( + node.superClass && + node.superClass.type === 'Identifier' && + node.superClass.name === 'EventEmitter' && + node.id.name !== 'TypedEmitter' + ) { + context.report({ + node: node.superClass, + messageId: 'noExtendsEventEmitter', + }); + } + }, + }; + }, + }, }; const isJsonParseCall = (node) => diff --git a/packages/cli/.eslintrc.js b/packages/cli/.eslintrc.js index ac66574887551..17ecfee499ae9 100644 --- a/packages/cli/.eslintrc.js +++ b/packages/cli/.eslintrc.js @@ -21,6 +21,7 @@ module.exports = { rules: { 'n8n-local-rules/no-dynamic-import-template': 'error', 'n8n-local-rules/misplaced-n8n-typeorm-import': 'error', + 'n8n-local-rules/no-type-unsafe-event-emitter': 'error', complexity: 'error', // TODO: Remove this @@ -39,11 +40,17 @@ module.exports = { overrides: [ { - files: ['./src/databases/**/*.ts', './test/**/*.ts'], + files: ['./src/databases/**/*.ts', './test/**/*.ts', './src/**/__tests__/**/*.ts'], rules: { 'n8n-local-rules/misplaced-n8n-typeorm-import': 'off', }, }, + { + files: ['./test/**/*.ts', './src/**/__tests__/**/*.ts'], + rules: { + 'n8n-local-rules/no-type-unsafe-event-emitter': 'off', + }, + }, { files: ['./src/decorators/**/*.ts'], rules: { diff --git a/packages/cli/package.json b/packages/cli/package.json index e69fb4fc26c52..ca8f192e40152 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "1.52.0", + "version": "1.53.0", "description": "n8n Workflow Automation Tool", "main": "dist/index", "types": "dist/index.d.ts", @@ -56,12 +56,12 @@ "@types/compression": "1.0.1", "@types/convict": "^6.1.1", "@types/cookie-parser": "^1.4.2", - "@types/express": "^4.17.21", + "@types/express": "catalog:", "@types/flat": "^5.0.5", "@types/formidable": "^3.4.5", "@types/json-diff": "^1.0.0", "@types/jsonwebtoken": "^9.0.6", - "@types/lodash": "^4.14.195", + "@types/lodash": "catalog:", "@types/psl": "^1.1.0", "@types/replacestream": "^4.0.1", "@types/shelljs": "^0.8.11", @@ -69,10 +69,10 @@ "@types/superagent": "^8.1.7", "@types/swagger-ui-express": "^4.1.6", "@types/syslog-client": "^1.1.2", - "@types/uuid": "^8.3.2", + "@types/uuid": "catalog:", "@types/validator": "^13.7.0", "@types/ws": "^8.5.4", - "@types/xml2js": "^0.4.14", + "@types/xml2js": "catalog:", "@types/yamljs": "^0.2.31", "@vvo/tzdb": "^6.141.0", "chokidar": "^3.5.2", @@ -83,6 +83,7 @@ "dependencies": { "@azure/identity": "^4.3.0", "@azure/keyvault-secrets": "^4.8.0", + "@google-cloud/secret-manager": "^5.6.0", "@n8n/client-oauth2": "workspace:*", "@n8n/config": "workspace:*", "@n8n/localtunnel": "2.1.0", @@ -95,7 +96,7 @@ "@sentry/integrations": "7.87.0", "@sentry/node": "7.87.0", "aws4": "1.11.0", - "axios": "1.6.7", + "axios": "catalog:", "bcryptjs": "2.4.3", "bull": "4.12.1", "cache-manager": "5.2.3", @@ -115,7 +116,7 @@ "express-openapi-validator": "5.1.6", "express-prom-bundle": "6.6.0", "express-rate-limit": "7.2.0", - "fast-glob": "3.2.12", + "fast-glob": "catalog:", "flat": "5.0.2", "flatted": "3.2.7", "formidable": "3.5.1", @@ -128,14 +129,14 @@ "jsonschema": "1.4.1", "jsonwebtoken": "9.0.2", "ldapts": "4.2.6", - "lodash": "4.17.21", - "luxon": "3.3.0", + "lodash": "catalog:", + "luxon": "catalog:", "mysql2": "3.10.0", "n8n-core": "workspace:*", "n8n-editor-ui": "workspace:*", "n8n-nodes-base": "workspace:*", "n8n-workflow": "workspace:*", - "nanoid": "3.3.6", + "nanoid": "catalog:", "nodemailer": "6.9.9", "oauth-1.0a": "2.2.6", "open": "7.4.2", @@ -162,12 +163,12 @@ "sshpk": "1.17.0", "swagger-ui-express": "5.0.0", "syslog-client": "1.1.1", - "typedi": "0.10.0", - "uuid": "8.3.2", + "typedi": "catalog:", + "uuid": "catalog:", "validator": "13.7.0", "winston": "3.8.2", "ws": "8.17.1", - "xml2js": "0.6.2", + "xml2js": "catalog:", "xmllint-wasm": "3.0.1", "yamljs": "0.3.0", "zod": "3.22.4" diff --git a/packages/cli/src/AbstractServer.ts b/packages/cli/src/AbstractServer.ts index bef45bb1433d2..ab150e2947328 100644 --- a/packages/cli/src/AbstractServer.ts +++ b/packages/cli/src/AbstractServer.ts @@ -34,7 +34,7 @@ export abstract class AbstractServer { protected externalHooks: ExternalHooks; - protected protocol = Container.get(GlobalConfig).protocol; + protected globalConfig = Container.get(GlobalConfig); protected sslKey: string; @@ -74,15 +74,15 @@ export abstract class AbstractServer { this.sslKey = config.getEnv('ssl_key'); this.sslCert = config.getEnv('ssl_cert'); - this.restEndpoint = config.getEnv('endpoints.rest'); + this.restEndpoint = this.globalConfig.endpoints.rest; - this.endpointForm = config.getEnv('endpoints.form'); - this.endpointFormTest = config.getEnv('endpoints.formTest'); - this.endpointFormWaiting = config.getEnv('endpoints.formWaiting'); + this.endpointForm = this.globalConfig.endpoints.form; + this.endpointFormTest = this.globalConfig.endpoints.formTest; + this.endpointFormWaiting = this.globalConfig.endpoints.formWaiting; - this.endpointWebhook = config.getEnv('endpoints.webhook'); - this.endpointWebhookTest = config.getEnv('endpoints.webhookTest'); - this.endpointWebhookWaiting = config.getEnv('endpoints.webhookWaiting'); + this.endpointWebhook = this.globalConfig.endpoints.webhook; + this.endpointWebhookTest = this.globalConfig.endpoints.webhookTest; + this.endpointWebhookWaiting = this.globalConfig.endpoints.webhookWaiting; this.uniqueInstanceId = generateHostInstanceId(instanceType); @@ -134,7 +134,8 @@ export abstract class AbstractServer { } async init(): Promise { - const { app, protocol, sslKey, sslCert } = this; + const { app, sslKey, sslCert } = this; + const { protocol } = this.globalConfig; if (protocol === 'https' && sslKey && sslCert) { const https = await import('https'); @@ -261,14 +262,16 @@ export abstract class AbstractServer { return; } - this.logger.debug(`Shutting down ${this.protocol} server`); + const { protocol } = this.globalConfig; + + this.logger.debug(`Shutting down ${protocol} server`); this.server.close((error) => { if (error) { - this.logger.error(`Error while shutting down ${this.protocol} server`, { error }); + this.logger.error(`Error while shutting down ${protocol} server`, { error }); } - this.logger.debug(`${this.protocol} server shut down`); + this.logger.debug(`${protocol} server shut down`); }); } } diff --git a/packages/cli/src/ActiveExecutions.ts b/packages/cli/src/ActiveExecutions.ts index 97313d5cb2f07..c1a6e8ffd65a9 100644 --- a/packages/cli/src/ActiveExecutions.ts +++ b/packages/cli/src/ActiveExecutions.ts @@ -12,6 +12,7 @@ import { ExecutionCancelledError, sleep, } from 'n8n-workflow'; +import { strict as assert } from 'node:assert'; import type { ExecutionPayload, @@ -74,9 +75,7 @@ export class ActiveExecutions { } executionId = await this.executionRepository.createNewExecution(fullExecutionData); - if (executionId === undefined) { - throw new ApplicationError('There was an issue assigning an execution id to the execution'); - } + assert(executionId); await this.concurrencyControl.throttle({ mode, executionId }); executionStatus = 'running'; diff --git a/packages/cli/src/ActiveWebhooks.ts b/packages/cli/src/ActiveWebhooks.ts index 79626df025f75..43136e75d384a 100644 --- a/packages/cli/src/ActiveWebhooks.ts +++ b/packages/cli/src/ActiveWebhooks.ts @@ -72,11 +72,9 @@ export class ActiveWebhooks implements IWebhookManager { const pathElements = path.split('/').slice(1); // extracting params from path - // @ts-ignore webhook.webhookPath.split('/').forEach((ele, index) => { if (ele.startsWith(':')) { // write params to req.params - // @ts-ignore request.params[ele.slice(1)] = pathElements[index]; } }); diff --git a/packages/cli/src/ExternalSecrets/ExternalSecretsManager.ee.ts b/packages/cli/src/ExternalSecrets/ExternalSecretsManager.ee.ts index 03436f3d7acaf..2ae33be62aa9e 100644 --- a/packages/cli/src/ExternalSecrets/ExternalSecretsManager.ee.ts +++ b/packages/cli/src/ExternalSecrets/ExternalSecretsManager.ee.ts @@ -13,7 +13,7 @@ import { Logger } from '@/Logger'; import { jsonParse, type IDataObject, ApplicationError } from 'n8n-workflow'; import { EXTERNAL_SECRETS_INITIAL_BACKOFF, EXTERNAL_SECRETS_MAX_BACKOFF } from './constants'; import { License } from '@/License'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; import { updateIntervalTime } from './externalSecretsHelper.ee'; import { ExternalSecretsProviders } from './ExternalSecretsProviders.ee'; import { OrchestrationService } from '@/services/orchestration.service'; diff --git a/packages/cli/src/ExternalSecrets/ExternalSecretsProviders.ee.ts b/packages/cli/src/ExternalSecrets/ExternalSecretsProviders.ee.ts index 33228673c3bda..274a1d69aaa51 100644 --- a/packages/cli/src/ExternalSecrets/ExternalSecretsProviders.ee.ts +++ b/packages/cli/src/ExternalSecrets/ExternalSecretsProviders.ee.ts @@ -4,6 +4,7 @@ import { InfisicalProvider } from './providers/infisical'; import { VaultProvider } from './providers/vault'; import { AwsSecretsManager } from './providers/aws-secrets/aws-secrets-manager'; import { AzureKeyVault } from './providers/azure-key-vault/azure-key-vault'; +import { GcpSecretsManager } from './providers/gcp-secrets-manager/gcp-secrets-manager'; @Service() export class ExternalSecretsProviders { @@ -12,6 +13,7 @@ export class ExternalSecretsProviders { infisical: InfisicalProvider, vault: VaultProvider, azureKeyVault: AzureKeyVault, + gcpSecretsManager: GcpSecretsManager, }; getProvider(name: string): { new (): SecretsProvider } | null { diff --git a/packages/cli/test/unit/ExternalSecrets/ExternalSecretsManager.test.ts b/packages/cli/src/ExternalSecrets/__tests__/ExternalSecretsManager.ee.test.ts similarity index 97% rename from packages/cli/test/unit/ExternalSecrets/ExternalSecretsManager.test.ts rename to packages/cli/src/ExternalSecrets/__tests__/ExternalSecretsManager.ee.test.ts index cf72688d24397..92475c93115e9 100644 --- a/packages/cli/test/unit/ExternalSecrets/ExternalSecretsManager.test.ts +++ b/packages/cli/src/ExternalSecrets/__tests__/ExternalSecretsManager.ee.test.ts @@ -6,13 +6,13 @@ import { License } from '@/License'; import { ExternalSecretsManager } from '@/ExternalSecrets/ExternalSecretsManager.ee'; import { ExternalSecretsProviders } from '@/ExternalSecrets/ExternalSecretsProviders.ee'; import { InternalHooks } from '@/InternalHooks'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance } from '@test/mocking'; import { DummyProvider, ErrorProvider, FailedProvider, MockProviders, -} from '../../shared/ExternalSecrets/utils'; +} from '@test/ExternalSecrets/utils'; import { mock } from 'jest-mock-extended'; describe('External Secrets Manager', () => { diff --git a/packages/cli/src/ExternalSecrets/providers/__tests__/azure-key-vault.test.ts b/packages/cli/src/ExternalSecrets/providers/__tests__/azure-key-vault.test.ts index 329bfca2acd6e..8bacd6bbb9d21 100644 --- a/packages/cli/src/ExternalSecrets/providers/__tests__/azure-key-vault.test.ts +++ b/packages/cli/src/ExternalSecrets/providers/__tests__/azure-key-vault.test.ts @@ -37,7 +37,7 @@ describe('AzureKeyVault', () => { yield { name: 'secret1' }; yield { name: 'secret2' }; yield { name: 'secret3' }; // no value - yield { name: '#@&' }; // invalid name + yield { name: '#@&' }; // unsupported name }, })); @@ -65,6 +65,6 @@ describe('AzureKeyVault', () => { expect(azureKeyVault.getSecret('secret1')).toBe('value1'); expect(azureKeyVault.getSecret('secret2')).toBe('value2'); expect(azureKeyVault.getSecret('secret3')).toBeUndefined(); // no value - expect(azureKeyVault.getSecret('#@&')).toBeUndefined(); // invalid name + expect(azureKeyVault.getSecret('#@&')).toBeUndefined(); // unsupported name }); }); diff --git a/packages/cli/src/ExternalSecrets/providers/__tests__/gcp-secrets-manager.test.ts b/packages/cli/src/ExternalSecrets/providers/__tests__/gcp-secrets-manager.test.ts new file mode 100644 index 0000000000000..40673041a760c --- /dev/null +++ b/packages/cli/src/ExternalSecrets/providers/__tests__/gcp-secrets-manager.test.ts @@ -0,0 +1,87 @@ +import { mock } from 'jest-mock-extended'; +import { GcpSecretsManager } from '../gcp-secrets-manager/gcp-secrets-manager'; +import type { GcpSecretsManagerContext } from '../gcp-secrets-manager/types'; +import { SecretManagerServiceClient } from '@google-cloud/secret-manager'; +import type { google } from '@google-cloud/secret-manager/build/protos/protos'; + +jest.mock('@google-cloud/secret-manager'); + +type GcpSecretVersionResponse = google.cloud.secretmanager.v1.IAccessSecretVersionResponse; + +describe('GCP Secrets Manager', () => { + const gcpSecretsManager = new GcpSecretsManager(); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should update cached secrets', async () => { + /** + * Arrange + */ + const PROJECT_ID = 'my-project-id'; + + const SECRETS: Record = { + secret1: 'value1', + secret2: 'value2', + secret3: '', // no value + '#@&': 'value', // unsupported name + }; + + await gcpSecretsManager.init( + mock({ + settings: { serviceAccountKey: `{ "project_id": "${PROJECT_ID}" }` }, + }), + ); + + const listSpy = jest + .spyOn(SecretManagerServiceClient.prototype, 'listSecrets') + // @ts-expect-error Partial mock + .mockResolvedValue([ + [ + { name: `projects/${PROJECT_ID}/secrets/secret1` }, + { name: `projects/${PROJECT_ID}/secrets/secret2` }, + { name: `projects/${PROJECT_ID}/secrets/secret3` }, + { name: `projects/${PROJECT_ID}/secrets/#@&` }, + ], + ]); + + const getSpy = jest + .spyOn(SecretManagerServiceClient.prototype, 'accessSecretVersion') + .mockImplementation(async ({ name }: { name: string }) => { + const secretName = name.split('/')[3]; + return [ + { payload: { data: Buffer.from(SECRETS[secretName]) } }, + ] as GcpSecretVersionResponse[]; + }); + + /** + * Act + */ + await gcpSecretsManager.connect(); + await gcpSecretsManager.update(); + + /** + * Assert + */ + expect(listSpy).toHaveBeenCalled(); + + expect(getSpy).toHaveBeenCalledWith({ + name: `projects/${PROJECT_ID}/secrets/secret1/versions/latest`, + }); + expect(getSpy).toHaveBeenCalledWith({ + name: `projects/${PROJECT_ID}/secrets/secret2/versions/latest`, + }); + expect(getSpy).toHaveBeenCalledWith({ + name: `projects/${PROJECT_ID}/secrets/secret3/versions/latest`, + }); + expect(getSpy).not.toHaveBeenCalledWith({ + name: `projects/${PROJECT_ID}/secrets/#@&/versions/latest`, + }); + + expect(gcpSecretsManager.getSecret('secret1')).toBe('value1'); + expect(gcpSecretsManager.getSecret('secret2')).toBe('value2'); + expect(gcpSecretsManager.getSecret('secret3')).toBeUndefined(); // no value + expect(gcpSecretsManager.getSecret('#@&')).toBeUndefined(); // unsupported name + }); +}); diff --git a/packages/cli/src/ExternalSecrets/providers/azure-key-vault/azure-key-vault.ts b/packages/cli/src/ExternalSecrets/providers/azure-key-vault/azure-key-vault.ts index c51e54e799ba1..7df196bdf96c4 100644 --- a/packages/cli/src/ExternalSecrets/providers/azure-key-vault/azure-key-vault.ts +++ b/packages/cli/src/ExternalSecrets/providers/azure-key-vault/azure-key-vault.ts @@ -74,7 +74,7 @@ export class AzureKeyVault implements SecretsProvider { const credential = new ClientSecretCredential(tenantId, clientId, clientSecret); this.client = new SecretClient(`https://${vaultName}.vault.azure.net/`, credential); this.state = 'connected'; - } catch (error) { + } catch { this.state = 'error'; } } @@ -86,7 +86,7 @@ export class AzureKeyVault implements SecretsProvider { await this.client.listPropertiesOfSecrets().next(); return [true]; } catch (error: unknown) { - return [false, error instanceof Error ? error.message : 'unknown error']; + return [false, error instanceof Error ? error.message : 'Unknown error']; } } diff --git a/packages/cli/src/ExternalSecrets/providers/gcp-secrets-manager/gcp-secrets-manager.ts b/packages/cli/src/ExternalSecrets/providers/gcp-secrets-manager/gcp-secrets-manager.ts new file mode 100644 index 0000000000000..64ed49f05cc41 --- /dev/null +++ b/packages/cli/src/ExternalSecrets/providers/gcp-secrets-manager/gcp-secrets-manager.ts @@ -0,0 +1,136 @@ +import { SecretManagerServiceClient as GcpClient } from '@google-cloud/secret-manager'; +import { DOCS_HELP_NOTICE, EXTERNAL_SECRETS_NAME_REGEX } from '@/ExternalSecrets/constants'; +import type { SecretsProvider, SecretsProviderState } from '@/Interfaces'; +import { jsonParse, type INodeProperties } from 'n8n-workflow'; +import type { + GcpSecretsManagerContext, + GcpSecretAccountKey, + RawGcpSecretAccountKey, +} from './types'; + +export class GcpSecretsManager implements SecretsProvider { + name = 'gcpSecretsManager'; + + displayName = 'GCP Secrets Manager'; + + state: SecretsProviderState = 'initializing'; + + properties: INodeProperties[] = [ + DOCS_HELP_NOTICE, + { + displayName: 'Service Account Key', + name: 'serviceAccountKey', + type: 'string', + default: '', + required: true, + typeOptions: { password: true }, + placeholder: 'e.g. { "type": "service_account", "project_id": "gcp-secrets-store", ... }', + hint: 'Content of JSON file downloaded from Google Cloud Console.', + noDataExpression: true, + }, + ]; + + private cachedSecrets: Record = {}; + + private client: GcpClient; + + private settings: GcpSecretAccountKey; + + async init(context: GcpSecretsManagerContext) { + this.settings = this.parseSecretAccountKey(context.settings.serviceAccountKey); + } + + async connect() { + const { projectId, privateKey, clientEmail } = this.settings; + + try { + this.client = new GcpClient({ + credentials: { client_email: clientEmail, private_key: privateKey }, + projectId, + }); + this.state = 'connected'; + } catch { + this.state = 'error'; + } + } + + async test(): Promise<[boolean] | [boolean, string]> { + if (!this.client) return [false, 'Failed to connect to GCP Secrets Manager']; + + try { + await this.client.initialize(); + return [true]; + } catch (error: unknown) { + return [false, error instanceof Error ? error.message : 'Unknown error']; + } + } + + async disconnect() { + // unused + } + + async update() { + const { projectId } = this.settings; + + const [rawSecretNames] = await this.client.listSecrets({ + parent: `projects/${projectId}`, + }); + + const secretNames = rawSecretNames.reduce((acc, cur) => { + if (!cur.name || !EXTERNAL_SECRETS_NAME_REGEX.test(cur.name)) return acc; + + const secretName = cur.name.split('/').pop(); + + if (secretName) acc.push(secretName); + + return acc; + }, []); + + const promises = secretNames.map(async (name) => { + const versions = await this.client.accessSecretVersion({ + name: `projects/${projectId}/secrets/${name}/versions/latest`, + }); + + if (!Array.isArray(versions) || !versions.length) return null; + + const [latestVersion] = versions; + + if (!latestVersion.payload?.data) return null; + + const value = latestVersion.payload.data.toString(); + + if (!value) return null; + + return { name, value }; + }); + + const results = await Promise.all(promises); + + this.cachedSecrets = results.reduce>((acc, cur) => { + if (cur) acc[cur.name] = cur.value; + return acc; + }, {}); + } + + getSecret(name: string) { + return this.cachedSecrets[name]; + } + + hasSecret(name: string) { + return name in this.cachedSecrets; + } + + getSecretNames() { + return Object.keys(this.cachedSecrets); + } + + private parseSecretAccountKey(privateKey: string): GcpSecretAccountKey { + const parsed = jsonParse(privateKey, { fallbackValue: {} }); + + return { + projectId: parsed?.project_id ?? '', + clientEmail: parsed?.client_email ?? '', + privateKey: parsed?.private_key ?? '', + }; + } +} diff --git a/packages/cli/src/ExternalSecrets/providers/gcp-secrets-manager/types.ts b/packages/cli/src/ExternalSecrets/providers/gcp-secrets-manager/types.ts new file mode 100644 index 0000000000000..c9611df9c4c2f --- /dev/null +++ b/packages/cli/src/ExternalSecrets/providers/gcp-secrets-manager/types.ts @@ -0,0 +1,19 @@ +import type { SecretsProviderSettings } from '@/Interfaces'; + +type JsonString = string; + +export type GcpSecretsManagerContext = SecretsProviderSettings<{ + serviceAccountKey: JsonString; +}>; + +export type RawGcpSecretAccountKey = { + project_id?: string; + private_key?: string; + client_email?: string; +}; + +export type GcpSecretAccountKey = { + projectId: string; + clientEmail: string; + privateKey: string; +}; diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index 25f758185aea9..8ddbd10fb29c1 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -267,14 +267,6 @@ export interface IWebhookManager { executeWebhook(req: WebhookRequest, res: Response): Promise; } -export interface ITelemetryUserDeletionData { - user_id: string; - target_user_old_status: 'active' | 'invited'; - migration_strategy?: 'transfer_data' | 'delete_data'; - target_user_id?: string; - migration_user_id?: string; -} - export interface IVersionNotificationSettings { enabled: boolean; endpoint: string; diff --git a/packages/cli/src/InternalHooks.ts b/packages/cli/src/InternalHooks.ts index e6418743d7b1a..031a7fe3a6259 100644 --- a/packages/cli/src/InternalHooks.ts +++ b/packages/cli/src/InternalHooks.ts @@ -1,55 +1,30 @@ import { Service } from 'typedi'; -import { snakeCase } from 'change-case'; -import { get as pslGet } from 'psl'; -import type { - ExecutionStatus, - INodesGraphResult, - IRun, - ITelemetryTrackProperties, - IWorkflowBase, -} from 'n8n-workflow'; -import { TelemetryHelpers } from 'n8n-workflow'; - -import config from '@/config'; -import { N8N_VERSION } from '@/constants'; -import type { AuthProviderType } from '@db/entities/AuthIdentity'; +import type { ITelemetryTrackProperties } from 'n8n-workflow'; import type { User } from '@db/entities/User'; -import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; -import { determineFinalExecutionStatus } from '@/executionLifecycleHooks/shared/sharedHookFunctions'; -import type { - ITelemetryUserDeletionData, - IWorkflowDb, - IExecutionTrackProperties, -} from '@/Interfaces'; import { WorkflowStatisticsService } from '@/services/workflow-statistics.service'; -import { NodeTypes } from '@/NodeTypes'; import { Telemetry } from '@/telemetry'; -import type { Project } from '@db/entities/Project'; -import { ProjectRelationRepository } from './databases/repositories/projectRelation.repository'; import { MessageEventBus } from './eventbus/MessageEventBus/MessageEventBus'; /** - * @deprecated Do not add to this class. To add audit or telemetry events, use - * `EventService` to emit the event and then use the `AuditEventRelay` or + * @deprecated Do not add to this class. To add log streaming or telemetry events, use + * `EventService` to emit the event and then use the `LogStreamingEventRelay` or * `TelemetryEventRelay` to forward them to the event bus or telemetry. */ @Service() export class InternalHooks { constructor( private readonly telemetry: Telemetry, - private readonly nodeTypes: NodeTypes, - private readonly sharedWorkflowRepository: SharedWorkflowRepository, workflowStatisticsService: WorkflowStatisticsService, - private readonly projectRelationRepository: ProjectRelationRepository, + // Can't use @ts-expect-error because only dev time tsconfig considers this as an error, but not build time + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - needed until we decouple telemetry private readonly _eventBus: MessageEventBus, // needed until we decouple telemetry ) { - workflowStatisticsService.on( - 'telemetry.onFirstProductionWorkflowSuccess', - async (metrics) => await this.onFirstProductionWorkflowSuccess(metrics), + workflowStatisticsService.on('telemetry.onFirstProductionWorkflowSuccess', (metrics) => + this.onFirstProductionWorkflowSuccess(metrics), ); - workflowStatisticsService.on( - 'telemetry.onFirstWorkflowDataLoad', - async (metrics) => await this.onFirstWorkflowDataLoad(metrics), + workflowStatisticsService.on('telemetry.onFirstWorkflowDataLoad', (metrics) => + this.onFirstWorkflowDataLoad(metrics), ); } @@ -57,365 +32,41 @@ export class InternalHooks { await this.telemetry.init(); } - async onFrontendSettingsAPI(pushRef?: string): Promise { - return await this.telemetry.track('Session started', { session_id: pushRef }); - } - - async onPersonalizationSurveySubmitted( - userId: string, - answers: Record, - ): Promise { - const camelCaseKeys = Object.keys(answers); - const personalizationSurveyData = { user_id: userId } as Record; - camelCaseKeys.forEach((camelCaseKey) => { - personalizationSurveyData[snakeCase(camelCaseKey)] = answers[camelCaseKey]; - }); - - return await this.telemetry.track( - 'User responded to personalization questions', - personalizationSurveyData, - ); - } - - async onWorkflowCreated( - user: User, - workflow: IWorkflowBase, - project: Project, - publicApi: boolean, - ): Promise { - const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes); - - void this.telemetry.track('User created workflow', { - user_id: user.id, - workflow_id: workflow.id, - node_graph_string: JSON.stringify(nodeGraph), - public_api: publicApi, - project_id: project.id, - project_type: project.type, - }); - } - - async onWorkflowDeleted(user: User, workflowId: string, publicApi: boolean): Promise { - void this.telemetry.track('User deleted workflow', { - user_id: user.id, - workflow_id: workflowId, - public_api: publicApi, - }); - } - - async onWorkflowSaved(user: User, workflow: IWorkflowDb, publicApi: boolean): Promise { - const isCloudDeployment = config.getEnv('deployment.type') === 'cloud'; - - const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes, { - isCloudDeployment, - }); - - let userRole: 'owner' | 'sharee' | 'member' | undefined = undefined; - const role = await this.sharedWorkflowRepository.findSharingRole(user.id, workflow.id); - if (role) { - userRole = role === 'workflow:owner' ? 'owner' : 'sharee'; - } else { - const workflowOwner = await this.sharedWorkflowRepository.getWorkflowOwningProject( - workflow.id, - ); - - if (workflowOwner) { - const projectRole = await this.projectRelationRepository.findProjectRole({ - userId: user.id, - projectId: workflowOwner.id, - }); - - if (projectRole && projectRole !== 'project:personalOwner') { - userRole = 'member'; - } - } - } - - const notesCount = Object.keys(nodeGraph.notes).length; - const overlappingCount = Object.values(nodeGraph.notes).filter( - (note) => note.overlapping, - ).length; - - void this.telemetry.track('User saved workflow', { - user_id: user.id, - workflow_id: workflow.id, - node_graph_string: JSON.stringify(nodeGraph), - notes_count_overlapping: overlappingCount, - notes_count_non_overlapping: notesCount - overlappingCount, - version_cli: N8N_VERSION, - num_tags: workflow.tags?.length ?? 0, - public_api: publicApi, - sharing_role: userRole, - }); - } - - // eslint-disable-next-line complexity - async onWorkflowPostExecute( - _executionId: string, - workflow: IWorkflowBase, - runData?: IRun, - userId?: string, - ): Promise { - if (!workflow.id) { - return; - } - - if (runData?.status === 'waiting') { - // No need to send telemetry or logs when the workflow hasn't finished yet. - return; - } - - const promises = []; - - const telemetryProperties: IExecutionTrackProperties = { - workflow_id: workflow.id, - is_manual: false, - version_cli: N8N_VERSION, - success: false, - }; - - if (userId) { - telemetryProperties.user_id = userId; - } - - if (runData?.data.resultData.error?.message?.includes('canceled')) { - runData.status = 'canceled'; - } - - telemetryProperties.success = !!runData?.finished; - - // const executionStatus: ExecutionStatus = runData?.status ?? 'unknown'; - const executionStatus: ExecutionStatus = runData - ? determineFinalExecutionStatus(runData) - : 'unknown'; - - if (runData !== undefined) { - telemetryProperties.execution_mode = runData.mode; - telemetryProperties.is_manual = runData.mode === 'manual'; - - let nodeGraphResult: INodesGraphResult | null = null; - - if (!telemetryProperties.success && runData?.data.resultData.error) { - telemetryProperties.error_message = runData?.data.resultData.error.message; - let errorNodeName = - 'node' in runData?.data.resultData.error - ? runData?.data.resultData.error.node?.name - : undefined; - telemetryProperties.error_node_type = - 'node' in runData?.data.resultData.error - ? runData?.data.resultData.error.node?.type - : undefined; - - if (runData.data.resultData.lastNodeExecuted) { - const lastNode = TelemetryHelpers.getNodeTypeForName( - workflow, - runData.data.resultData.lastNodeExecuted, - ); - - if (lastNode !== undefined) { - telemetryProperties.error_node_type = lastNode.type; - errorNodeName = lastNode.name; - } - } - - if (telemetryProperties.is_manual) { - nodeGraphResult = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes); - telemetryProperties.node_graph = nodeGraphResult.nodeGraph; - telemetryProperties.node_graph_string = JSON.stringify(nodeGraphResult.nodeGraph); - - if (errorNodeName) { - telemetryProperties.error_node_id = nodeGraphResult.nameIndices[errorNodeName]; - } - } - } - - if (telemetryProperties.is_manual) { - if (!nodeGraphResult) { - nodeGraphResult = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes); - } - - let userRole: 'owner' | 'sharee' | undefined = undefined; - if (userId) { - const role = await this.sharedWorkflowRepository.findSharingRole(userId, workflow.id); - if (role) { - userRole = role === 'workflow:owner' ? 'owner' : 'sharee'; - } - } - - const manualExecEventProperties: ITelemetryTrackProperties = { - user_id: userId, - workflow_id: workflow.id, - status: executionStatus, - executionStatus: runData?.status ?? 'unknown', - error_message: telemetryProperties.error_message as string, - error_node_type: telemetryProperties.error_node_type, - node_graph_string: telemetryProperties.node_graph_string as string, - error_node_id: telemetryProperties.error_node_id as string, - webhook_domain: null, - sharing_role: userRole, - }; - - if (!manualExecEventProperties.node_graph_string) { - nodeGraphResult = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes); - manualExecEventProperties.node_graph_string = JSON.stringify(nodeGraphResult.nodeGraph); - } - - if (runData.data.startData?.destinationNode) { - const telemetryPayload = { - ...manualExecEventProperties, - node_type: TelemetryHelpers.getNodeTypeForName( - workflow, - runData.data.startData?.destinationNode, - )?.type, - node_id: nodeGraphResult.nameIndices[runData.data.startData?.destinationNode], - }; - - promises.push(this.telemetry.track('Manual node exec finished', telemetryPayload)); - } else { - nodeGraphResult.webhookNodeNames.forEach((name: string) => { - const execJson = runData.data.resultData.runData[name]?.[0]?.data?.main?.[0]?.[0] - ?.json as { headers?: { origin?: string } }; - if (execJson?.headers?.origin && execJson.headers.origin !== '') { - manualExecEventProperties.webhook_domain = pslGet( - execJson.headers.origin.replace(/^https?:\/\//, ''), - ); - } - }); - - promises.push( - this.telemetry.track('Manual workflow exec finished', manualExecEventProperties), - ); - } - } - } - - void Promise.all([...promises, this.telemetry.trackWorkflowExecution(telemetryProperties)]); + onFrontendSettingsAPI(pushRef?: string): void { + this.telemetry.track('Session started', { session_id: pushRef }); } - async onWorkflowSharingUpdate(workflowId: string, userId: string, userList: string[]) { + onWorkflowSharingUpdate(workflowId: string, userId: string, userList: string[]) { const properties: ITelemetryTrackProperties = { workflow_id: workflowId, user_id_sharer: userId, user_id_list: userList, }; - return await this.telemetry.track('User updated workflow sharing', properties); + this.telemetry.track('User updated workflow sharing', properties); } async onN8nStop(): Promise { const timeoutPromise = new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, 3000); + setTimeout(resolve, 3000); }); return await Promise.race([timeoutPromise, this.telemetry.trackN8nStop()]); } - async onUserDeletion(userDeletionData: { - user: User; - telemetryData: ITelemetryUserDeletionData; - publicApi: boolean; - }): Promise { - void this.telemetry.track('User deleted user', { - ...userDeletionData.telemetryData, - user_id: userDeletionData.user.id, - public_api: userDeletionData.publicApi, - }); - } - - async onUserInvite(userInviteData: { - user: User; - target_user_id: string[]; - public_api: boolean; - email_sent: boolean; - invitee_role: string; - }): Promise { - void this.telemetry.track('User invited new user', { - user_id: userInviteData.user.id, - target_user_id: userInviteData.target_user_id, - public_api: userInviteData.public_api, - email_sent: userInviteData.email_sent, - invitee_role: userInviteData.invitee_role, - }); - } - - async onUserRoleChange(userRoleChangeData: { - user: User; - target_user_id: string; - public_api: boolean; - target_user_new_role: string; - }) { - const { user, ...rest } = userRoleChangeData; - - void this.telemetry.track('User changed role', { user_id: user.id, ...rest }); - } - - async onUserRetrievedUser(userRetrievedData: { - user_id: string; - public_api: boolean; - }): Promise { - return await this.telemetry.track('User retrieved user', userRetrievedData); - } - - async onUserRetrievedAllUsers(userRetrievedData: { - user_id: string; - public_api: boolean; - }): Promise { - return await this.telemetry.track('User retrieved all users', userRetrievedData); - } - - async onUserRetrievedExecution(userRetrievedData: { - user_id: string; - public_api: boolean; - }): Promise { - return await this.telemetry.track('User retrieved execution', userRetrievedData); - } - - async onUserRetrievedAllExecutions(userRetrievedData: { - user_id: string; - public_api: boolean; - }): Promise { - return await this.telemetry.track('User retrieved all executions', userRetrievedData); - } - - async onUserRetrievedWorkflow(userRetrievedData: { - user_id: string; - public_api: boolean; - }): Promise { - return await this.telemetry.track('User retrieved workflow', userRetrievedData); - } - - async onUserRetrievedAllWorkflows(userRetrievedData: { - user_id: string; - public_api: boolean; - }): Promise { - return await this.telemetry.track('User retrieved all workflows', userRetrievedData); - } - - async onUserUpdate(userUpdateData: { user: User; fields_changed: string[] }): Promise { - void this.telemetry.track('User changed personal settings', { - user_id: userUpdateData.user.id, - fields_changed: userUpdateData.fields_changed, - }); - } - - async onUserInviteEmailClick(userInviteClickData: { - inviter: User; - invitee: User; - }): Promise { - void this.telemetry.track('User clicked invite link from email', { + onUserInviteEmailClick(userInviteClickData: { inviter: User; invitee: User }) { + this.telemetry.track('User clicked invite link from email', { user_id: userInviteClickData.invitee.id, }); } - async onUserPasswordResetEmailClick(userPasswordResetData: { user: User }): Promise { - void this.telemetry.track('User clicked password reset link from email', { + onUserPasswordResetEmailClick(userPasswordResetData: { user: User }) { + this.telemetry.track('User clicked password reset link from email', { user_id: userPasswordResetData.user.id, }); } - async onUserTransactionalEmail(userTransactionalEmailData: { + onUserTransactionalEmail(userTransactionalEmailData: { user_id: string; message_type: | 'Reset password' @@ -424,37 +75,21 @@ export class InternalHooks { | 'Workflow shared' | 'Credentials shared'; public_api: boolean; - }): Promise { - return await this.telemetry.track( - 'Instance sent transactional email to user', - userTransactionalEmailData, - ); + }) { + this.telemetry.track('Instance sent transactional email to user', userTransactionalEmailData); } - async onUserPasswordResetRequestClick(userPasswordResetData: { user: User }): Promise { - void this.telemetry.track('User requested password reset while logged out', { + onUserPasswordResetRequestClick(userPasswordResetData: { user: User }) { + this.telemetry.track('User requested password reset while logged out', { user_id: userPasswordResetData.user.id, }); } - async onInstanceOwnerSetup(instanceOwnerSetupData: { user_id: string }): Promise { - return await this.telemetry.track('Owner finished instance setup', instanceOwnerSetupData); - } - - async onUserSignup( - user: User, - userSignupData: { - user_type: AuthProviderType; - was_disabled_ldap_user: boolean; - }, - ): Promise { - void this.telemetry.track('User signed up', { - user_id: user.id, - ...userSignupData, - }); + onInstanceOwnerSetup(instanceOwnerSetupData: { user_id: string }) { + this.telemetry.track('Owner finished instance setup', instanceOwnerSetupData); } - async onEmailFailed(failedEmailData: { + onEmailFailed(failedEmailData: { user: User; message_type: | 'Reset password' @@ -463,8 +98,8 @@ export class InternalHooks { | 'Workflow shared' | 'Credentials shared'; public_api: boolean; - }): Promise { - void this.telemetry.track('Instance failed to send transactional email to user', { + }) { + this.telemetry.track('Instance failed to send transactional email to user', { user_id: failedEmailData.user.id, }); } @@ -472,21 +107,18 @@ export class InternalHooks { /* * Execution Statistics */ - async onFirstProductionWorkflowSuccess(data: { - user_id: string; - workflow_id: string; - }): Promise { - return await this.telemetry.track('Workflow first prod success', data); + onFirstProductionWorkflowSuccess(data: { user_id: string; workflow_id: string }) { + this.telemetry.track('Workflow first prod success', data); } - async onFirstWorkflowDataLoad(data: { + onFirstWorkflowDataLoad(data: { user_id: string; workflow_id: string; node_type: string; node_id: string; credential_type?: string; credential_id?: string; - }): Promise { - return await this.telemetry.track('Workflow first data fetched', data); + }) { + this.telemetry.track('Workflow first data fetched', data); } } diff --git a/packages/cli/test/unit/Ldap/helpers.test.ts b/packages/cli/src/Ldap/__tests__/helpers.test.ts similarity index 96% rename from packages/cli/test/unit/Ldap/helpers.test.ts rename to packages/cli/src/Ldap/__tests__/helpers.test.ts index debb96da382d1..719adea76c5a7 100644 --- a/packages/cli/test/unit/Ldap/helpers.test.ts +++ b/packages/cli/src/Ldap/__tests__/helpers.test.ts @@ -1,5 +1,5 @@ import { UserRepository } from '@/databases/repositories/user.repository'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance } from '@test/mocking'; import * as helpers from '@/Ldap/helpers.ee'; import { AuthIdentity } from '@/databases/entities/AuthIdentity'; import { User } from '@/databases/entities/User'; diff --git a/packages/cli/src/Ldap/ldap.controller.ee.ts b/packages/cli/src/Ldap/ldap.controller.ee.ts index 9bdbea39b2d99..7a56d8049d9ca 100644 --- a/packages/cli/src/Ldap/ldap.controller.ee.ts +++ b/packages/cli/src/Ldap/ldap.controller.ee.ts @@ -6,7 +6,7 @@ import { NON_SENSIBLE_LDAP_CONFIG_PROPERTIES } from './constants'; import { getLdapSynchronizations } from './helpers.ee'; import { LdapConfiguration } from './types'; import { LdapService } from './ldap.service.ee'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; @RestController('/ldap') export class LdapController { diff --git a/packages/cli/src/Ldap/ldap.service.ee.ts b/packages/cli/src/Ldap/ldap.service.ee.ts index 85c8c6c636ea6..32c3152fb5f47 100644 --- a/packages/cli/src/Ldap/ldap.service.ee.ts +++ b/packages/cli/src/Ldap/ldap.service.ee.ts @@ -44,7 +44,7 @@ import { LDAP_LOGIN_ENABLED, LDAP_LOGIN_LABEL, } from './constants'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; @Service() export class LdapService { diff --git a/packages/cli/src/License.ts b/packages/cli/src/License.ts index 25abf0ecd6829..643da18fd26e9 100644 --- a/packages/cli/src/License.ts +++ b/packages/cli/src/License.ts @@ -55,7 +55,7 @@ export class License { * This ensures the mains do not cause a 429 (too many requests) on license init. */ if (config.getEnv('multiMainSetup.enabled')) { - return autoRenewEnabled && config.getEnv('multiMainSetup.instanceType') === 'leader'; + return autoRenewEnabled && this.instanceSettings.isLeader; } return autoRenewEnabled; diff --git a/packages/cli/src/PublicApi/index.ts b/packages/cli/src/PublicApi/index.ts index d484a34e36941..af4fc97fc7907 100644 --- a/packages/cli/src/PublicApi/index.ts +++ b/packages/cli/src/PublicApi/index.ts @@ -15,7 +15,7 @@ import { UserRepository } from '@db/repositories/user.repository'; import { UrlService } from '@/services/url.service'; import type { AuthenticatedRequest } from '@/requests'; import { GlobalConfig } from '@n8n/config'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; async function createApiRouter( version: string, diff --git a/packages/cli/src/PublicApi/types.ts b/packages/cli/src/PublicApi/types.ts index 2e80deb1afbf7..631a8c09d7236 100644 --- a/packages/cli/src/PublicApi/types.ts +++ b/packages/cli/src/PublicApi/types.ts @@ -29,6 +29,7 @@ export declare namespace ExecutionRequest { includeData?: boolean; workflowId?: string; lastId?: string; + projectId?: string; } >; @@ -72,6 +73,7 @@ export declare namespace WorkflowRequest { workflowId?: number; active: boolean; name?: string; + projectId?: string; } >; @@ -82,6 +84,11 @@ export declare namespace WorkflowRequest { type Activate = Get; type GetTags = Get; type UpdateTags = AuthenticatedRequest<{ id: string }, {}, TagEntity[]>; + type Transfer = AuthenticatedRequest< + { workflowId: string }, + {}, + { destinationProjectId: string } + >; } export declare namespace UserRequest { @@ -136,6 +143,12 @@ export declare namespace CredentialRequest { >; type Delete = AuthenticatedRequest<{ id: string }, {}, {}, Record>; + + type Transfer = AuthenticatedRequest< + { workflowId: string }, + {}, + { destinationProjectId: string } + >; } export type OperationID = 'getUsers' | 'getUser'; diff --git a/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.handler.ts b/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.handler.ts index 4da7635831340..57b6bd66f6841 100644 --- a/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.handler.ts +++ b/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.handler.ts @@ -19,6 +19,8 @@ import { toJsonSchema, } from './credentials.service'; import { Container } from 'typedi'; +import { z } from 'zod'; +import { EnterpriseCredentialsService } from '@/credentials/credentials.service.ee'; export = { createCredential: [ @@ -44,6 +46,20 @@ export = { } }, ], + transferCredential: [ + projectScope('credential:move', 'credential'), + async (req: CredentialRequest.Transfer, res: express.Response) => { + const body = z.object({ destinationProjectId: z.string() }).parse(req.body); + + await Container.get(EnterpriseCredentialsService).transferOne( + req.user, + req.params.workflowId, + body.destinationProjectId, + ); + + res.status(204).send(); + }, + ], deleteCredential: [ projectScope('credential:delete', 'credential'), async ( diff --git a/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts b/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts index 668424530a516..2c4a35a6aa177 100644 --- a/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts +++ b/packages/cli/src/PublicApi/v1/handlers/credentials/credentials.service.ts @@ -17,7 +17,7 @@ import { Container } from 'typedi'; import { CredentialsRepository } from '@db/repositories/credentials.repository'; import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; import { ProjectRepository } from '@/databases/repositories/project.repository'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; export async function getCredentials(credentialId: string): Promise { return await Container.get(CredentialsRepository).findOneBy({ id: credentialId }); diff --git a/packages/cli/src/PublicApi/v1/handlers/credentials/spec/paths/credentials.id.transfer.yml b/packages/cli/src/PublicApi/v1/handlers/credentials/spec/paths/credentials.id.transfer.yml new file mode 100644 index 0000000000000..a9e9c5cf7c229 --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/credentials/spec/paths/credentials.id.transfer.yml @@ -0,0 +1,31 @@ +put: + x-eov-operation-id: transferCredential + x-eov-operation-handler: v1/handlers/credentials/credentials.handler + tags: + - Workflow + summary: Transfer a credential to another project. + description: Transfer a credential to another project. + parameters: + - $ref: '../schemas/parameters/credentialId.yml' + requestBody: + description: Destination project for the credential transfer. + content: + application/json: + schema: + type: object + properties: + destinationProjectId: + type: string + description: The ID of the project to transfer the credential to. + required: + - destinationProjectId + required: true + responses: + '200': + description: Operation successful. + '400': + $ref: '../../../../shared/spec/responses/badRequest.yml' + '401': + $ref: '../../../../shared/spec/responses/unauthorized.yml' + '404': + $ref: '../../../../shared/spec/responses/notFound.yml' diff --git a/packages/cli/src/PublicApi/v1/handlers/credentials/spec/schemas/parameters/credentialId.yml b/packages/cli/src/PublicApi/v1/handlers/credentials/spec/schemas/parameters/credentialId.yml new file mode 100644 index 0000000000000..f16676ce0b96c --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/credentials/spec/schemas/parameters/credentialId.yml @@ -0,0 +1,6 @@ +name: id +in: path +description: The ID of the credential. +required: true +schema: + type: string diff --git a/packages/cli/src/PublicApi/v1/handlers/executions/executions.handler.ts b/packages/cli/src/PublicApi/v1/handlers/executions/executions.handler.ts index cd936d1d1cec1..ab6927724c1aa 100644 --- a/packages/cli/src/PublicApi/v1/handlers/executions/executions.handler.ts +++ b/packages/cli/src/PublicApi/v1/handlers/executions/executions.handler.ts @@ -7,7 +7,7 @@ import { validCursor } from '../../shared/middlewares/global.middleware'; import type { ExecutionRequest } from '../../../types'; import { getSharedWorkflowIds } from '../workflows/workflows.service'; import { encodeNextCursor } from '../../shared/services/pagination.service'; -import { InternalHooks } from '@/InternalHooks'; +import { EventService } from '@/events/event.service'; import { ExecutionRepository } from '@db/repositories/execution.repository'; import { ConcurrencyControlService } from '@/concurrency/concurrency-control.service'; @@ -78,9 +78,9 @@ export = { return res.status(404).json({ message: 'Not Found' }); } - void Container.get(InternalHooks).onUserRetrievedExecution({ - user_id: req.user.id, - public_api: true, + Container.get(EventService).emit('user-retrieved-execution', { + userId: req.user.id, + publicApi: true, }); return res.json(replaceCircularReferences(execution)); @@ -95,9 +95,10 @@ export = { status = undefined, includeData = false, workflowId = undefined, + projectId, } = req.query; - const sharedWorkflowsIds = await getSharedWorkflowIds(req.user, ['workflow:read']); + const sharedWorkflowsIds = await getSharedWorkflowIds(req.user, ['workflow:read'], projectId); // user does not have workflows hence no executions // or the execution they are trying to access belongs to a workflow they do not own @@ -129,9 +130,9 @@ export = { const count = await Container.get(ExecutionRepository).getExecutionsCountForPublicApi(filters); - void Container.get(InternalHooks).onUserRetrievedAllExecutions({ - user_id: req.user.id, - public_api: true, + Container.get(EventService).emit('user-retrieved-all-executions', { + userId: req.user.id, + publicApi: true, }); return res.json({ diff --git a/packages/cli/src/PublicApi/v1/handlers/executions/spec/paths/executions.yml b/packages/cli/src/PublicApi/v1/handlers/executions/spec/paths/executions.yml index 8d26492ec30bf..6fcdaf356e4bb 100644 --- a/packages/cli/src/PublicApi/v1/handlers/executions/spec/paths/executions.yml +++ b/packages/cli/src/PublicApi/v1/handlers/executions/spec/paths/executions.yml @@ -21,6 +21,14 @@ get: schema: type: string example: '1000' + - name: projectId + in: query + required: false + explode: false + allowReserved: true + schema: + type: string + example: VmwOO9HeTEj20kxM - $ref: '../../../../shared/spec/parameters/limit.yml' - $ref: '../../../../shared/spec/parameters/cursor.yml' responses: diff --git a/packages/cli/src/PublicApi/v1/handlers/projects/projects.handler.ts b/packages/cli/src/PublicApi/v1/handlers/projects/projects.handler.ts new file mode 100644 index 0000000000000..e61e808daf301 --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/projects/projects.handler.ts @@ -0,0 +1,65 @@ +import { globalScope, isLicensed, validCursor } from '../../shared/middlewares/global.middleware'; +import type { Response } from 'express'; +import type { ProjectRequest } from '@/requests'; +import type { PaginatedRequest } from '@/PublicApi/types'; +import Container from 'typedi'; +import { ProjectController } from '@/controllers/project.controller'; +import { ProjectRepository } from '@/databases/repositories/project.repository'; +import { encodeNextCursor } from '../../shared/services/pagination.service'; + +type Create = ProjectRequest.Create; +type Update = ProjectRequest.Update; +type Delete = ProjectRequest.Delete; +type GetAll = PaginatedRequest; + +export = { + createProject: [ + isLicensed('feat:projectRole:admin'), + globalScope('project:create'), + async (req: Create, res: Response) => { + const project = await Container.get(ProjectController).createProject(req); + + return res.status(201).json(project); + }, + ], + updateProject: [ + isLicensed('feat:projectRole:admin'), + globalScope('project:update'), + async (req: Update, res: Response) => { + await Container.get(ProjectController).updateProject(req); + + return res.status(204).send(); + }, + ], + deleteProject: [ + isLicensed('feat:projectRole:admin'), + globalScope('project:delete'), + async (req: Delete, res: Response) => { + await Container.get(ProjectController).deleteProject(req); + + return res.status(204).send(); + }, + ], + getProjects: [ + isLicensed('feat:projectRole:admin'), + globalScope('project:list'), + validCursor, + async (req: GetAll, res: Response) => { + const { offset = 0, limit = 100 } = req.query; + + const [projects, count] = await Container.get(ProjectRepository).findAndCount({ + skip: offset, + take: limit, + }); + + return res.json({ + data: projects, + nextCursor: encodeNextCursor({ + offset, + limit, + numberOfTotalRecords: count, + }), + }); + }, + ], +}; diff --git a/packages/cli/src/PublicApi/v1/handlers/projects/spec/paths/projects.projectId.yml b/packages/cli/src/PublicApi/v1/handlers/projects/spec/paths/projects.projectId.yml new file mode 100644 index 0000000000000..a5aab19b3d6ed --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/projects/spec/paths/projects.projectId.yml @@ -0,0 +1,43 @@ +delete: + x-eov-operation-id: deleteProject + x-eov-operation-handler: v1/handlers/projects/projects.handler + tags: + - Projects + summary: Delete a project + description: Delete a project from your instance. + parameters: + - $ref: '../schemas/parameters/projectId.yml' + responses: + '204': + description: Operation successful. + '401': + $ref: '../../../../shared/spec/responses/unauthorized.yml' + '403': + $ref: '../../../../shared/spec/responses/forbidden.yml' + '404': + $ref: '../../../../shared/spec/responses/notFound.yml' +put: + x-eov-operation-id: updateProject + x-eov-operation-handler: v1/handlers/projects/projects.handler + tags: + - Project + summary: Update a project + description: Update a project. + requestBody: + description: Updated project object. + content: + application/json: + schema: + $ref: '../schemas/project.yml' + required: true + responses: + '204': + description: Operation successful. + '400': + $ref: '../../../../shared/spec/responses/badRequest.yml' + '401': + $ref: '../../../../shared/spec/responses/unauthorized.yml' + '403': + $ref: '../../../../shared/spec/responses/forbidden.yml' + '404': + $ref: '../../../../shared/spec/responses/notFound.yml' diff --git a/packages/cli/src/PublicApi/v1/handlers/projects/spec/paths/projects.yml b/packages/cli/src/PublicApi/v1/handlers/projects/spec/paths/projects.yml new file mode 100644 index 0000000000000..1babd3dd6ea03 --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/projects/spec/paths/projects.yml @@ -0,0 +1,40 @@ +post: + x-eov-operation-id: createProject + x-eov-operation-handler: v1/handlers/projects/projects.handler + tags: + - Projects + summary: Create a project + description: Create a project in your instance. + requestBody: + description: Payload for project to create. + content: + application/json: + schema: + $ref: '../schemas/project.yml' + required: true + responses: + '201': + description: Operation successful. + '400': + $ref: '../../../../shared/spec/responses/badRequest.yml' + '401': + $ref: '../../../../shared/spec/responses/unauthorized.yml' +get: + x-eov-operation-id: getProjects + x-eov-operation-handler: v1/handlers/projects/projects.handler + tags: + - Projects + summary: Retrieve projects + description: Retrieve projects from your instance. + parameters: + - $ref: '../../../../shared/spec/parameters/limit.yml' + - $ref: '../../../../shared/spec/parameters/cursor.yml' + responses: + '200': + description: Operation successful. + content: + application/json: + schema: + $ref: '../schemas/projectList.yml' + '401': + $ref: '../../../../shared/spec/responses/unauthorized.yml' diff --git a/packages/cli/src/PublicApi/v1/handlers/projects/spec/schemas/parameters/projectId.yml b/packages/cli/src/PublicApi/v1/handlers/projects/spec/schemas/parameters/projectId.yml new file mode 100644 index 0000000000000..32961f46016f9 --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/projects/spec/schemas/parameters/projectId.yml @@ -0,0 +1,6 @@ +name: projectId +in: path +description: The ID of the project. +required: true +schema: + type: string diff --git a/packages/cli/src/PublicApi/v1/handlers/projects/spec/schemas/project.yml b/packages/cli/src/PublicApi/v1/handlers/projects/spec/schemas/project.yml new file mode 100644 index 0000000000000..7a4d2ec432bdd --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/projects/spec/schemas/project.yml @@ -0,0 +1,13 @@ +type: object +additionalProperties: false +required: + - name +properties: + id: + type: string + readOnly: true + name: + type: string + type: + type: string + readOnly: true diff --git a/packages/cli/src/PublicApi/v1/handlers/projects/spec/schemas/projectList.yml b/packages/cli/src/PublicApi/v1/handlers/projects/spec/schemas/projectList.yml new file mode 100644 index 0000000000000..7d88be72fb008 --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/projects/spec/schemas/projectList.yml @@ -0,0 +1,11 @@ +type: object +properties: + data: + type: array + items: + $ref: './project.yml' + nextCursor: + type: string + description: Paginate through projects by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection. + nullable: true + example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA diff --git a/packages/cli/src/PublicApi/v1/handlers/sourceControl/sourceControl.handler.ts b/packages/cli/src/PublicApi/v1/handlers/sourceControl/sourceControl.handler.ts index f54e8bd95d8e2..7a3cf08ec0eb5 100644 --- a/packages/cli/src/PublicApi/v1/handlers/sourceControl/sourceControl.handler.ts +++ b/packages/cli/src/PublicApi/v1/handlers/sourceControl/sourceControl.handler.ts @@ -10,7 +10,7 @@ import { getTrackingInformationFromPullResult, isSourceControlLicensed, } from '@/environments/sourceControl/sourceControlHelper.ee'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; export = { pull: [ diff --git a/packages/cli/src/PublicApi/v1/handlers/users/spec/paths/users.id.role.yml b/packages/cli/src/PublicApi/v1/handlers/users/spec/paths/users.id.role.yml new file mode 100644 index 0000000000000..92993adf7f9d4 --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/users/spec/paths/users.id.role.yml @@ -0,0 +1,31 @@ +patch: + x-eov-operation-id: changeRole + x-eov-operation-handler: v1/handlers/users/users.handler.ee + tags: + - User + summary: Change a user's global role + description: Change a user's global role + parameters: + - $ref: '../schemas/parameters/userIdentifier.yml' + requestBody: + description: New role for the user + required: true + content: + application/json: + schema: + type: object + properties: + newRoleName: + type: string + enum: [global:admin, global:member] + required: + - newRoleName + responses: + '200': + description: Operation successful. + '401': + $ref: '../../../../shared/spec/responses/unauthorized.yml' + '403': + $ref: '../../../../shared/spec/responses/forbidden.yml' + '404': + $ref: '../../../../shared/spec/responses/notFound.yml' diff --git a/packages/cli/src/PublicApi/v1/handlers/users/spec/paths/users.id.yml b/packages/cli/src/PublicApi/v1/handlers/users/spec/paths/users.id.yml index 0d3c86c4cef97..f3dcae00534c4 100644 --- a/packages/cli/src/PublicApi/v1/handlers/users/spec/paths/users.id.yml +++ b/packages/cli/src/PublicApi/v1/handlers/users/spec/paths/users.id.yml @@ -17,3 +17,21 @@ get: $ref: '../schemas/user.yml' '401': $ref: '../../../../shared/spec/responses/unauthorized.yml' +delete: + x-eov-operation-id: deleteUser + x-eov-operation-handler: v1/handlers/users/users.handler.ee + tags: + - User + summary: Delete a user + description: Delete a user from your instance. + parameters: + - $ref: '../schemas/parameters/userIdentifier.yml' + responses: + '204': + description: Operation successful. + '401': + $ref: '../../../../shared/spec/responses/unauthorized.yml' + '403': + $ref: '../../../../shared/spec/responses/forbidden.yml' + '404': + $ref: '../../../../shared/spec/responses/notFound.yml' diff --git a/packages/cli/src/PublicApi/v1/handlers/users/spec/paths/users.yml b/packages/cli/src/PublicApi/v1/handlers/users/spec/paths/users.yml index 1d618435531b5..e767ab33cc16b 100644 --- a/packages/cli/src/PublicApi/v1/handlers/users/spec/paths/users.yml +++ b/packages/cli/src/PublicApi/v1/handlers/users/spec/paths/users.yml @@ -9,6 +9,14 @@ get: - $ref: '../../../../shared/spec/parameters/limit.yml' - $ref: '../../../../shared/spec/parameters/cursor.yml' - $ref: '../schemas/parameters/includeRole.yml' + - name: projectId + in: query + required: false + explode: false + allowReserved: true + schema: + type: string + example: VmwOO9HeTEj20kxM responses: '200': description: Operation successful. @@ -18,3 +26,53 @@ get: $ref: '../schemas/userList.yml' '401': $ref: '../../../../shared/spec/responses/unauthorized.yml' +post: + x-eov-operation-id: createUser + x-eov-operation-handler: v1/handlers/users/users.handler.ee + tags: + - User + summary: Create multiple users + description: Create one or more users. + requestBody: + description: Array of users to be created. + required: true + content: + application/json: + schema: + type: array + items: + type: object + properties: + email: + type: string + format: email + role: + type: string + enum: [global:admin, global:member] + required: + - email + responses: + '200': + description: Operation successful. + content: + application/json: + schema: + type: object + properties: + user: + type: object + properties: + id: + type: string + email: + type: string + inviteAcceptUrl: + type: string + emailSent: + type: boolean + error: + type: string + '401': + $ref: '../../../../shared/spec/responses/unauthorized.yml' + '403': + $ref: '../../../../shared/spec/responses/forbidden.yml' diff --git a/packages/cli/src/PublicApi/v1/handlers/users/users.handler.ee.ts b/packages/cli/src/PublicApi/v1/handlers/users/users.handler.ee.ts index 96b2d57239c57..59a7a4c3e3f42 100644 --- a/packages/cli/src/PublicApi/v1/handlers/users/users.handler.ee.ts +++ b/packages/cli/src/PublicApi/v1/handlers/users/users.handler.ee.ts @@ -6,11 +6,20 @@ import { clean, getAllUsersAndCount, getUser } from './users.service.ee'; import { encodeNextCursor } from '../../shared/services/pagination.service'; import { globalScope, + isLicensed, validCursor, validLicenseWithUserQuota, } from '../../shared/middlewares/global.middleware'; import type { UserRequest } from '@/requests'; -import { InternalHooks } from '@/InternalHooks'; +import { EventService } from '@/events/event.service'; +import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository'; +import type { Response } from 'express'; +import { InvitationController } from '@/controllers/invitation.controller'; +import { UsersController } from '@/controllers/users.controller'; + +type Create = UserRequest.Invite; +type Delete = UserRequest.Delete; +type ChangeRole = UserRequest.ChangeRole; export = { getUser: [ @@ -28,12 +37,10 @@ export = { }); } - const telemetryData = { - user_id: req.user.id, - public_api: true, - }; - - void Container.get(InternalHooks).onUserRetrievedUser(telemetryData); + Container.get(EventService).emit('user-retrieved-user', { + userId: req.user.id, + publicApi: true, + }); return res.json(clean(user, { includeRole })); }, @@ -43,20 +50,23 @@ export = { validCursor, globalScope(['user:list', 'user:read']), async (req: UserRequest.Get, res: express.Response) => { - const { offset = 0, limit = 100, includeRole = false } = req.query; + const { offset = 0, limit = 100, includeRole = false, projectId } = req.query; + + const _in = projectId + ? await Container.get(ProjectRelationRepository).findUserIdsByProjectId(projectId) + : undefined; const [users, count] = await getAllUsersAndCount({ includeRole, limit, offset, + in: _in, }); - const telemetryData = { - user_id: req.user.id, - public_api: true, - }; - - void Container.get(InternalHooks).onUserRetrievedAllUsers(telemetryData); + Container.get(EventService).emit('user-retrieved-all-users', { + userId: req.user.id, + publicApi: true, + }); return res.json({ data: clean(users, { includeRole }), @@ -68,4 +78,29 @@ export = { }); }, ], + createUser: [ + globalScope('user:create'), + async (req: Create, res: Response) => { + const usersInvited = await Container.get(InvitationController).inviteUser(req); + + return res.status(201).json(usersInvited); + }, + ], + deleteUser: [ + globalScope('user:delete'), + async (req: Delete, res: Response) => { + await Container.get(UsersController).deleteUser(req); + + return res.status(204).send(); + }, + ], + changeRole: [ + isLicensed('feat:advancedPermissions'), + globalScope('user:changeRole'), + async (req: ChangeRole, res: Response) => { + await Container.get(UsersController).changeGlobalRole(req); + + return res.status(204).send(); + }, + ], }; diff --git a/packages/cli/src/PublicApi/v1/handlers/users/users.service.ee.ts b/packages/cli/src/PublicApi/v1/handlers/users/users.service.ee.ts index f7bf6618168f0..e62c9465747ed 100644 --- a/packages/cli/src/PublicApi/v1/handlers/users/users.service.ee.ts +++ b/packages/cli/src/PublicApi/v1/handlers/users/users.service.ee.ts @@ -3,6 +3,8 @@ import { UserRepository } from '@db/repositories/user.repository'; import type { User } from '@db/entities/User'; import pick from 'lodash/pick'; import { validate as uuidValidate } from 'uuid'; +// eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import +import { In } from '@n8n/typeorm'; export async function getUser(data: { withIdentifier: string; @@ -25,9 +27,12 @@ export async function getAllUsersAndCount(data: { includeRole?: boolean; limit?: number; offset?: number; + in?: string[]; }): Promise<[User[], number]> { + const { in: _in } = data; + const users = await Container.get(UserRepository).find({ - where: {}, + where: { ...(_in && { id: In(_in) }) }, skip: data.offset, take: data.limit, }); diff --git a/packages/cli/src/PublicApi/v1/handlers/variables/spec/paths/variables.id.yml b/packages/cli/src/PublicApi/v1/handlers/variables/spec/paths/variables.id.yml new file mode 100644 index 0000000000000..79c65416b26f6 --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/variables/spec/paths/variables.id.yml @@ -0,0 +1,16 @@ +delete: + x-eov-operation-id: deleteVariable + x-eov-operation-handler: v1/handlers/variables/variables.handler + tags: + - Variables + summary: Delete a variable + description: Delete a variable from your instance. + parameters: + - $ref: '../schemas/parameters/variableId.yml' + responses: + '204': + description: Operation successful. + '401': + $ref: '../../../../shared/spec/responses/unauthorized.yml' + '404': + $ref: '../../../../shared/spec/responses/notFound.yml' diff --git a/packages/cli/src/PublicApi/v1/handlers/variables/spec/paths/variables.yml b/packages/cli/src/PublicApi/v1/handlers/variables/spec/paths/variables.yml new file mode 100644 index 0000000000000..7418c2fe05716 --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/variables/spec/paths/variables.yml @@ -0,0 +1,40 @@ +post: + x-eov-operation-id: createVariable + x-eov-operation-handler: v1/handlers/variables/variables.handler + tags: + - Variables + summary: Create a variable + description: Create a variable in your instance. + requestBody: + description: Payload for variable to create. + content: + application/json: + schema: + $ref: '../schemas/variable.yml' + required: true + responses: + '201': + description: Operation successful. + '400': + $ref: '../../../../shared/spec/responses/badRequest.yml' + '401': + $ref: '../../../../shared/spec/responses/unauthorized.yml' +get: + x-eov-operation-id: getVariables + x-eov-operation-handler: v1/handlers/variables/variables.handler + tags: + - Variables + summary: Retrieve variables + description: Retrieve variables from your instance. + parameters: + - $ref: '../../../../shared/spec/parameters/limit.yml' + - $ref: '../../../../shared/spec/parameters/cursor.yml' + responses: + '200': + description: Operation successful. + content: + application/json: + schema: + $ref: '../schemas/variableList.yml' + '401': + $ref: '../../../../shared/spec/responses/unauthorized.yml' diff --git a/packages/cli/src/PublicApi/v1/handlers/variables/spec/schemas/parameters/variableId.yml b/packages/cli/src/PublicApi/v1/handlers/variables/spec/schemas/parameters/variableId.yml new file mode 100644 index 0000000000000..a886039d8e195 --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/variables/spec/schemas/parameters/variableId.yml @@ -0,0 +1,6 @@ +name: id +in: path +description: The ID of the variable. +required: true +schema: + type: string diff --git a/packages/cli/src/PublicApi/v1/handlers/variables/spec/schemas/variable.yml b/packages/cli/src/PublicApi/v1/handlers/variables/spec/schemas/variable.yml new file mode 100644 index 0000000000000..319ad8d4403ae --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/variables/spec/schemas/variable.yml @@ -0,0 +1,17 @@ +type: object +additionalProperties: false +required: + - key + - value +properties: + id: + type: string + readOnly: true + key: + type: string + value: + type: string + example: test + type: + type: string + readOnly: true diff --git a/packages/cli/src/PublicApi/v1/handlers/variables/spec/schemas/variableList.yml b/packages/cli/src/PublicApi/v1/handlers/variables/spec/schemas/variableList.yml new file mode 100644 index 0000000000000..95e66e180f426 --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/variables/spec/schemas/variableList.yml @@ -0,0 +1,11 @@ +type: object +properties: + data: + type: array + items: + $ref: './variable.yml' + nextCursor: + type: string + description: Paginate through variables by setting the cursor parameter to a nextCursor attribute returned by a previous request. Default value fetches the first "page" of the collection. + nullable: true + example: MTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDA diff --git a/packages/cli/src/PublicApi/v1/handlers/variables/variables.handler.ts b/packages/cli/src/PublicApi/v1/handlers/variables/variables.handler.ts new file mode 100644 index 0000000000000..3073044eac03d --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/variables/variables.handler.ts @@ -0,0 +1,55 @@ +import Container from 'typedi'; +import { VariablesRepository } from '@/databases/repositories/variables.repository'; +import { VariablesController } from '@/environments/variables/variables.controller.ee'; +import { globalScope, isLicensed, validCursor } from '../../shared/middlewares/global.middleware'; +import { encodeNextCursor } from '../../shared/services/pagination.service'; +import type { Response } from 'express'; +import type { VariablesRequest } from '@/requests'; +import type { PaginatedRequest } from '@/PublicApi/types'; + +type Create = VariablesRequest.Create; +type Delete = VariablesRequest.Delete; +type GetAll = PaginatedRequest; + +export = { + createVariable: [ + isLicensed('feat:variables'), + globalScope('variable:create'), + async (req: Create, res: Response) => { + await Container.get(VariablesController).createVariable(req); + + res.status(201).send(); + }, + ], + deleteVariable: [ + isLicensed('feat:variables'), + globalScope('variable:delete'), + async (req: Delete, res: Response) => { + await Container.get(VariablesController).deleteVariable(req); + + res.status(204).send(); + }, + ], + getVariables: [ + isLicensed('feat:variables'), + globalScope('variable:list'), + validCursor, + async (req: GetAll, res: Response) => { + const { offset = 0, limit = 100 } = req.query; + + const [variables, count] = await Container.get(VariablesRepository).findAndCount({ + skip: offset, + take: limit, + }); + + return res.json({ + data: variables, + nextCursor: encodeNextCursor({ + offset, + limit, + numberOfTotalRecords: count, + }), + }); + }, + ], +}; diff --git a/packages/cli/src/PublicApi/v1/handlers/workflows/spec/paths/workflows.id.transfer.yml b/packages/cli/src/PublicApi/v1/handlers/workflows/spec/paths/workflows.id.transfer.yml new file mode 100644 index 0000000000000..3997647e1dafa --- /dev/null +++ b/packages/cli/src/PublicApi/v1/handlers/workflows/spec/paths/workflows.id.transfer.yml @@ -0,0 +1,31 @@ +put: + x-eov-operation-id: transferWorkflow + x-eov-operation-handler: v1/handlers/workflows/workflows.handler + tags: + - Workflow + summary: Transfer a workflow to another project. + description: Transfer a workflow to another project. + parameters: + - $ref: '../schemas/parameters/workflowId.yml' + requestBody: + description: Destination project information for the workflow transfer. + content: + application/json: + schema: + type: object + properties: + destinationProjectId: + type: string + description: The ID of the project to transfer the workflow to. + required: + - destinationProjectId + required: true + responses: + '200': + description: Operation successful. + '400': + $ref: '../../../../shared/spec/responses/badRequest.yml' + '401': + $ref: '../../../../shared/spec/responses/unauthorized.yml' + '404': + $ref: '../../../../shared/spec/responses/notFound.yml' diff --git a/packages/cli/src/PublicApi/v1/handlers/workflows/spec/paths/workflows.yml b/packages/cli/src/PublicApi/v1/handlers/workflows/spec/paths/workflows.yml index 6db149195df26..1024e36cb5948 100644 --- a/packages/cli/src/PublicApi/v1/handlers/workflows/spec/paths/workflows.yml +++ b/packages/cli/src/PublicApi/v1/handlers/workflows/spec/paths/workflows.yml @@ -52,6 +52,14 @@ get: schema: type: string example: My Workflow + - name: projectId + in: query + required: false + explode: false + allowReserved: true + schema: + type: string + example: VmwOO9HeTEj20kxM - $ref: '../../../../shared/spec/parameters/limit.yml' - $ref: '../../../../shared/spec/parameters/cursor.yml' responses: diff --git a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts index 943d53764daf3..d5abac95a1787 100644 --- a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts +++ b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.handler.ts @@ -26,13 +26,14 @@ import { updateTags, } from './workflows.service'; import { WorkflowService } from '@/workflows/workflow.service'; -import { InternalHooks } from '@/InternalHooks'; import { WorkflowHistoryService } from '@/workflows/workflowHistory/workflowHistory.service.ee'; import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflow.repository'; import { TagRepository } from '@/databases/repositories/tag.repository'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import { ProjectRepository } from '@/databases/repositories/project.repository'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; +import { z } from 'zod'; +import { EnterpriseWorkflowService } from '@/workflows/workflow.service.ee'; export = { createWorkflow: [ @@ -58,15 +59,31 @@ export = { ); await Container.get(ExternalHooks).run('workflow.afterCreate', [createdWorkflow]); - void Container.get(InternalHooks).onWorkflowCreated(req.user, createdWorkflow, project, true); Container.get(EventService).emit('workflow-created', { workflow: createdWorkflow, user: req.user, + publicApi: true, + projectId: project.id, + projectType: project.type, }); return res.json(createdWorkflow); }, ], + transferWorkflow: [ + projectScope('workflow:move', 'workflow'), + async (req: WorkflowRequest.Transfer, res: express.Response) => { + const body = z.object({ destinationProjectId: z.string() }).parse(req.body); + + await Container.get(EnterpriseWorkflowService).transferOne( + req.user, + req.params.workflowId, + body.destinationProjectId, + ); + + res.status(204).send(); + }, + ], deleteWorkflow: [ projectScope('workflow:delete', 'workflow'), async (req: WorkflowRequest.Get, res: express.Response): Promise => { @@ -101,9 +118,9 @@ export = { return res.status(404).json({ message: 'Not Found' }); } - void Container.get(InternalHooks).onUserRetrievedWorkflow({ - user_id: req.user.id, - public_api: true, + Container.get(EventService).emit('user-retrieved-workflow', { + userId: req.user.id, + publicApi: true, }); return res.json(workflow); @@ -112,7 +129,7 @@ export = { getWorkflows: [ validCursor, async (req: WorkflowRequest.GetAll, res: express.Response): Promise => { - const { offset = 0, limit = 100, active, tags, name } = req.query; + const { offset = 0, limit = 100, active, tags, name, projectId } = req.query; const where: FindOptionsWhere = { ...(active !== undefined && { active }), @@ -145,6 +162,10 @@ export = { workflows = workflows.filter((wf) => workflowIds.includes(wf.id)); } + if (projectId) { + workflows = workflows.filter((w) => w.projectId === projectId); + } + if (!workflows.length) { return res.status(200).json({ data: [], @@ -163,9 +184,9 @@ export = { ...(!config.getEnv('workflowTagsDisabled') && { relations: ['tags'] }), }); - void Container.get(InternalHooks).onUserRetrievedAllWorkflows({ - user_id: req.user.id, - public_api: true, + Container.get(EventService).emit('user-retrieved-all-workflows', { + userId: req.user.id, + publicApi: true, }); return res.json({ @@ -239,11 +260,10 @@ export = { } await Container.get(ExternalHooks).run('workflow.afterUpdate', [updateData]); - void Container.get(InternalHooks).onWorkflowSaved(req.user, updateData, true); Container.get(EventService).emit('workflow-saved', { user: req.user, - workflowId: updateData.id, - workflowName: updateData.name, + workflow: updateData, + publicApi: true, }); return res.json(updatedWorkflow); diff --git a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.service.ts b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.service.ts index d301e61c93966..3e3afc078ed92 100644 --- a/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.service.ts +++ b/packages/cli/src/PublicApi/v1/handlers/workflows/workflows.service.ts @@ -17,15 +17,21 @@ function insertIf(condition: boolean, elements: string[]): string[] { return condition ? elements : []; } -export async function getSharedWorkflowIds(user: User, scopes: Scope[]): Promise { +export async function getSharedWorkflowIds( + user: User, + scopes: Scope[], + projectId?: string, +): Promise { if (Container.get(License).isSharingEnabled()) { return await Container.get(WorkflowSharingService).getSharedWorkflowIds(user, { scopes, + projectId, }); } else { return await Container.get(WorkflowSharingService).getSharedWorkflowIds(user, { workflowRoles: ['workflow:owner'], projectRoles: ['project:personalOwner'], + projectId, }); } } diff --git a/packages/cli/src/PublicApi/v1/openapi.yml b/packages/cli/src/PublicApi/v1/openapi.yml index 9d8249983511c..30c3a73bde0c7 100644 --- a/packages/cli/src/PublicApi/v1/openapi.yml +++ b/packages/cli/src/PublicApi/v1/openapi.yml @@ -30,6 +30,10 @@ tags: description: Operations about tags - name: SourceControl description: Operations about source control + - name: Variables + description: Operations about variables + - name: Projects + description: Operations about projects paths: /audit: @@ -56,14 +60,28 @@ paths: $ref: './handlers/workflows/spec/paths/workflows.id.activate.yml' /workflows/{id}/deactivate: $ref: './handlers/workflows/spec/paths/workflows.id.deactivate.yml' + /workflows/{id}/transfer: + $ref: './handlers/workflows/spec/paths/workflows.id.transfer.yml' + /credentials/{id}/transfer: + $ref: './handlers/credentials/spec/paths/credentials.id.transfer.yml' /workflows/{id}/tags: $ref: './handlers/workflows/spec/paths/workflows.id.tags.yml' /users: $ref: './handlers/users/spec/paths/users.yml' /users/{id}: $ref: './handlers/users/spec/paths/users.id.yml' + /users/{id}/role: + $ref: './handlers/users/spec/paths/users.id.role.yml' /source-control/pull: $ref: './handlers/sourceControl/spec/paths/sourceControl.yml' + /variables: + $ref: './handlers/variables/spec/paths/variables.yml' + /variables/{id}: + $ref: './handlers/variables/spec/paths/variables.id.yml' + /projects: + $ref: './handlers/projects/spec/paths/projects.yml' + /projects/{projectId}: + $ref: './handlers/projects/spec/paths/projects.projectId.yml' components: schemas: $ref: './shared/spec/schemas/_index.yml' diff --git a/packages/cli/src/PublicApi/v1/shared/middlewares/global.middleware.ts b/packages/cli/src/PublicApi/v1/shared/middlewares/global.middleware.ts index 6fa9bed113666..7e8c39bb91a3d 100644 --- a/packages/cli/src/PublicApi/v1/shared/middlewares/global.middleware.ts +++ b/packages/cli/src/PublicApi/v1/shared/middlewares/global.middleware.ts @@ -9,6 +9,8 @@ import type { PaginatedRequest } from '../../../types'; import { decodeCursor } from '../services/pagination.service'; import type { Scope } from '@n8n/permissions'; import { userHasScope } from '@/permissions/checkAccess'; +import type { BooleanLicenseFeature } from '@/Interfaces'; +import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error'; const UNLIMITED_USERS_QUOTA = -1; @@ -86,3 +88,11 @@ export const validLicenseWithUserQuota = ( return next(); }; + +export const isLicensed = (feature: BooleanLicenseFeature) => { + return async (_: AuthenticatedRequest, res: express.Response, next: express.NextFunction) => { + if (Container.get(License).isFeatureEnabled(feature)) return next(); + + return res.status(403).json({ message: new FeatureNotLicensedError(feature).message }); + }; +}; diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index dc63d697e77b8..0eb2040e7f1bb 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -6,7 +6,6 @@ import { promisify } from 'util'; import cookieParser from 'cookie-parser'; import express from 'express'; import helmet from 'helmet'; -import { GlobalConfig } from '@n8n/config'; import { InstanceSettings } from 'n8n-core'; import type { IN8nUISettings } from 'n8n-workflow'; @@ -36,7 +35,7 @@ import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; import { handleMfaDisable, isMfaFeatureEnabled } from '@/Mfa/helpers'; import type { FrontendService } from '@/services/frontend.service'; import { OrchestrationService } from '@/services/orchestration.service'; -import { AuditEventRelay } from './eventbus/audit-event-relay.service'; +import { LogStreamingEventRelay } from '@/events/log-streaming-event-relay'; import '@/controllers/activeWorkflows.controller'; import '@/controllers/auth.controller'; @@ -65,7 +64,7 @@ import '@/ExternalSecrets/ExternalSecrets.controller.ee'; import '@/license/license.controller'; import '@/workflows/workflowHistory/workflowHistory.controller.ee'; import '@/workflows/workflows.controller'; -import { EventService } from './eventbus/event.service'; +import { EventService } from './events/event.service'; const exec = promisify(callbackExec); @@ -81,17 +80,16 @@ export class Server extends AbstractServer { private readonly loadNodesAndCredentials: LoadNodesAndCredentials, private readonly orchestrationService: OrchestrationService, private readonly postHogClient: PostHogClient, - private readonly globalConfig: GlobalConfig, private readonly eventService: EventService, ) { super('main'); this.testWebhooksEnabled = true; - this.webhooksEnabled = !config.getEnv('endpoints.disableProductionWebhooksOnMainProcess'); + this.webhooksEnabled = !this.globalConfig.endpoints.disableProductionWebhooksOnMainProcess; } async start() { - if (!config.getEnv('endpoints.disableUi')) { + if (!this.globalConfig.endpoints.disableUi) { const { FrontendService } = await import('@/services/frontend.service'); this.frontendService = Container.get(FrontendService); } @@ -133,7 +131,7 @@ export class Server extends AbstractServer { await import('@/controllers/mfa.controller'); } - if (!config.getEnv('endpoints.disableUi')) { + if (!this.globalConfig.endpoints.disableUi) { await import('@/controllers/cta.controller'); } @@ -167,7 +165,7 @@ export class Server extends AbstractServer { } async configure(): Promise { - if (config.getEnv('endpoints.metrics.enable')) { + if (this.globalConfig.endpoints.metrics.enable) { const { PrometheusMetricsService } = await import('@/metrics/prometheus-metrics.service'); await Container.get(PrometheusMetricsService).init(this.app); } @@ -252,7 +250,7 @@ export class Server extends AbstractServer { // ---------------------------------------- const eventBus = Container.get(MessageEventBus); await eventBus.initialize(); - Container.get(AuditEventRelay).init(); + Container.get(LogStreamingEventRelay).init(); if (this.endpointPresetCredentials !== '') { // POST endpoint to set preset credentials @@ -307,7 +305,8 @@ export class Server extends AbstractServer { this.app.use('/icons/@:scope/:packageName/*/*.(svg|png)', serveIcons); this.app.use('/icons/:packageName/*/*.(svg|png)', serveIcons); - const isTLSEnabled = this.protocol === 'https' && !!(this.sslKey && this.sslCert); + const isTLSEnabled = + this.globalConfig.protocol === 'https' && !!(this.sslKey && this.sslCert); const isPreviewMode = process.env.N8N_PREVIEW_MODE === 'true'; const securityHeadersMiddleware = helmet({ contentSecurityPolicy: false, @@ -341,7 +340,7 @@ export class Server extends AbstractServer { this.restEndpoint, this.endpointPresetCredentials, isApiEnabled() ? '' : publicApiEndpoint, - ...config.getEnv('endpoints.additionalNonUIRoutes').split(':'), + ...this.globalConfig.endpoints.additionalNonUIRoutes.split(':'), ].filter((u) => !!u); const nonUIRoutesRegex = new RegExp(`^/(${nonUIRoutes.join('|')})/?.*$`); const historyApiHandler: express.RequestHandler = (req, res, next) => { diff --git a/packages/cli/src/TypedEmitter.ts b/packages/cli/src/TypedEmitter.ts new file mode 100644 index 0000000000000..176aa9584c907 --- /dev/null +++ b/packages/cli/src/TypedEmitter.ts @@ -0,0 +1,48 @@ +import { EventEmitter } from 'node:events'; +import debounce from 'lodash/debounce'; + +type Payloads = { + [E in keyof ListenerMap]: unknown; +}; + +type Listener = (payload: Payload) => void; + +export class TypedEmitter> extends EventEmitter { + private debounceWait = 300; // milliseconds + + override on( + eventName: EventName, + listener: Listener, + ) { + return super.on(eventName, listener); + } + + override once( + eventName: EventName, + listener: Listener, + ) { + return super.once(eventName, listener); + } + + override off( + eventName: EventName, + listener: Listener, + ) { + return super.off(eventName, listener); + } + + override emit( + eventName: EventName, + payload?: ListenerMap[EventName], + ): boolean { + return super.emit(eventName, payload); + } + + protected debouncedEmit = debounce( + ( + eventName: EventName, + payload?: ListenerMap[EventName], + ) => super.emit(eventName, payload), + this.debounceWait, + ); +} diff --git a/packages/cli/src/UserManagement/email/UserManagementMailer.ts b/packages/cli/src/UserManagement/email/UserManagementMailer.ts index 531ce24cd67c4..1e092077b10de 100644 --- a/packages/cli/src/UserManagement/email/UserManagementMailer.ts +++ b/packages/cli/src/UserManagement/email/UserManagementMailer.ts @@ -16,7 +16,7 @@ import { toError } from '@/utils'; import type { InviteEmailData, PasswordResetData, SendEmailResult } from './Interfaces'; import { NodeMailer } from './NodeMailer'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; type Template = HandlebarsTemplateDelegate; type TemplateName = 'invite' | 'passwordReset' | 'workflowShared' | 'credentialsShared'; @@ -112,7 +112,7 @@ export class UserManagementMailer { this.logger.info('Sent workflow shared email successfully', { sharerId: sharer.id }); - void Container.get(InternalHooks).onUserTransactionalEmail({ + Container.get(InternalHooks).onUserTransactionalEmail({ user_id: sharer.id, message_type: 'Workflow shared', public_api: false, @@ -120,7 +120,7 @@ export class UserManagementMailer { return result; } catch (e) { - void Container.get(InternalHooks).onEmailFailed({ + Container.get(InternalHooks).onEmailFailed({ user: sharer, message_type: 'Workflow shared', public_api: false, @@ -171,7 +171,7 @@ export class UserManagementMailer { this.logger.info('Sent credentials shared email successfully', { sharerId: sharer.id }); - void Container.get(InternalHooks).onUserTransactionalEmail({ + Container.get(InternalHooks).onUserTransactionalEmail({ user_id: sharer.id, message_type: 'Credentials shared', public_api: false, @@ -179,7 +179,7 @@ export class UserManagementMailer { return result; } catch (e) { - void Container.get(InternalHooks).onEmailFailed({ + Container.get(InternalHooks).onEmailFailed({ user: sharer, message_type: 'Credentials shared', public_api: false, diff --git a/packages/cli/test/unit/UserManagementMailer.test.ts b/packages/cli/src/UserManagement/email/__tests__/UserManagementMailer.test.ts similarity index 100% rename from packages/cli/test/unit/UserManagementMailer.test.ts rename to packages/cli/src/UserManagement/email/__tests__/UserManagementMailer.test.ts diff --git a/packages/cli/src/WebhookHelpers.ts b/packages/cli/src/WebhookHelpers.ts index 95e8825e1a840..eafec28133326 100644 --- a/packages/cli/src/WebhookHelpers.ts +++ b/packages/cli/src/WebhookHelpers.ts @@ -256,6 +256,10 @@ export async function executeWebhook( // Prepare everything that is needed to run the workflow const additionalData = await WorkflowExecuteAdditionalData.getBase(); + if (executionId) { + additionalData.executionId = executionId; + } + // Get the responseMode const responseMode = workflow.expression.getSimpleParameterValue( workflowStartNode, @@ -359,12 +363,12 @@ export async function executeWebhook( additionalData, NodeExecuteFunctions, executionMode, + runExecutionData ?? null, ); - Container.get(WorkflowStatisticsService).emit( - 'nodeFetchedData', - workflow.id, - workflowStartNode, - ); + Container.get(WorkflowStatisticsService).emit('nodeFetchedData', { + workflowId: workflow.id, + node: workflowStartNode, + }); } catch (err) { // Send error response to webhook caller const errorMessage = 'Workflow Webhook Error: Workflow could not be started!'; diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index e5a17de7ec26d..127e0cc817393 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -52,7 +52,6 @@ import { Push } from '@/push'; import * as WorkflowHelpers from '@/WorkflowHelpers'; import { findSubworkflowStart, isWorkflowIdValid } from '@/utils'; import { PermissionChecker } from './UserManagement/PermissionChecker'; -import { InternalHooks } from '@/InternalHooks'; import { ExecutionRepository } from '@db/repositories/execution.repository'; import { WorkflowStatisticsService } from '@/services/workflow-statistics.service'; import { SecretsHelper } from './SecretsHelpers'; @@ -71,7 +70,7 @@ import { WorkflowRepository } from './databases/repositories/workflow.repository import { UrlService } from './services/url.service'; import { WorkflowExecutionService } from './workflows/workflowExecution.service'; import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; -import { EventService } from './eventbus/event.service'; +import { EventService } from './events/event.service'; import { GlobalConfig } from '@n8n/config'; import { SubworkflowPolicyChecker } from './subworkflows/subworkflow-policy-checker.service'; @@ -525,17 +524,16 @@ function hookFunctionsSave(): IWorkflowExecuteHooks { ); } } finally { - workflowStatisticsService.emit( - 'workflowExecutionCompleted', - this.workflowData, + workflowStatisticsService.emit('workflowExecutionCompleted', { + workflowData: this.workflowData, fullRunData, - ); + }); } }, ], nodeFetchedData: [ async (workflowId: string, node: INode) => { - workflowStatisticsService.emit('nodeFetchedData', workflowId, node); + workflowStatisticsService.emit('nodeFetchedData', { workflowId, node }); }, ], }; @@ -549,7 +547,6 @@ function hookFunctionsSave(): IWorkflowExecuteHooks { */ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks { const logger = Container.get(Logger); - const internalHooks = Container.get(InternalHooks); const workflowStatisticsService = Container.get(WorkflowStatisticsService); const eventService = Container.get(EventService); return { @@ -636,23 +633,19 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks { this.retryOf, ); } finally { - workflowStatisticsService.emit( - 'workflowExecutionCompleted', - this.workflowData, + workflowStatisticsService.emit('workflowExecutionCompleted', { + workflowData: this.workflowData, fullRunData, - ); + }); } }, async function (this: WorkflowHooks, runData: IRun): Promise { const { executionId, workflowData: workflow } = this; - void internalHooks.onWorkflowPostExecute(executionId, workflow, runData); eventService.emit('workflow-post-execute', { - workflowId: workflow.id, - workflowName: workflow.name, + workflow, executionId, - success: runData.status === 'success', - isManual: runData.mode === 'manual', + runData, }); }, async function (this: WorkflowHooks, fullRunData: IRun) { @@ -676,7 +669,7 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks { ], nodeFetchedData: [ async (workflowId: string, node: INode) => { - workflowStatisticsService.emit('nodeFetchedData', workflowId, node); + workflowStatisticsService.emit('nodeFetchedData', { workflowId, node }); }, ], }; @@ -724,7 +717,6 @@ export async function getRunData( const runData: IWorkflowExecutionDataProcess = { executionMode: mode, executionData: runExecutionData, - // @ts-ignore workflowData, }; @@ -788,7 +780,6 @@ async function executeWorkflow( parentCallbackManager?: CallbackManager; }, ): Promise | IWorkflowExecuteProcess> { - const internalHooks = Container.get(InternalHooks); const externalHooks = Container.get(ExternalHooks); await externalHooks.init(); @@ -934,14 +925,11 @@ async function executeWorkflow( await externalHooks.run('workflow.postExecute', [data, workflowData, executionId]); - void internalHooks.onWorkflowPostExecute(executionId, workflowData, data, additionalData.userId); eventService.emit('workflow-post-execute', { - workflowId: workflowData.id, - workflowName: workflowData.name, + workflow: workflowData, executionId, - success: data.status === 'success', - isManual: data.mode === 'manual', userId: additionalData.userId, + runData: data, }); // subworkflow either finished, or is in status waiting due to a wait node, both cases are considered successes here @@ -1002,23 +990,19 @@ export async function getBase( ): Promise { const urlBaseWebhook = Container.get(UrlService).getWebhookBaseUrl(); - const formWaitingBaseUrl = urlBaseWebhook + config.getEnv('endpoints.formWaiting'); - - const webhookBaseUrl = urlBaseWebhook + config.getEnv('endpoints.webhook'); - const webhookTestBaseUrl = urlBaseWebhook + config.getEnv('endpoints.webhookTest'); - const webhookWaitingBaseUrl = urlBaseWebhook + config.getEnv('endpoints.webhookWaiting'); + const globalConfig = Container.get(GlobalConfig); const variables = await WorkflowHelpers.getVariables(); return { credentialsHelper: Container.get(CredentialsHelper), executeWorkflow, - restApiUrl: urlBaseWebhook + config.getEnv('endpoints.rest'), + restApiUrl: urlBaseWebhook + globalConfig.endpoints.rest, instanceBaseUrl: urlBaseWebhook, - formWaitingBaseUrl, - webhookBaseUrl, - webhookWaitingBaseUrl, - webhookTestBaseUrl, + formWaitingBaseUrl: urlBaseWebhook + globalConfig.endpoints.formWaiting, + webhookBaseUrl: urlBaseWebhook + globalConfig.endpoints.webhook, + webhookWaitingBaseUrl: urlBaseWebhook + globalConfig.endpoints.webhookWaiting, + webhookTestBaseUrl: urlBaseWebhook + globalConfig.endpoints.webhookTest, currentNodeParameters, executionTimeoutTimestamp, userId, diff --git a/packages/cli/src/WorkflowRunner.ts b/packages/cli/src/WorkflowRunner.ts index f8faf55fbe49d..2f7976a073b30 100644 --- a/packages/cli/src/WorkflowRunner.ts +++ b/packages/cli/src/WorkflowRunner.ts @@ -34,10 +34,9 @@ import * as WorkflowHelpers from '@/WorkflowHelpers'; import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData'; import { generateFailedExecutionFromError } from '@/WorkflowHelpers'; import { PermissionChecker } from '@/UserManagement/PermissionChecker'; -import { InternalHooks } from '@/InternalHooks'; import { Logger } from '@/Logger'; import { WorkflowStaticDataService } from '@/workflows/workflowStaticData.service'; -import { EventService } from './eventbus/event.service'; +import { EventService } from './events/event.service'; @Service() export class WorkflowRunner { @@ -160,19 +159,11 @@ export class WorkflowRunner { const postExecutePromise = this.activeExecutions.getPostExecutePromise(executionId); postExecutePromise .then(async (executionData) => { - void Container.get(InternalHooks).onWorkflowPostExecute( - executionId, - data.workflowData, - executionData, - data.userId, - ); this.eventService.emit('workflow-post-execute', { - workflowId: data.workflowData.id, - workflowName: data.workflowData.name, + workflow: data.workflowData, executionId, - success: executionData?.status === 'success', - isManual: data.executionMode === 'manual', userId: data.userId, + runData: executionData, }); if (this.externalHooks.exists('workflow.postExecute')) { try { diff --git a/packages/cli/test/unit/ActiveExecutions.test.ts b/packages/cli/src/__tests__/ActiveExecutions.test.ts similarity index 98% rename from packages/cli/test/unit/ActiveExecutions.test.ts rename to packages/cli/src/__tests__/ActiveExecutions.test.ts index b2454de87c5e1..0cc70a10a4e61 100644 --- a/packages/cli/test/unit/ActiveExecutions.test.ts +++ b/packages/cli/src/__tests__/ActiveExecutions.test.ts @@ -20,7 +20,10 @@ const executionRepository = mock({ createNewExecution, }); -const concurrencyControl = mockInstance(ConcurrencyControlService, { isEnabled: false }); +const concurrencyControl = mockInstance(ConcurrencyControlService, { + // @ts-expect-error Private property + isEnabled: false, +}); describe('ActiveExecutions', () => { let activeExecutions: ActiveExecutions; diff --git a/packages/cli/test/unit/CredentialTypes.test.ts b/packages/cli/src/__tests__/CredentialTypes.test.ts similarity index 95% rename from packages/cli/test/unit/CredentialTypes.test.ts rename to packages/cli/src/__tests__/CredentialTypes.test.ts index 0ee99c9f8ae6e..2a8e6a939a045 100644 --- a/packages/cli/test/unit/CredentialTypes.test.ts +++ b/packages/cli/src/__tests__/CredentialTypes.test.ts @@ -1,7 +1,7 @@ import { CredentialTypes } from '@/CredentialTypes'; import { Container } from 'typedi'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; -import { mockInstance } from '../shared/mocking'; +import { mockInstance } from '@test/mocking'; describe('CredentialTypes', () => { const mockNodesAndCredentials = mockInstance(LoadNodesAndCredentials, { diff --git a/packages/cli/test/unit/CredentialsHelper.test.ts b/packages/cli/src/__tests__/CredentialsHelper.test.ts similarity index 99% rename from packages/cli/test/unit/CredentialsHelper.test.ts rename to packages/cli/src/__tests__/CredentialsHelper.test.ts index 19be100b02b84..88cd19ad3dc5d 100644 --- a/packages/cli/test/unit/CredentialsHelper.test.ts +++ b/packages/cli/src/__tests__/CredentialsHelper.test.ts @@ -14,7 +14,7 @@ import { NodeTypes } from '@/NodeTypes'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; import { CredentialsRepository } from '@db/repositories/credentials.repository'; import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; -import { mockInstance } from '../shared/mocking'; +import { mockInstance } from '@test/mocking'; describe('CredentialsHelper', () => { mockInstance(CredentialsRepository); diff --git a/packages/cli/test/unit/License.test.ts b/packages/cli/src/__tests__/License.test.ts similarity index 99% rename from packages/cli/test/unit/License.test.ts rename to packages/cli/src/__tests__/License.test.ts index 6d7311656c961..31effecc0eba4 100644 --- a/packages/cli/test/unit/License.test.ts +++ b/packages/cli/src/__tests__/License.test.ts @@ -5,7 +5,7 @@ import config from '@/config'; import { License } from '@/License'; import { Logger } from '@/Logger'; import { N8N_VERSION } from '@/constants'; -import { mockInstance } from '../shared/mocking'; +import { mockInstance } from '@test/mocking'; import { OrchestrationService } from '@/services/orchestration.service'; jest.mock('@n8n_io/license-sdk'); diff --git a/packages/cli/test/unit/TestWebhooks.test.ts b/packages/cli/src/__tests__/TestWebhooks.test.ts similarity index 100% rename from packages/cli/test/unit/TestWebhooks.test.ts rename to packages/cli/src/__tests__/TestWebhooks.test.ts diff --git a/packages/cli/test/unit/WaitTracker.test.ts b/packages/cli/src/__tests__/WaitTracker.test.ts similarity index 99% rename from packages/cli/test/unit/WaitTracker.test.ts rename to packages/cli/src/__tests__/WaitTracker.test.ts index 0f8464e18d14d..fb51d2e25bd30 100644 --- a/packages/cli/test/unit/WaitTracker.test.ts +++ b/packages/cli/src/__tests__/WaitTracker.test.ts @@ -10,7 +10,7 @@ jest.useFakeTimers(); describe('WaitTracker', () => { const executionRepository = mock(); const multiMainSetup = mock(); - const orchestrationService = new OrchestrationService(mock(), mock(), multiMainSetup); + const orchestrationService = new OrchestrationService(mock(), mock(), mock(), multiMainSetup); const execution = mock({ id: '123', diff --git a/packages/cli/test/unit/WebhookHelpers.test.ts b/packages/cli/src/__tests__/WebhookHelpers.test.ts similarity index 100% rename from packages/cli/test/unit/WebhookHelpers.test.ts rename to packages/cli/src/__tests__/WebhookHelpers.test.ts diff --git a/packages/cli/test/unit/WorkflowExecuteAdditionalData.test.ts b/packages/cli/src/__tests__/WorkflowExecuteAdditionalData.test.ts similarity index 96% rename from packages/cli/test/unit/WorkflowExecuteAdditionalData.test.ts rename to packages/cli/src/__tests__/WorkflowExecuteAdditionalData.test.ts index 2984220637aae..eca60e56c5216 100644 --- a/packages/cli/test/unit/WorkflowExecuteAdditionalData.test.ts +++ b/packages/cli/src/__tests__/WorkflowExecuteAdditionalData.test.ts @@ -1,5 +1,5 @@ import { VariablesService } from '@/environments/variables/variables.service.ee'; -import { mockInstance } from '../shared/mocking'; +import { mockInstance } from '@test/mocking'; import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; import { getBase } from '@/WorkflowExecuteAdditionalData'; import Container from 'typedi'; diff --git a/packages/cli/test/unit/WorkflowHelpers.test.ts b/packages/cli/src/__tests__/WorkflowHelpers.test.ts similarity index 100% rename from packages/cli/test/unit/WorkflowHelpers.test.ts rename to packages/cli/src/__tests__/WorkflowHelpers.test.ts diff --git a/packages/cli/test/unit/WorkflowRunner.test.ts b/packages/cli/src/__tests__/WorkflowRunner.test.ts similarity index 86% rename from packages/cli/test/unit/WorkflowRunner.test.ts rename to packages/cli/src/__tests__/WorkflowRunner.test.ts index c972d6bb73933..668150092f60a 100644 --- a/packages/cli/test/unit/WorkflowRunner.test.ts +++ b/packages/cli/src/__tests__/WorkflowRunner.test.ts @@ -4,11 +4,11 @@ import type { User } from '@db/entities/User'; import { WorkflowRunner } from '@/WorkflowRunner'; import config from '@/config'; -import * as testDb from '../integration/shared/testDb'; -import { setupTestServer } from '../integration/shared/utils'; -import { createUser } from '../integration/shared/db/users'; -import { createWorkflow } from '../integration/shared/db/workflows'; -import { createExecution } from '../integration/shared/db/executions'; +import * as testDb from '@test-integration/testDb'; +import { setupTestServer } from '@test-integration/utils'; +import { createUser } from '@test-integration/db/users'; +import { createWorkflow } from '@test-integration/db/workflows'; +import { createExecution } from '@test-integration/db/executions'; import { mockInstance } from '@test/mocking'; import { Telemetry } from '@/telemetry'; diff --git a/packages/cli/test/unit/auth/auth.service.test.ts b/packages/cli/src/auth/__tests__/auth.service.test.ts similarity index 100% rename from packages/cli/test/unit/auth/auth.service.test.ts rename to packages/cli/src/auth/__tests__/auth.service.test.ts diff --git a/packages/cli/src/auth/auth.service.ts b/packages/cli/src/auth/auth.service.ts index ccf562e27e050..e2487e6f6f9a6 100644 --- a/packages/cli/src/auth/auth.service.ts +++ b/packages/cli/src/auth/auth.service.ts @@ -1,4 +1,4 @@ -import { Service } from 'typedi'; +import Container, { Service } from 'typedi'; import type { NextFunction, Response } from 'express'; import { createHash } from 'crypto'; import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken'; @@ -14,6 +14,7 @@ import { Logger } from '@/Logger'; import type { AuthenticatedRequest } from '@/requests'; import { JwtService } from '@/services/jwt.service'; import { UrlService } from '@/services/url.service'; +import { GlobalConfig } from '@n8n/config'; interface AuthJwtPayload { /** User Id */ @@ -33,7 +34,7 @@ interface PasswordResetToken { hash: string; } -const restEndpoint = config.get('endpoints.rest'); +const restEndpoint = Container.get(GlobalConfig).endpoints.rest; // The browser-id check needs to be skipped on these endpoints const skipBrowserIdCheckEndpoints = [ // we need to exclude push endpoint because we can't send custom header on websocket requests @@ -42,10 +43,6 @@ const skipBrowserIdCheckEndpoints = [ // We need to exclude binary-data downloading endpoint because we can't send custom headers on `` tags `/${restEndpoint}/binary-data/`, - - // oAuth callback urls aren't called by the frontend. therefore we can't send custom header on these requests - `/${restEndpoint}/oauth1-credential/callback`, - `/${restEndpoint}/oauth2-credential/callback`, ]; @Service() diff --git a/packages/cli/src/auth/methods/email.ts b/packages/cli/src/auth/methods/email.ts index a88d00186b06a..f954991974570 100644 --- a/packages/cli/src/auth/methods/email.ts +++ b/packages/cli/src/auth/methods/email.ts @@ -4,7 +4,7 @@ import { Container } from 'typedi'; import { isLdapLoginEnabled } from '@/Ldap/helpers.ee'; import { UserRepository } from '@db/repositories/user.repository'; import { AuthError } from '@/errors/response-errors/auth.error'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; export const handleEmailLogin = async ( email: string, diff --git a/packages/cli/src/auth/methods/ldap.ts b/packages/cli/src/auth/methods/ldap.ts index 09e8f38c87a03..66f6f6dcd9ffe 100644 --- a/packages/cli/src/auth/methods/ldap.ts +++ b/packages/cli/src/auth/methods/ldap.ts @@ -1,6 +1,5 @@ import { Container } from 'typedi'; -import { InternalHooks } from '@/InternalHooks'; import { LdapService } from '@/Ldap/ldap.service.ee'; import { createLdapUserOnLocalDb, @@ -12,7 +11,7 @@ import { updateLdapUserOnLocalDb, } from '@/Ldap/helpers.ee'; import type { User } from '@db/entities/User'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; export const handleLdapLogin = async ( loginId: string, @@ -51,11 +50,11 @@ export const handleLdapLogin = async ( await updateLdapUserOnLocalDb(identity, ldapAttributesValues); } else { const user = await createLdapUserOnLocalDb(ldapAttributesValues, ldapId); - void Container.get(InternalHooks).onUserSignup(user, { - user_type: 'ldap', - was_disabled_ldap_user: false, + Container.get(EventService).emit('user-signed-up', { + user, + userType: 'ldap', + wasDisabledLdapUser: false, }); - Container.get(EventService).emit('user-signed-up', { user }); return user; } } else { diff --git a/packages/cli/src/commands/BaseCommand.ts b/packages/cli/src/commands/BaseCommand.ts index b00d314b91212..62ff002e43298 100644 --- a/packages/cli/src/commands/BaseCommand.ts +++ b/packages/cli/src/commands/BaseCommand.ts @@ -23,7 +23,7 @@ import { initExpressionEvaluator } from '@/ExpressionEvaluator'; import { generateHostInstanceId } from '@db/utils/generators'; import { WorkflowHistoryManager } from '@/workflows/workflowHistory/workflowHistoryManager.ee'; import { ShutdownService } from '@/shutdown/Shutdown.service'; -import { TelemetryEventRelay } from '@/telemetry/telemetry-event-relay.service'; +import { TelemetryEventRelay } from '@/events/telemetry-event-relay'; export abstract class BaseCommand extends Command { protected logger = Container.get(Logger); @@ -44,13 +44,16 @@ export abstract class BaseCommand extends Command { protected license: License; - private globalConfig = Container.get(GlobalConfig); + protected readonly globalConfig = Container.get(GlobalConfig); /** * How long to wait for graceful shutdown before force killing the process. */ protected gracefulShutdownTimeoutInS = config.getEnv('generic.gracefulShutdownTimeout'); + /** Whether to init community packages (if enabled) */ + protected needsCommunityPackages = false; + async init(): Promise { await initErrorHandling(); initExpressionEvaluator(); @@ -111,6 +114,12 @@ export abstract class BaseCommand extends Command { ); } + const { communityPackages } = this.globalConfig.nodes; + if (communityPackages.enabled && this.needsCommunityPackages) { + const { CommunityPackagesService } = await import('@/services/communityPackages.service'); + await Container.get(CommunityPackagesService).checkForMissingPackages(); + } + await Container.get(PostHogClient).init(); await Container.get(InternalHooks).init(); await Container.get(TelemetryEventRelay).init(); diff --git a/packages/cli/test/unit/commands/db/revert.test.ts b/packages/cli/src/commands/db/__tests__/revert.test.ts similarity index 98% rename from packages/cli/test/unit/commands/db/revert.test.ts rename to packages/cli/src/commands/db/__tests__/revert.test.ts index cea3f89253383..13c554a786ad3 100644 --- a/packages/cli/test/unit/commands/db/revert.test.ts +++ b/packages/cli/src/commands/db/__tests__/revert.test.ts @@ -1,5 +1,5 @@ import { main } from '@/commands/db/revert'; -import { mockInstance } from '../../../shared/mocking'; +import { mockInstance } from '@test/mocking'; import { Logger } from '@/Logger'; import type { IrreversibleMigration, ReversibleMigration } from '@/databases/types'; import type { Migration, MigrationExecutor } from '@n8n/typeorm'; diff --git a/packages/cli/src/commands/execute.ts b/packages/cli/src/commands/execute.ts index a375d19c31034..cdf949e87ceda 100644 --- a/packages/cli/src/commands/execute.ts +++ b/packages/cli/src/commands/execute.ts @@ -27,6 +27,8 @@ export class Execute extends BaseCommand { }), }; + override needsCommunityPackages = true; + async init() { await super.init(); await this.initBinaryDataService(); diff --git a/packages/cli/src/commands/executeBatch.ts b/packages/cli/src/commands/executeBatch.ts index d8fdbeb8a201c..227dd962efb1b 100644 --- a/packages/cli/src/commands/executeBatch.ts +++ b/packages/cli/src/commands/executeBatch.ts @@ -108,6 +108,8 @@ export class ExecuteBatch extends BaseCommand { }), }; + override needsCommunityPackages = true; + /** * Gracefully handles exit. * @param {boolean} skipExit Whether to skip exit or number according to received signal diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 2e88c62e06194..98e8e5b98141c 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -8,7 +8,6 @@ import { createReadStream, createWriteStream, existsSync } from 'fs'; import { pipeline } from 'stream/promises'; import replaceStream from 'replacestream'; import glob from 'fast-glob'; -import { GlobalConfig } from '@n8n/config'; import { jsonParse, randomString } from 'n8n-workflow'; import config from '@/config'; @@ -33,7 +32,7 @@ import { ExecutionService } from '@/executions/execution.service'; import { OwnershipService } from '@/services/ownership.service'; import { WorkflowRunner } from '@/WorkflowRunner'; import { ExecutionRecoveryService } from '@/executions/execution-recovery.service'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires const open = require('open'); @@ -68,6 +67,8 @@ export class Start extends BaseCommand { protected server = Container.get(Server); + override needsCommunityPackages = true; + constructor(argv: string[], cmdConfig: Config) { super(argv, cmdConfig); this.setInstanceType('main'); @@ -124,8 +125,7 @@ export class Start extends BaseCommand { private async generateStaticAssets() { // Read the index file and replace the path placeholder - const n8nPath = Container.get(GlobalConfig).path; - const restEndpoint = config.getEnv('endpoints.rest'); + const n8nPath = this.globalConfig.path; const hooksUrls = config.getEnv('externalFrontendHooksUrls'); let scriptsString = ''; @@ -151,7 +151,9 @@ export class Start extends BaseCommand { ]; if (filePath.endsWith('index.html')) { streams.push( - replaceStream('{{REST_ENDPOINT}}', restEndpoint, { ignoreCase: false }), + replaceStream('{{REST_ENDPOINT}}', this.globalConfig.endpoints.rest, { + ignoreCase: false, + }), replaceStream(closingTitleTag, closingTitleTag + scriptsString, { ignoreCase: false, }), @@ -176,6 +178,22 @@ export class Start extends BaseCommand { this.logger.debug(`Queue mode id: ${this.queueModeId}`); } + const { flags } = await this.parse(Start); + const { communityPackages } = this.globalConfig.nodes; + // cli flag overrides the config env variable + if (flags.reinstallMissingPackages) { + if (communityPackages.enabled) { + this.logger.warn( + '`--reinstallMissingPackages` is deprecated: Please use the env variable `N8N_REINSTALL_MISSING_PACKAGES` instead', + ); + communityPackages.reinstallMissing = true; + } else { + this.logger.warn( + '`--reinstallMissingPackages` was passed, but community packages are disabled', + ); + } + } + await super.init(); this.activeWorkflowManager = Container.get(ActiveWorkflowManager); @@ -184,10 +202,7 @@ export class Start extends BaseCommand { await this.initOrchestration(); this.logger.debug('Orchestration init complete'); - if ( - !config.getEnv('license.autoRenewEnabled') && - config.getEnv('multiMainSetup.instanceType') === 'leader' - ) { + if (!config.getEnv('license.autoRenewEnabled') && this.instanceSettings.isLeader) { this.logger.warn( 'Automatic license renewal is disabled. The license will not renew automatically, and access to licensed features may be lost!', ); @@ -204,14 +219,14 @@ export class Start extends BaseCommand { this.initWorkflowHistory(); this.logger.debug('Workflow history init complete'); - if (!config.getEnv('endpoints.disableUi')) { + if (!this.globalConfig.endpoints.disableUi) { await this.generateStaticAssets(); } } async initOrchestration() { if (config.getEnv('executions.mode') === 'regular') { - config.set('multiMainSetup.instanceType', 'leader'); + this.instanceSettings.markAsLeader(); return; } @@ -252,18 +267,9 @@ export class Start extends BaseCommand { config.set(setting.key, jsonParse(setting.value, { fallbackValue: setting.value })); }); - const globalConfig = Container.get(GlobalConfig); - - if (globalConfig.nodes.communityPackages.enabled) { - const { CommunityPackagesService } = await import('@/services/communityPackages.service'); - await Container.get(CommunityPackagesService).setMissingPackages({ - reinstallMissingPackages: flags.reinstallMissingPackages, - }); - } - - const { type: dbType } = globalConfig.database; + const { type: dbType } = this.globalConfig.database; if (dbType === 'sqlite') { - const shouldRunVacuum = globalConfig.database.sqlite.executeVacuumOnStartup; + const shouldRunVacuum = this.globalConfig.database.sqlite.executeVacuumOnStartup; if (shouldRunVacuum) { await Container.get(ExecutionRepository).query('VACUUM;'); } @@ -283,7 +289,7 @@ export class Start extends BaseCommand { } const { default: localtunnel } = await import('@n8n/localtunnel'); - const { port } = Container.get(GlobalConfig); + const { port } = this.globalConfig; const webhookTunnel = await localtunnel(port, { host: 'https://hooks.n8n.cloud', diff --git a/packages/cli/src/commands/webhook.ts b/packages/cli/src/commands/webhook.ts index 5b72c1eb86a9b..e76ac358170c1 100644 --- a/packages/cli/src/commands/webhook.ts +++ b/packages/cli/src/commands/webhook.ts @@ -22,6 +22,8 @@ export class Webhook extends BaseCommand { protected server = Container.get(WebhookServer); + override needsCommunityPackages = true; + constructor(argv: string[], cmdConfig: Config) { super(argv, cmdConfig); this.setInstanceType('webhook'); diff --git a/packages/cli/src/commands/worker.ts b/packages/cli/src/commands/worker.ts index 4f582904cddbb..151ecfdf90d12 100644 --- a/packages/cli/src/commands/worker.ts +++ b/packages/cli/src/commands/worker.ts @@ -3,7 +3,6 @@ import { Flags, type Config } from '@oclif/core'; import express from 'express'; import http from 'http'; import type PCancelable from 'p-cancelable'; -import { GlobalConfig } from '@n8n/config'; import { WorkflowExecute } from 'n8n-core'; import type { ExecutionStatus, IExecuteResponsePromiseData, INodeTypes, IRun } from 'n8n-workflow'; import { Workflow, sleep, ApplicationError } from 'n8n-workflow'; @@ -30,7 +29,7 @@ import type { WorkerJobStatusSummary } from '@/services/orchestration/worker/typ import { ServiceUnavailableError } from '@/errors/response-errors/service-unavailable.error'; import { BaseCommand } from './BaseCommand'; import { MaxStalledCountError } from '@/errors/max-stalled-count.error'; -import { AuditEventRelay } from '@/eventbus/audit-event-relay.service'; +import { LogStreamingEventRelay } from '@/events/log-streaming-event-relay'; export class Worker extends BaseCommand { static description = '\nStarts a n8n worker'; @@ -57,6 +56,8 @@ export class Worker extends BaseCommand { redisSubscriber: RedisServicePubSubSubscriber; + override needsCommunityPackages = true; + /** * Stop n8n in a graceful way. * Make for example sure that all the webhooks from third party services @@ -286,7 +287,7 @@ export class Worker extends BaseCommand { await Container.get(MessageEventBus).initialize({ workerId: this.queueModeId, }); - Container.get(AuditEventRelay).init(); + Container.get(LogStreamingEventRelay).init(); } /** @@ -429,8 +430,7 @@ export class Worker extends BaseCommand { let presetCredentialsLoaded = false; - const globalConfig = Container.get(GlobalConfig); - const endpointPresetCredentials = globalConfig.credentials.overwrite.endpoint; + const endpointPresetCredentials = this.globalConfig.credentials.overwrite.endpoint; if (endpointPresetCredentials !== '') { // POST endpoint to set preset credentials app.post( diff --git a/packages/cli/src/concurrency/__tests__/concurrency-control.service.test.ts b/packages/cli/src/concurrency/__tests__/concurrency-control.service.test.ts index c694ab29406a3..08f58ac600324 100644 --- a/packages/cli/src/concurrency/__tests__/concurrency-control.service.test.ts +++ b/packages/cli/src/concurrency/__tests__/concurrency-control.service.test.ts @@ -12,7 +12,7 @@ import type { WorkflowExecuteMode as ExecutionMode } from 'n8n-workflow'; import type { ExecutionRepository } from '@/databases/repositories/execution.repository'; import type { IExecutingWorkflowData } from '@/Interfaces'; import type { Telemetry } from '@/telemetry'; -import type { EventService } from '@/eventbus/event.service'; +import type { EventService } from '@/events/event.service'; describe('ConcurrencyControlService', () => { const logger = mock(); diff --git a/packages/cli/src/concurrency/concurrency-control.service.ts b/packages/cli/src/concurrency/concurrency-control.service.ts index cf249c51722eb..6e62e9b78df54 100644 --- a/packages/cli/src/concurrency/concurrency-control.service.ts +++ b/packages/cli/src/concurrency/concurrency-control.service.ts @@ -8,7 +8,7 @@ import { ExecutionRepository } from '@/databases/repositories/execution.reposito import type { WorkflowExecuteMode as ExecutionMode } from 'n8n-workflow'; import type { IExecutingWorkflowData } from '@/Interfaces'; import { Telemetry } from '@/telemetry'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; export const CLOUD_TEMP_PRODUCTION_LIMIT = 999; export const CLOUD_TEMP_REPORTABLE_THRESHOLDS = [5, 10, 20, 50, 100, 200]; @@ -53,20 +53,20 @@ export class ConcurrencyControlService { this.isEnabled = true; - this.productionQueue.on('concurrency-check', ({ capacity }: { capacity: number }) => { + this.productionQueue.on('concurrency-check', ({ capacity }) => { if (this.shouldReport(capacity)) { - void this.telemetry.track('User hit concurrency limit', { + this.telemetry.track('User hit concurrency limit', { threshold: CLOUD_TEMP_PRODUCTION_LIMIT - capacity, }); } }); - this.productionQueue.on('execution-throttled', ({ executionId }: { executionId: string }) => { + this.productionQueue.on('execution-throttled', ({ executionId }) => { this.log('Execution throttled', { executionId }); this.eventService.emit('execution-throttled', { executionId }); }); - this.productionQueue.on('execution-released', async (executionId: string) => { + this.productionQueue.on('execution-released', async (executionId) => { this.log('Execution released', { executionId }); await this.executionRepository.resetStartedAt(executionId); }); diff --git a/packages/cli/src/concurrency/concurrency-queue.ts b/packages/cli/src/concurrency/concurrency-queue.ts index 90c62d2efc21e..1b578b5a8ef1d 100644 --- a/packages/cli/src/concurrency/concurrency-queue.ts +++ b/packages/cli/src/concurrency/concurrency-queue.ts @@ -1,9 +1,14 @@ import { Service } from 'typedi'; -import { EventEmitter } from 'node:events'; -import debounce from 'lodash/debounce'; +import { TypedEmitter } from '@/TypedEmitter'; + +type ConcurrencyEvents = { + 'execution-throttled': { executionId: string }; + 'execution-released': string; + 'concurrency-check': { capacity: number }; +}; @Service() -export class ConcurrencyQueue extends EventEmitter { +export class ConcurrencyQueue extends TypedEmitter { private readonly queue: Array<{ executionId: string; resolve: () => void; @@ -63,9 +68,4 @@ export class ConcurrencyQueue extends EventEmitter { resolve(); } - - private debouncedEmit = debounce( - (event: string, payload: object) => this.emit(event, payload), - 300, - ); } diff --git a/packages/cli/test/unit/config/index.test.ts b/packages/cli/src/config/__tests__/index.test.ts similarity index 100% rename from packages/cli/test/unit/config/index.test.ts rename to packages/cli/src/config/__tests__/index.test.ts diff --git a/packages/cli/src/config/index.ts b/packages/cli/src/config/index.ts index f743e7961dba5..dedc803839292 100644 --- a/packages/cli/src/config/index.ts +++ b/packages/cli/src/config/index.ts @@ -96,9 +96,10 @@ config.validate({ }); const userManagement = config.get('userManagement'); if (userManagement.jwtRefreshTimeoutHours >= userManagement.jwtSessionDurationHours) { - console.warn( - 'N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS needs to smaller than N8N_USER_MANAGEMENT_JWT_DURATION_HOURS. Setting N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS to 0 for now.', - ); + if (!inTest) + console.warn( + 'N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS needs to smaller than N8N_USER_MANAGEMENT_JWT_DURATION_HOURS. Setting N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS to 0 for now.', + ); config.set('userManagement.jwtRefreshTimeoutHours', 0); } diff --git a/packages/cli/src/config/schema.ts b/packages/cli/src/config/schema.ts index 1e6d00aed7699..c424066c8554f 100644 --- a/packages/cli/src/config/schema.ts +++ b/packages/cli/src/config/schema.ts @@ -4,6 +4,7 @@ import { Container } from 'typedi'; import { InstanceSettings } from 'n8n-core'; import { LOG_LEVELS } from 'n8n-workflow'; import { ensureStringArray } from './utils'; +import { GlobalConfig } from '@n8n/config'; convict.addFormat({ name: 'comma-separated-list', @@ -355,149 +356,6 @@ export const schema = { }, }, - endpoints: { - payloadSizeMax: { - format: Number, - default: 16, - env: 'N8N_PAYLOAD_SIZE_MAX', - doc: 'Maximum payload size in MB.', - }, - metrics: { - enable: { - format: Boolean, - default: false, - env: 'N8N_METRICS', - doc: 'Enable /metrics endpoint. Default: false', - }, - prefix: { - format: String, - default: 'n8n_', - env: 'N8N_METRICS_PREFIX', - doc: 'An optional prefix for metric names. Default: n8n_', - }, - includeDefaultMetrics: { - format: Boolean, - default: true, - env: 'N8N_METRICS_INCLUDE_DEFAULT_METRICS', - doc: 'Whether to expose default system and node.js metrics. Default: true', - }, - includeWorkflowIdLabel: { - format: Boolean, - default: false, - env: 'N8N_METRICS_INCLUDE_WORKFLOW_ID_LABEL', - doc: 'Whether to include a label for the workflow ID on workflow metrics. Default: false', - }, - includeNodeTypeLabel: { - format: Boolean, - default: false, - env: 'N8N_METRICS_INCLUDE_NODE_TYPE_LABEL', - doc: 'Whether to include a label for the node type on node metrics. Default: false', - }, - includeCredentialTypeLabel: { - format: Boolean, - default: false, - env: 'N8N_METRICS_INCLUDE_CREDENTIAL_TYPE_LABEL', - doc: 'Whether to include a label for the credential type on credential metrics. Default: false', - }, - includeApiEndpoints: { - format: Boolean, - default: false, - env: 'N8N_METRICS_INCLUDE_API_ENDPOINTS', - doc: 'Whether to expose metrics for API endpoints. Default: false', - }, - includeApiPathLabel: { - format: Boolean, - default: false, - env: 'N8N_METRICS_INCLUDE_API_PATH_LABEL', - doc: 'Whether to include a label for the path of API invocations. Default: false', - }, - includeApiMethodLabel: { - format: Boolean, - default: false, - env: 'N8N_METRICS_INCLUDE_API_METHOD_LABEL', - doc: 'Whether to include a label for the HTTP method (GET, POST, ...) of API invocations. Default: false', - }, - includeApiStatusCodeLabel: { - format: Boolean, - default: false, - env: 'N8N_METRICS_INCLUDE_API_STATUS_CODE_LABEL', - doc: 'Whether to include a label for the HTTP status code (200, 404, ...) of API invocations. Default: false', - }, - includeCacheMetrics: { - format: Boolean, - default: false, - env: 'N8N_METRICS_INCLUDE_CACHE_METRICS', - doc: 'Whether to include metrics for cache hits and misses. Default: false', - }, - includeMessageEventBusMetrics: { - format: Boolean, - default: true, - env: 'N8N_METRICS_INCLUDE_MESSAGE_EVENT_BUS_METRICS', - doc: 'Whether to include metrics for events. Default: false', - }, - }, - rest: { - format: String, - default: 'rest', - env: 'N8N_ENDPOINT_REST', - doc: 'Path for rest endpoint', - }, - form: { - format: String, - default: 'form', - env: 'N8N_ENDPOINT_FORM', - doc: 'Path for form endpoint', - }, - formTest: { - format: String, - default: 'form-test', - env: 'N8N_ENDPOINT_FORM_TEST', - doc: 'Path for test form endpoint', - }, - formWaiting: { - format: String, - default: 'form-waiting', - env: 'N8N_ENDPOINT_FORM_WAIT', - doc: 'Path for waiting form endpoint', - }, - webhook: { - format: String, - default: 'webhook', - env: 'N8N_ENDPOINT_WEBHOOK', - doc: 'Path for webhook endpoint', - }, - webhookWaiting: { - format: String, - default: 'webhook-waiting', - env: 'N8N_ENDPOINT_WEBHOOK_WAIT', - doc: 'Path for waiting-webhook endpoint', - }, - webhookTest: { - format: String, - default: 'webhook-test', - env: 'N8N_ENDPOINT_WEBHOOK_TEST', - doc: 'Path for test-webhook endpoint', - }, - disableUi: { - format: Boolean, - default: false, - env: 'N8N_DISABLE_UI', - doc: 'Disable N8N UI (Frontend).', - }, - disableProductionWebhooksOnMainProcess: { - format: Boolean, - default: false, - env: 'N8N_DISABLE_PRODUCTION_MAIN_PROCESS', - doc: 'Disable production webhooks from main process. This helps ensures no http traffic load to main process when using webhook-specific processes.', - }, - additionalNonUIRoutes: { - doc: 'Additional endpoints to not open the UI on. Multiple endpoints can be separated by colon (":")', - format: String, - default: '', - env: 'N8N_ADDITIONAL_NON_UI_ROUTES', - }, - }, - workflowTagsDisabled: { format: Boolean, default: false, @@ -524,12 +382,17 @@ export const schema = { default: 0, env: 'N8N_USER_MANAGEMENT_JWT_REFRESH_TIMEOUT_HOURS', }, + + /** + * @important Do not remove until after cloud hooks are updated to stop using convict config. + */ isInstanceOwnerSetUp: { // n8n loads this setting from DB on startup doc: "Whether the instance owner's account has been set up", format: Boolean, default: false, }, + authenticationMethod: { doc: 'How to authenticate users (e.g. "email", "ldap", "saml")', format: ['email', 'ldap', 'saml'] as const, @@ -797,43 +660,19 @@ export const schema = { }, }, - cache: { - backend: { - doc: 'Backend to use for caching', - format: ['memory', 'redis', 'auto'] as const, - default: 'auto', - env: 'N8N_CACHE_BACKEND', - }, - memory: { - maxSize: { - doc: 'Maximum size of memory cache in bytes', - format: Number, - default: 3 * 1024 * 1024, // 3 MB - env: 'N8N_CACHE_MEMORY_MAX_SIZE', - }, - ttl: { - doc: 'Time to live for cached items in memory (in ms)', - format: Number, - default: 3600 * 1000, // 1 hour - env: 'N8N_CACHE_MEMORY_TTL', - }, - }, - redis: { - prefix: { - doc: 'Prefix for all cache keys', - format: String, - default: 'cache', - env: 'N8N_CACHE_REDIS_KEY_PREFIX', - }, - ttl: { - doc: 'Time to live for cached items in redis (in ms), 0 for no TTL', - format: Number, - default: 3600 * 1000, // 1 hour - env: 'N8N_CACHE_REDIS_TTL', - }, + /** + * @important Do not remove until after cloud hooks are updated to stop using convict config. + */ + endpoints: { + rest: { + format: String, + default: Container.get(GlobalConfig).endpoints.rest, }, }, + /** + * @important Do not remove until after cloud hooks are updated to stop using convict config. + */ ai: { enabled: { doc: 'Whether AI features are enabled', @@ -884,11 +723,6 @@ export const schema = { }, multiMainSetup: { - instanceType: { - doc: 'Type of instance in multi-main setup', - format: ['unset', 'leader', 'follower'] as const, - default: 'unset', // only until first leader key check - }, enabled: { doc: 'Whether to enable multi-main setup for queue mode (license required)', format: Boolean, diff --git a/packages/cli/test/unit/controllers/curl.controller.test.ts b/packages/cli/src/controllers/__tests__/curl.controller.test.ts similarity index 100% rename from packages/cli/test/unit/controllers/curl.controller.test.ts rename to packages/cli/src/controllers/__tests__/curl.controller.test.ts diff --git a/packages/cli/test/unit/controllers/dynamic-node-parameters.controller.test.ts b/packages/cli/src/controllers/__tests__/dynamic-node-parameters.controller.test.ts similarity index 100% rename from packages/cli/test/unit/controllers/dynamic-node-parameters.controller.test.ts rename to packages/cli/src/controllers/__tests__/dynamic-node-parameters.controller.test.ts diff --git a/packages/cli/test/unit/controllers/me.controller.test.ts b/packages/cli/src/controllers/__tests__/me.controller.test.ts similarity index 96% rename from packages/cli/test/unit/controllers/me.controller.test.ts rename to packages/cli/src/controllers/__tests__/me.controller.test.ts index 2416ecafbddb4..6e187f9cd5e2e 100644 --- a/packages/cli/test/unit/controllers/me.controller.test.ts +++ b/packages/cli/src/controllers/__tests__/me.controller.test.ts @@ -13,14 +13,16 @@ import { InternalHooks } from '@/InternalHooks'; import { License } from '@/License'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { UserRepository } from '@/databases/repositories/user.repository'; -import { badPasswords } from '../shared/testData'; -import { mockInstance } from '../../shared/mocking'; +import { EventService } from '@/events/event.service'; +import { badPasswords } from '@test/testData'; +import { mockInstance } from '@test/mocking'; const browserId = 'test-browser-id'; describe('MeController', () => { const externalHooks = mockInstance(ExternalHooks); - const internalHooks = mockInstance(InternalHooks); + mockInstance(InternalHooks); + const eventService = mockInstance(EventService); const userService = mockInstance(UserService); const userRepository = mockInstance(UserRepository); mockInstance(License).isWithinUsersLimit.mockReturnValue(true); @@ -202,9 +204,9 @@ describe('MeController', () => { req.user.password, ]); - expect(internalHooks.onUserUpdate).toHaveBeenCalledWith({ + expect(eventService.emit).toHaveBeenCalledWith('user-updated', { user: req.user, - fields_changed: ['password'], + fieldsChanged: ['password'], }); }); }); diff --git a/packages/cli/test/unit/controllers/owner.controller.test.ts b/packages/cli/src/controllers/__tests__/owner.controller.test.ts similarity index 97% rename from packages/cli/test/unit/controllers/owner.controller.test.ts rename to packages/cli/src/controllers/__tests__/owner.controller.test.ts index 0057c2370896b..3aabc87cade9a 100644 --- a/packages/cli/test/unit/controllers/owner.controller.test.ts +++ b/packages/cli/src/controllers/__tests__/owner.controller.test.ts @@ -16,8 +16,8 @@ import type { OwnerRequest } from '@/requests'; import type { UserService } from '@/services/user.service'; import { PasswordUtility } from '@/services/password.utility'; -import { mockInstance } from '../../shared/mocking'; -import { badPasswords } from '../shared/testData'; +import { mockInstance } from '@test/mocking'; +import { badPasswords } from '@test/testData'; describe('OwnerController', () => { const configGetSpy = jest.spyOn(config, 'getEnv'); diff --git a/packages/cli/test/unit/controllers/translation.controller.test.ts b/packages/cli/src/controllers/__tests__/translation.controller.test.ts similarity index 100% rename from packages/cli/test/unit/controllers/translation.controller.test.ts rename to packages/cli/src/controllers/__tests__/translation.controller.test.ts diff --git a/packages/cli/test/unit/controllers/userSettings.controller.test.ts b/packages/cli/src/controllers/__tests__/userSettings.controller.test.ts similarity index 96% rename from packages/cli/test/unit/controllers/userSettings.controller.test.ts rename to packages/cli/src/controllers/__tests__/userSettings.controller.test.ts index e29afb74ccc29..3794008641fa5 100644 --- a/packages/cli/test/unit/controllers/userSettings.controller.test.ts +++ b/packages/cli/src/controllers/__tests__/userSettings.controller.test.ts @@ -60,7 +60,7 @@ describe('UserSettingsController', () => { [], ], [ - 'updates user settings, reseting to waiting state', + 'updates user settings, resetting to waiting state', { waitingForResponse: true, ignoredCount: 0, @@ -137,7 +137,7 @@ describe('UserSettingsController', () => { 'is waitingForResponse but missing ignoredCount', { lastShownAt: 123, waitingForResponse: true }, ], - ])('thows error when request payload is %s', async (_, payload) => { + ])('throws error when request payload is %s', async (_, payload) => { const req = mock(); req.user.id = '1'; req.body = payload; diff --git a/packages/cli/src/controllers/auth.controller.ts b/packages/cli/src/controllers/auth.controller.ts index 7994221d9db6c..e2a481fae9fe0 100644 --- a/packages/cli/src/controllers/auth.controller.ts +++ b/packages/cli/src/controllers/auth.controller.ts @@ -24,7 +24,7 @@ import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { ApplicationError } from 'n8n-workflow'; import { UserRepository } from '@/databases/repositories/user.repository'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; @RestController() export class AuthController { @@ -179,7 +179,7 @@ export class AuthController { throw new BadRequestError('Invalid request'); } - void this.internalHooks.onUserInviteEmailClick({ inviter, invitee }); + this.internalHooks.onUserInviteEmailClick({ inviter, invitee }); this.eventService.emit('user-invite-email-click', { inviter, invitee }); const { firstName, lastName } = inviter; diff --git a/packages/cli/src/controllers/communityPackages.controller.ts b/packages/cli/src/controllers/communityPackages.controller.ts index b37d3df249ce4..d9e7f49719679 100644 --- a/packages/cli/src/controllers/communityPackages.controller.ts +++ b/packages/cli/src/controllers/communityPackages.controller.ts @@ -1,20 +1,17 @@ -import { Request, Response, NextFunction } from 'express'; -import config from '@/config'; import { RESPONSE_ERROR_MESSAGES, STARTER_TEMPLATE_NAME, UNKNOWN_FAILURE_REASON, } from '@/constants'; -import { Delete, Get, Middleware, Patch, Post, RestController, GlobalScope } from '@/decorators'; +import { Delete, Get, Patch, Post, RestController, GlobalScope } from '@/decorators'; import { NodeRequest } from '@/requests'; import type { InstalledPackages } from '@db/entities/InstalledPackages'; import type { CommunityPackages } from '@/Interfaces'; -import { InternalHooks } from '@/InternalHooks'; import { Push } from '@/push'; import { CommunityPackagesService } from '@/services/communityPackages.service'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { InternalServerError } from '@/errors/response-errors/internal-server.error'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; const { PACKAGE_NOT_INSTALLED, @@ -37,22 +34,10 @@ export function isNpmError(error: unknown): error is { code: number; stdout: str export class CommunityPackagesController { constructor( private readonly push: Push, - private readonly internalHooks: InternalHooks, private readonly communityPackagesService: CommunityPackagesService, private readonly eventService: EventService, ) {} - // TODO: move this into a new decorator `@IfConfig('executions.mode', 'queue')` - @Middleware() - checkIfCommunityNodesEnabled(req: Request, res: Response, next: NextFunction) { - if (config.getEnv('executions.mode') === 'queue' && req.method !== 'GET') - res.status(400).json({ - status: 'error', - message: 'Package management is disabled when running in "queue" mode', - }); - else next(); - } - @Post('/') @GlobalScope('communityPackage:install') async installPackage(req: NodeRequest.Post) { @@ -101,7 +86,7 @@ export class CommunityPackagesController { let installedPackage: InstalledPackages; try { - installedPackage = await this.communityPackagesService.installNpmModule( + installedPackage = await this.communityPackagesService.installPackage( parsed.packageName, parsed.version, ); @@ -209,7 +194,7 @@ export class CommunityPackagesController { } try { - await this.communityPackagesService.removeNpmModule(name, installedPackage); + await this.communityPackagesService.removePackage(name, installedPackage); } catch (error) { const message = [ `Error removing package "${name}"`, @@ -254,7 +239,7 @@ export class CommunityPackagesController { } try { - const newInstalledPackage = await this.communityPackagesService.updateNpmModule( + const newInstalledPackage = await this.communityPackagesService.updatePackage( this.communityPackagesService.parseNpmPackageName(name).packageName, previouslyInstalledPackage, ); diff --git a/packages/cli/src/controllers/invitation.controller.ts b/packages/cli/src/controllers/invitation.controller.ts index d89d077ae61b0..19bd803c5e6c0 100644 --- a/packages/cli/src/controllers/invitation.controller.ts +++ b/packages/cli/src/controllers/invitation.controller.ts @@ -16,15 +16,13 @@ import type { User } from '@/databases/entities/User'; import { UserRepository } from '@db/repositories/user.repository'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; -import { InternalHooks } from '@/InternalHooks'; import { ExternalHooks } from '@/ExternalHooks'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; @RestController('/invitations') export class InvitationController { constructor( private readonly logger: Logger, - private readonly internalHooks: InternalHooks, private readonly externalHooks: ExternalHooks, private readonly authService: AuthService, private readonly userService: UserService, @@ -168,11 +166,11 @@ export class InvitationController { this.authService.issueCookie(res, updatedUser, req.browserId); - void this.internalHooks.onUserSignup(updatedUser, { - user_type: 'email', - was_disabled_ldap_user: false, + this.eventService.emit('user-signed-up', { + user: updatedUser, + userType: 'email', + wasDisabledLdapUser: false, }); - this.eventService.emit('user-signed-up', { user: updatedUser }); const publicInvitee = await this.userService.toPublic(invitee); diff --git a/packages/cli/src/controllers/me.controller.ts b/packages/cli/src/controllers/me.controller.ts index 62a2e101ff27d..2073f737c47a6 100644 --- a/packages/cli/src/controllers/me.controller.ts +++ b/packages/cli/src/controllers/me.controller.ts @@ -19,11 +19,10 @@ import { isSamlLicensedAndEnabled } from '@/sso/saml/samlHelpers'; import { UserService } from '@/services/user.service'; import { Logger } from '@/Logger'; import { ExternalHooks } from '@/ExternalHooks'; -import { InternalHooks } from '@/InternalHooks'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { UserRepository } from '@/databases/repositories/user.repository'; import { isApiEnabled } from '@/PublicApi'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; export const API_KEY_PREFIX = 'n8n_api_'; @@ -40,7 +39,6 @@ export class MeController { constructor( private readonly logger: Logger, private readonly externalHooks: ExternalHooks, - private readonly internalHooks: InternalHooks, private readonly authService: AuthService, private readonly userService: UserService, private readonly passwordUtility: PasswordUtility, @@ -101,7 +99,6 @@ export class MeController { this.authService.issueCookie(res, user, req.browserId); const fieldsChanged = Object.keys(payload); - void this.internalHooks.onUserUpdate({ user, fields_changed: fieldsChanged }); this.eventService.emit('user-updated', { user, fieldsChanged }); const publicUser = await this.userService.toPublic(user); @@ -151,7 +148,6 @@ export class MeController { this.authService.issueCookie(res, updatedUser, req.browserId); - void this.internalHooks.onUserUpdate({ user: updatedUser, fields_changed: ['password'] }); this.eventService.emit('user-updated', { user: updatedUser, fieldsChanged: ['password'] }); await this.externalHooks.run('user.password.update', [updatedUser.email, updatedUser.password]); @@ -186,7 +182,10 @@ export class MeController { this.logger.info('User survey updated successfully', { userId: req.user.id }); - void this.internalHooks.onPersonalizationSurveySubmitted(req.user.id, personalizationAnswers); + this.eventService.emit('user-submitted-personalization-survey', { + userId: req.user.id, + answers: personalizationAnswers, + }); return { success: true }; } diff --git a/packages/cli/test/unit/controllers/oauth/oAuth1Credential.controller.test.ts b/packages/cli/src/controllers/oauth/__tests__/oAuth1Credential.controller.test.ts similarity index 99% rename from packages/cli/test/unit/controllers/oauth/oAuth1Credential.controller.test.ts rename to packages/cli/src/controllers/oauth/__tests__/oAuth1Credential.controller.test.ts index cbadc1cf87716..fae443b4ce9a2 100644 --- a/packages/cli/test/unit/controllers/oauth/oAuth1Credential.controller.test.ts +++ b/packages/cli/src/controllers/oauth/__tests__/oAuth1Credential.controller.test.ts @@ -19,7 +19,7 @@ import { CredentialsHelper } from '@/CredentialsHelper'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; -import { mockInstance } from '../../../shared/mocking'; +import { mockInstance } from '@test/mocking'; describe('OAuth1CredentialController', () => { mockInstance(Logger); diff --git a/packages/cli/test/unit/controllers/oauth/oAuth2Credential.controller.test.ts b/packages/cli/src/controllers/oauth/__tests__/oAuth2Credential.controller.test.ts similarity index 99% rename from packages/cli/test/unit/controllers/oauth/oAuth2Credential.controller.test.ts rename to packages/cli/src/controllers/oauth/__tests__/oAuth2Credential.controller.test.ts index f2b718fda0090..f354a1ec024e7 100644 --- a/packages/cli/test/unit/controllers/oauth/oAuth2Credential.controller.test.ts +++ b/packages/cli/src/controllers/oauth/__tests__/oAuth2Credential.controller.test.ts @@ -19,7 +19,7 @@ import { CredentialsHelper } from '@/CredentialsHelper'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; -import { mockInstance } from '../../../shared/mocking'; +import { mockInstance } from '@test/mocking'; describe('OAuth2CredentialController', () => { mockInstance(Logger); diff --git a/packages/cli/src/controllers/oauth/abstractOAuth.controller.ts b/packages/cli/src/controllers/oauth/abstractOAuth.controller.ts index 9454b4ee7d1ce..b9f0d64446ad5 100644 --- a/packages/cli/src/controllers/oauth/abstractOAuth.controller.ts +++ b/packages/cli/src/controllers/oauth/abstractOAuth.controller.ts @@ -5,9 +5,7 @@ import { Credentials } from 'n8n-core'; import type { ICredentialDataDecryptedObject, IWorkflowExecuteAdditionalData } from 'n8n-workflow'; import { jsonParse, ApplicationError } from 'n8n-workflow'; -import config from '@/config'; import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; -import type { User } from '@db/entities/User'; import { CredentialsRepository } from '@db/repositories/credentials.repository'; import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; import type { ICredentialsDb } from '@/Interfaces'; @@ -20,6 +18,7 @@ import { ExternalHooks } from '@/ExternalHooks'; import { UrlService } from '@/services/url.service'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; +import { GlobalConfig } from '@n8n/config'; export interface CsrfStateParam { cid: string; @@ -37,10 +36,11 @@ export abstract class AbstractOAuthController { private readonly credentialsRepository: CredentialsRepository, private readonly sharedCredentialsRepository: SharedCredentialsRepository, private readonly urlService: UrlService, + private readonly globalConfig: GlobalConfig, ) {} get baseUrl() { - const restUrl = `${this.urlService.getInstanceBaseUrl()}/${config.getEnv('endpoints.rest')}`; + const restUrl = `${this.urlService.getInstanceBaseUrl()}/${this.globalConfig.endpoints.rest}`; return `${restUrl}/oauth${this.oauthVersion}-credential`; } @@ -70,8 +70,8 @@ export abstract class AbstractOAuthController { return credential; } - protected async getAdditionalData(user: User) { - return await WorkflowExecuteAdditionalData.getBase(user.id); + protected async getAdditionalData() { + return await WorkflowExecuteAdditionalData.getBase(); } protected async getDecryptedData( @@ -118,7 +118,7 @@ export abstract class AbstractOAuthController { return await this.credentialsRepository.findOneBy({ id: credentialId }); } - protected createCsrfState(credentialsId: string): [string, string] { + createCsrfState(credentialsId: string): [string, string] { const token = new Csrf(); const csrfSecret = token.secretSync(); const state: CsrfStateParam = { diff --git a/packages/cli/src/controllers/oauth/oAuth1Credential.controller.ts b/packages/cli/src/controllers/oauth/oAuth1Credential.controller.ts index 578a209e3664f..2a50b00bf9d57 100644 --- a/packages/cli/src/controllers/oauth/oAuth1Credential.controller.ts +++ b/packages/cli/src/controllers/oauth/oAuth1Credential.controller.ts @@ -33,7 +33,7 @@ export class OAuth1CredentialController extends AbstractOAuthController { @Get('/auth') async getAuthUri(req: OAuthRequest.OAuth1Credential.Auth): Promise { const credential = await this.getCredential(req); - const additionalData = await this.getAdditionalData(req.user); + const additionalData = await this.getAdditionalData(); const decryptedDataOriginal = await this.getDecryptedData(credential, additionalData); const oauthCredentials = this.applyDefaultsAndOverwrites( credential, @@ -99,9 +99,8 @@ export class OAuth1CredentialController extends AbstractOAuthController { } /** Verify and store app code. Generate access tokens and store for respective credential */ - @Get('/callback', { usesTemplates: true }) + @Get('/callback', { usesTemplates: true, skipAuth: true }) async handleCallback(req: OAuthRequest.OAuth1Credential.Callback, res: Response) { - const userId = req.user?.id; try { const { oauth_verifier, oauth_token, state: encodedState } = req.query; @@ -124,11 +123,11 @@ export class OAuth1CredentialController extends AbstractOAuthController { const credential = await this.getCredentialWithoutUser(credentialId); if (!credential) { const errorMessage = 'OAuth1 callback failed because of insufficient permissions'; - this.logger.error(errorMessage, { userId, credentialId }); + this.logger.error(errorMessage, { credentialId }); return this.renderCallbackError(res, errorMessage); } - const additionalData = await this.getAdditionalData(req.user); + const additionalData = await this.getAdditionalData(); const decryptedDataOriginal = await this.getDecryptedData(credential, additionalData); const oauthCredentials = this.applyDefaultsAndOverwrites( credential, @@ -138,7 +137,7 @@ export class OAuth1CredentialController extends AbstractOAuthController { if (this.verifyCsrfState(decryptedDataOriginal, state)) { const errorMessage = 'The OAuth1 callback state is invalid!'; - this.logger.debug(errorMessage, { userId, credentialId }); + this.logger.debug(errorMessage, { credentialId }); return this.renderCallbackError(res, errorMessage); } @@ -156,7 +155,7 @@ export class OAuth1CredentialController extends AbstractOAuthController { try { oauthToken = await axios.request(options); } catch (error) { - this.logger.error('Unable to fetch tokens for OAuth1 callback', { userId, credentialId }); + this.logger.error('Unable to fetch tokens for OAuth1 callback', { credentialId }); const errorResponse = new NotFoundError('Unable to get access tokens!'); return sendErrorResponse(res, errorResponse); } @@ -172,15 +171,11 @@ export class OAuth1CredentialController extends AbstractOAuthController { await this.encryptAndSaveData(credential, decryptedDataOriginal); this.logger.verbose('OAuth1 callback successful for new credential', { - userId, credentialId, }); return res.render('oauth-callback'); } catch (error) { - this.logger.error('OAuth1 callback failed because of insufficient user permissions', { - userId, - }); - // Error response + this.logger.error('OAuth1 callback failed because of insufficient user permissions'); return sendErrorResponse(res, error as Error); } } diff --git a/packages/cli/src/controllers/oauth/oAuth2Credential.controller.ts b/packages/cli/src/controllers/oauth/oAuth2Credential.controller.ts index 71a0fe140c4c1..5b7929495fd42 100644 --- a/packages/cli/src/controllers/oauth/oAuth2Credential.controller.ts +++ b/packages/cli/src/controllers/oauth/oAuth2Credential.controller.ts @@ -20,7 +20,7 @@ export class OAuth2CredentialController extends AbstractOAuthController { @Get('/auth') async getAuthUri(req: OAuthRequest.OAuth2Credential.Auth): Promise { const credential = await this.getCredential(req); - const additionalData = await this.getAdditionalData(req.user); + const additionalData = await this.getAdditionalData(); const decryptedDataOriginal = await this.getDecryptedData(credential, additionalData); // At some point in the past we saved hidden scopes to credentials (but shouldn't) @@ -80,9 +80,8 @@ export class OAuth2CredentialController extends AbstractOAuthController { } /** Verify and store app code. Generate access tokens and store for respective credential */ - @Get('/callback', { usesTemplates: true }) + @Get('/callback', { usesTemplates: true, skipAuth: true }) async handleCallback(req: OAuthRequest.OAuth2Credential.Callback, res: Response) { - const userId = req.user?.id; try { const { code, state: encodedState } = req.query; if (!code || !encodedState) { @@ -104,11 +103,11 @@ export class OAuth2CredentialController extends AbstractOAuthController { const credential = await this.getCredentialWithoutUser(credentialId); if (!credential) { const errorMessage = 'OAuth2 callback failed because of insufficient permissions'; - this.logger.error(errorMessage, { userId, credentialId }); + this.logger.error(errorMessage, { credentialId }); return this.renderCallbackError(res, errorMessage); } - const additionalData = await this.getAdditionalData(req.user); + const additionalData = await this.getAdditionalData(); const decryptedDataOriginal = await this.getDecryptedData(credential, additionalData); const oauthCredentials = this.applyDefaultsAndOverwrites( credential, @@ -118,7 +117,7 @@ export class OAuth2CredentialController extends AbstractOAuthController { if (this.verifyCsrfState(decryptedDataOriginal, state)) { const errorMessage = 'The OAuth2 callback state is invalid!'; - this.logger.debug(errorMessage, { userId, credentialId }); + this.logger.debug(errorMessage, { credentialId }); return this.renderCallbackError(res, errorMessage); } @@ -157,7 +156,7 @@ export class OAuth2CredentialController extends AbstractOAuthController { if (oauthToken === undefined) { const errorMessage = 'Unable to get OAuth2 access tokens!'; - this.logger.error(errorMessage, { userId, credentialId }); + this.logger.error(errorMessage, { credentialId }); return this.renderCallbackError(res, errorMessage); } @@ -174,7 +173,6 @@ export class OAuth2CredentialController extends AbstractOAuthController { await this.encryptAndSaveData(credential, decryptedDataOriginal); this.logger.verbose('OAuth2 callback successful for credential', { - userId, credentialId, }); diff --git a/packages/cli/src/controllers/owner.controller.ts b/packages/cli/src/controllers/owner.controller.ts index 6077026c90202..9b86ac41ab313 100644 --- a/packages/cli/src/controllers/owner.controller.ts +++ b/packages/cli/src/controllers/owner.controller.ts @@ -85,7 +85,7 @@ export class OwnerController { this.authService.issueCookie(res, owner, req.browserId); - void this.internalHooks.onInstanceOwnerSetup({ user_id: owner.id }); + this.internalHooks.onInstanceOwnerSetup({ user_id: owner.id }); return await this.userService.toPublic(owner, { posthog: this.postHog, withScopes: true }); } diff --git a/packages/cli/src/controllers/passwordReset.controller.ts b/packages/cli/src/controllers/passwordReset.controller.ts index c548e607fd9d4..82506e952bc32 100644 --- a/packages/cli/src/controllers/passwordReset.controller.ts +++ b/packages/cli/src/controllers/passwordReset.controller.ts @@ -21,7 +21,7 @@ import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { UnprocessableRequestError } from '@/errors/response-errors/unprocessable.error'; import { UserRepository } from '@/databases/repositories/user.repository'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; @RestController() export class PasswordResetController { @@ -120,7 +120,7 @@ export class PasswordResetController { domain: this.urlService.getInstanceBaseUrl(), }); } catch (error) { - void this.internalHooks.onEmailFailed({ + this.internalHooks.onEmailFailed({ user, message_type: 'Reset password', public_api: false, @@ -132,13 +132,13 @@ export class PasswordResetController { } this.logger.info('Sent password reset email successfully', { userId: user.id, email }); - void this.internalHooks.onUserTransactionalEmail({ + this.internalHooks.onUserTransactionalEmail({ user_id: id, message_type: 'Reset password', public_api: false, }); - void this.internalHooks.onUserPasswordResetRequestClick({ user }); + this.internalHooks.onUserPasswordResetRequestClick({ user }); this.eventService.emit('user-password-reset-request-click', { user }); } @@ -171,7 +171,7 @@ export class PasswordResetController { } this.logger.info('Reset-password token resolved successfully', { userId: user.id }); - void this.internalHooks.onUserPasswordResetEmailClick({ user }); + this.internalHooks.onUserPasswordResetEmailClick({ user }); this.eventService.emit('user-password-reset-email-click', { user }); } @@ -215,17 +215,16 @@ export class PasswordResetController { this.authService.issueCookie(res, user, req.browserId); - void this.internalHooks.onUserUpdate({ user, fields_changed: ['password'] }); this.eventService.emit('user-updated', { user, fieldsChanged: ['password'] }); - // if this user used to be an LDAP users + // if this user used to be an LDAP user const ldapIdentity = user?.authIdentities?.find((i) => i.providerType === 'ldap'); if (ldapIdentity) { - void this.internalHooks.onUserSignup(user, { - user_type: 'email', - was_disabled_ldap_user: true, + this.eventService.emit('user-signed-up', { + user, + userType: 'email', + wasDisabledLdapUser: true, }); - this.eventService.emit('user-signed-up', { user }); } await this.externalHooks.run('user.password.update', [user.email, passwordHash]); diff --git a/packages/cli/src/controllers/project.controller.ts b/packages/cli/src/controllers/project.controller.ts index 848ba4b84c6eb..e93b919ecb8e3 100644 --- a/packages/cli/src/controllers/project.controller.ts +++ b/packages/cli/src/controllers/project.controller.ts @@ -23,7 +23,7 @@ import { ProjectRepository } from '@/databases/repositories/project.repository'; // eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import import { In, Not } from '@n8n/typeorm'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; @RestController('/projects') export class ProjectController { diff --git a/packages/cli/src/controllers/users.controller.ts b/packages/cli/src/controllers/users.controller.ts index 0fd8481ed8813..c834d2cc237bc 100644 --- a/packages/cli/src/controllers/users.controller.ts +++ b/packages/cli/src/controllers/users.controller.ts @@ -9,7 +9,7 @@ import { UserRoleChangePayload, UserSettingsUpdatePayload, } from '@/requests'; -import type { PublicUser, ITelemetryUserDeletionData } from '@/Interfaces'; +import type { PublicUser } from '@/Interfaces'; import { AuthIdentity } from '@db/entities/AuthIdentity'; import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; @@ -21,21 +21,19 @@ import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ExternalHooks } from '@/ExternalHooks'; -import { InternalHooks } from '@/InternalHooks'; import { validateEntity } from '@/GenericHelpers'; import { ProjectRepository } from '@/databases/repositories/project.repository'; import { Project } from '@/databases/entities/Project'; import { WorkflowService } from '@/workflows/workflow.service'; import { CredentialsService } from '@/credentials/credentials.service'; import { ProjectService } from '@/services/project.service'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; @RestController('/users') export class UsersController { constructor( private readonly logger: Logger, private readonly externalHooks: ExternalHooks, - private readonly internalHooks: InternalHooks, private readonly sharedCredentialsRepository: SharedCredentialsRepository, private readonly sharedWorkflowRepository: SharedWorkflowRepository, private readonly userRepository: UserRepository, @@ -183,12 +181,7 @@ export class UsersController { ); } - const telemetryData: ITelemetryUserDeletionData = { - user_id: req.user.id, - target_user_old_status: userToDelete.isPending ? 'invited' : 'active', - target_user_id: idToDelete, - migration_strategy: transferId ? 'transfer_data' : 'delete_data', - }; + let transfereeId; if (transferId) { const transfereePersonalProject = await this.projectRepository.findOneBy({ id: transferId }); @@ -206,7 +199,7 @@ export class UsersController { }, }); - telemetryData.migration_user_id = transferee.id; + transfereeId = transferee.id; await this.userService.getManager().transaction(async (trx) => { await this.workflowService.transferAll( @@ -253,12 +246,14 @@ export class UsersController { await trx.delete(User, { id: userToDelete.id }); }); - void this.internalHooks.onUserDeletion({ + this.eventService.emit('user-deleted', { user: req.user, - telemetryData, publicApi: false, + targetUserOldStatus: userToDelete.isPending ? 'invited' : 'active', + targetUserId: idToDelete, + migrationStrategy: transferId ? 'transfer_data' : 'delete_data', + migrationUserId: transfereeId, }); - this.eventService.emit('user-deleted', { user: req.user }); await this.externalHooks.run('user.deleted', [await this.userService.toPublic(userToDelete)]); @@ -294,11 +289,11 @@ export class UsersController { await this.userService.update(targetUser.id, { role: payload.newRoleName }); - void this.internalHooks.onUserRoleChange({ - user: req.user, - target_user_id: targetUser.id, - target_user_new_role: ['global', payload.newRoleName].join(' '), - public_api: false, + this.eventService.emit('user-changed-role', { + userId: req.user.id, + targetUserId: targetUser.id, + targetUserNewRole: ['global', payload.newRoleName].join(' '), + publicApi: false, }); const projects = await this.projectService.getUserOwnedOrAdminProjects(targetUser.id); diff --git a/packages/cli/src/credentials/__tests__/credentials.service.test.ts b/packages/cli/src/credentials/__tests__/credentials.service.test.ts index 18216df8674ce..a5fa3ca183cb7 100644 --- a/packages/cli/src/credentials/__tests__/credentials.service.test.ts +++ b/packages/cli/src/credentials/__tests__/credentials.service.test.ts @@ -3,7 +3,7 @@ import { mock } from 'jest-mock-extended'; import { CREDENTIAL_BLANKING_VALUE } from '@/constants'; import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; import type { CredentialTypes } from '@/CredentialTypes'; -import { CredentialsService } from '../credentials.service'; +import { CredentialsService } from '@/credentials/credentials.service'; describe('CredentialsService', () => { const credType = mock({ diff --git a/packages/cli/src/credentials/credentials.controller.ts b/packages/cli/src/credentials/credentials.controller.ts index 1587fbd1ca426..9ac57bb2e3af3 100644 --- a/packages/cli/src/credentials/credentials.controller.ts +++ b/packages/cli/src/credentials/credentials.controller.ts @@ -30,7 +30,7 @@ import { SharedCredentialsRepository } from '@/databases/repositories/sharedCred import { SharedCredentials } from '@/databases/entities/SharedCredentials'; import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository'; import { z } from 'zod'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; @RestController('/credentials') export class CredentialsController { diff --git a/packages/cli/test/unit/databases/entities/user.entity.test.ts b/packages/cli/src/databases/entities/__tests__/user.entity.test.ts similarity index 100% rename from packages/cli/test/unit/databases/entities/user.entity.test.ts rename to packages/cli/src/databases/entities/__tests__/user.entity.test.ts diff --git a/packages/cli/test/unit/repositories/execution.repository.test.ts b/packages/cli/src/databases/repositories/__tests__/execution.repository.test.ts similarity index 95% rename from packages/cli/test/unit/repositories/execution.repository.test.ts rename to packages/cli/src/databases/repositories/__tests__/execution.repository.test.ts index f8aba2e8a1228..1a4929f5cdf6e 100644 --- a/packages/cli/test/unit/repositories/execution.repository.test.ts +++ b/packages/cli/src/databases/repositories/__tests__/execution.repository.test.ts @@ -8,8 +8,7 @@ import { mock } from 'jest-mock-extended'; import { ExecutionEntity } from '@db/entities/ExecutionEntity'; import { ExecutionRepository } from '@db/repositories/execution.repository'; -import { mockEntityManager } from '../../shared/mocking'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance, mockEntityManager } from '@test/mocking'; describe('ExecutionRepository', () => { const entityManager = mockEntityManager(ExecutionEntity); diff --git a/packages/cli/test/unit/repositories/sharedCredentials.repository.test.ts b/packages/cli/src/databases/repositories/__tests__/sharedCredentials.repository.test.ts similarity index 98% rename from packages/cli/test/unit/repositories/sharedCredentials.repository.test.ts rename to packages/cli/src/databases/repositories/__tests__/sharedCredentials.repository.test.ts index 8eb8b498145ef..6fb4bad4eacf3 100644 --- a/packages/cli/test/unit/repositories/sharedCredentials.repository.test.ts +++ b/packages/cli/src/databases/repositories/__tests__/sharedCredentials.repository.test.ts @@ -8,7 +8,7 @@ import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; import { SharedCredentials } from '@db/entities/SharedCredentials'; import { SharedCredentialsRepository } from '@db/repositories/sharedCredentials.repository'; import { GLOBAL_MEMBER_SCOPES, GLOBAL_OWNER_SCOPES } from '@/permissions/global-roles'; -import { mockEntityManager } from '../../shared/mocking'; +import { mockEntityManager } from '@test/mocking'; describe('SharedCredentialsRepository', () => { const entityManager = mockEntityManager(SharedCredentials); diff --git a/packages/cli/test/unit/repositories/workflowStatistics.test.ts b/packages/cli/src/databases/repositories/__tests__/workflowStatistics.test.ts similarity index 96% rename from packages/cli/test/unit/repositories/workflowStatistics.test.ts rename to packages/cli/src/databases/repositories/__tests__/workflowStatistics.test.ts index 86e0ee1c92bd1..7bed056549751 100644 --- a/packages/cli/test/unit/repositories/workflowStatistics.test.ts +++ b/packages/cli/src/databases/repositories/__tests__/workflowStatistics.test.ts @@ -5,7 +5,7 @@ import { mock, mockClear } from 'jest-mock-extended'; import { StatisticsNames, WorkflowStatistics } from '@db/entities/WorkflowStatistics'; import { WorkflowStatisticsRepository } from '@db/repositories/workflowStatistics.repository'; -import { mockEntityManager } from '../../shared/mocking'; +import { mockEntityManager } from '@test/mocking'; describe('insertWorkflowStatistics', () => { const entityManager = mockEntityManager(WorkflowStatistics); diff --git a/packages/cli/src/databases/repositories/execution.repository.ts b/packages/cli/src/databases/repositories/execution.repository.ts index 1ebb22d8eb4bc..a8605147aa751 100644 --- a/packages/cli/src/databases/repositories/execution.repository.ts +++ b/packages/cli/src/databases/repositories/execution.repository.ts @@ -270,17 +270,27 @@ export class ExecutionRepository extends Repository { return rest; } + /** + * Insert a new execution and its execution data using a transaction. + */ async createNewExecution(execution: ExecutionPayload): Promise { - const { data, workflowData, ...rest } = execution; - const { identifiers: inserted } = await this.insert(rest); - const { id: executionId } = inserted[0] as { id: string }; - const { connections, nodes, name, settings } = workflowData ?? {}; - await this.executionDataRepository.insert({ - executionId, - workflowData: { connections, nodes, name, settings, id: workflowData.id }, - data: stringify(data), + return await this.manager.transaction(async (transactionManager) => { + const { data, workflowData, ...rest } = execution; + const insertResult = await transactionManager.insert(ExecutionEntity, rest); + const { id: executionId } = insertResult.identifiers[0] as { id: string }; + + const { connections, nodes, name, settings } = workflowData ?? {}; + await this.executionDataRepository.createExecutionDataForExecution( + { + executionId, + workflowData: { connections, nodes, name, settings, id: workflowData.id }, + data: stringify(data), + }, + transactionManager, + ); + + return String(executionId); }); - return String(executionId); } async markAsCrashed(executionIds: string | string[]) { diff --git a/packages/cli/src/databases/repositories/executionData.repository.ts b/packages/cli/src/databases/repositories/executionData.repository.ts index 5872f9888cd66..013453d998e42 100644 --- a/packages/cli/src/databases/repositories/executionData.repository.ts +++ b/packages/cli/src/databases/repositories/executionData.repository.ts @@ -1,13 +1,32 @@ import { Service } from 'typedi'; +import type { EntityManager } from '@n8n/typeorm'; +import type { IWorkflowBase } from 'n8n-workflow'; import { DataSource, In, Repository } from '@n8n/typeorm'; import { ExecutionData } from '../entities/ExecutionData'; +export interface CreateExecutionDataOpts extends Pick { + workflowData: Pick; +} + @Service() export class ExecutionDataRepository extends Repository { constructor(dataSource: DataSource) { super(ExecutionData, dataSource.manager); } + async createExecutionDataForExecution( + executionData: CreateExecutionDataOpts, + transactionManager: EntityManager, + ) { + const { data, executionId, workflowData } = executionData; + + return await transactionManager.insert(ExecutionData, { + executionId, + data, + workflowData, + }); + } + async findByExecutionIds(executionIds: string[]) { return await this.find({ select: ['workflowData'], diff --git a/packages/cli/src/databases/repositories/projectRelation.repository.ts b/packages/cli/src/databases/repositories/projectRelation.repository.ts index bddfd6e38d66f..00fc4de34a195 100644 --- a/packages/cli/src/databases/repositories/projectRelation.repository.ts +++ b/packages/cli/src/databases/repositories/projectRelation.repository.ts @@ -52,4 +52,13 @@ export class ProjectRelationRepository extends Repository { {} as Record, ); } + + async findUserIdsByProjectId(projectId: string): Promise { + const rows = await this.find({ + select: ['userId'], + where: { projectId }, + }); + + return [...new Set(rows.map((r) => r.userId))]; + } } diff --git a/packages/cli/src/databases/repositories/sharedWorkflow.repository.ts b/packages/cli/src/databases/repositories/sharedWorkflow.repository.ts index f8ff3523b2eab..4dc54935cb193 100644 --- a/packages/cli/src/databases/repositories/sharedWorkflow.repository.ts +++ b/packages/cli/src/databases/repositories/sharedWorkflow.repository.ts @@ -175,7 +175,7 @@ export class SharedWorkflowRepository extends Repository { }, }); - return sharedWorkflows.map((sw) => sw.workflow); + return sharedWorkflows.map((sw) => ({ ...sw.workflow, projectId: sw.projectId })); } /** diff --git a/packages/cli/test/unit/databases/utils/customValidators.test.ts b/packages/cli/src/databases/utils/__tests__/customValidators.test.ts similarity index 100% rename from packages/cli/test/unit/databases/utils/customValidators.test.ts rename to packages/cli/src/databases/utils/__tests__/customValidators.test.ts diff --git a/packages/cli/test/unit/databases/utils/migrationHelpers.test.ts b/packages/cli/src/databases/utils/__tests__/migrationHelpers.test.ts similarity index 100% rename from packages/cli/test/unit/databases/utils/migrationHelpers.test.ts rename to packages/cli/src/databases/utils/__tests__/migrationHelpers.test.ts diff --git a/packages/cli/src/decorators/Redactable.ts b/packages/cli/src/decorators/Redactable.ts index e5debeb7a1507..51d02c5c3d6fc 100644 --- a/packages/cli/src/decorators/Redactable.ts +++ b/packages/cli/src/decorators/Redactable.ts @@ -1,5 +1,5 @@ import { RedactableError } from '@/errors/redactable.error'; -import type { UserLike } from '@/eventbus/event.types'; +import type { UserLike } from '@/events/relay-event-map'; function toRedactable(userLike: UserLike) { return { @@ -14,7 +14,7 @@ function toRedactable(userLike: UserLike) { type FieldName = 'user' | 'inviter' | 'invitee'; /** - * Mark redactable properties in a `{ user: UserLike }` field in an `AuditEventRelay` + * Mark redactable properties in a `{ user: UserLike }` field in an `LogStreamingEventRelay` * method arg. These properties will be later redacted by the log streaming * destination based on user prefs. Only for `n8n.audit.*` logs. * diff --git a/packages/cli/test/unit/decorators/OnShutdown.test.ts b/packages/cli/src/decorators/__tests__/OnShutdown.test.ts similarity index 100% rename from packages/cli/test/unit/decorators/OnShutdown.test.ts rename to packages/cli/src/decorators/__tests__/OnShutdown.test.ts diff --git a/packages/cli/test/unit/decorators/controller.registry.test.ts b/packages/cli/src/decorators/__tests__/controller.registry.test.ts similarity index 94% rename from packages/cli/test/unit/decorators/controller.registry.test.ts rename to packages/cli/src/decorators/__tests__/controller.registry.test.ts index 04b4884dcc331..05a97aab5378e 100644 --- a/packages/cli/test/unit/decorators/controller.registry.test.ts +++ b/packages/cli/src/decorators/__tests__/controller.registry.test.ts @@ -10,16 +10,18 @@ import { ControllerRegistry, Get, Licensed, RestController } from '@/decorators' import type { AuthService } from '@/auth/auth.service'; import type { License } from '@/License'; import type { SuperAgentTest } from '@test-integration/types'; +import type { GlobalConfig } from '@n8n/config'; describe('ControllerRegistry', () => { const license = mock(); const authService = mock(); + const globalConfig = mock({ endpoints: { rest: 'rest' } }); let agent: SuperAgentTest; beforeEach(() => { jest.resetAllMocks(); const app = express(); - new ControllerRegistry(license, authService).activate(app); + new ControllerRegistry(license, authService, globalConfig).activate(app); agent = testAgent(app); }); diff --git a/packages/cli/src/decorators/controller.registry.ts b/packages/cli/src/decorators/controller.registry.ts index c012922c16728..4173f9309ef3c 100644 --- a/packages/cli/src/decorators/controller.registry.ts +++ b/packages/cli/src/decorators/controller.registry.ts @@ -4,7 +4,6 @@ import type { Application, Request, Response, RequestHandler } from 'express'; import { rateLimit as expressRateLimit } from 'express-rate-limit'; import { AuthService } from '@/auth/auth.service'; -import config from '@/config'; import { UnauthenticatedError } from '@/errors/response-errors/unauthenticated.error'; import { inProduction, RESPONSE_ERROR_MESSAGES } from '@/constants'; import type { BooleanLicenseFeature } from '@/Interfaces'; @@ -12,7 +11,7 @@ import { License } from '@/License'; import type { AuthenticatedRequest } from '@/requests'; import { send } from '@/ResponseHelper'; // TODO: move `ResponseHelper.send` to this file import { userHasScope } from '@/permissions/checkAccess'; - +import { GlobalConfig } from '@n8n/config'; import type { AccessScope, Controller, @@ -52,6 +51,7 @@ export class ControllerRegistry { constructor( private readonly license: License, private readonly authService: AuthService, + private readonly globalConfig: GlobalConfig, ) {} activate(app: Application) { @@ -64,7 +64,7 @@ export class ControllerRegistry { const metadata = registry.get(controllerClass)!; const router = Router({ mergeParams: true }); - const prefix = `/${config.getEnv('endpoints.rest')}/${metadata.basePath}` + const prefix = `/${this.globalConfig.endpoints.rest}/${metadata.basePath}` .replace(/\/+/g, '/') .replace(/\/$/, ''); app.use(prefix, router); diff --git a/packages/cli/test/unit/GitService.test.ts b/packages/cli/src/environments/sourceControl/__tests__/sourceControlGit.service.test.ts similarity index 100% rename from packages/cli/test/unit/GitService.test.ts rename to packages/cli/src/environments/sourceControl/__tests__/sourceControlGit.service.test.ts diff --git a/packages/cli/test/unit/SourceControl.test.ts b/packages/cli/src/environments/sourceControl/__tests__/sourceControlHelper.ee.test.ts similarity index 99% rename from packages/cli/test/unit/SourceControl.test.ts rename to packages/cli/src/environments/sourceControl/__tests__/sourceControlHelper.ee.test.ts index 5eeccbb1b97f4..5141d36f2f7dd 100644 --- a/packages/cli/test/unit/SourceControl.test.ts +++ b/packages/cli/src/environments/sourceControl/__tests__/sourceControlHelper.ee.test.ts @@ -18,7 +18,7 @@ import { import { constants as fsConstants, accessSync } from 'fs'; import type { SourceControlledFile } from '@/environments/sourceControl/types/sourceControlledFile'; import type { SourceControlPreferences } from '@/environments/sourceControl/types/sourceControlPreferences'; -import { mockInstance } from '../shared/mocking'; +import { mockInstance } from '@test/mocking'; const pushResult: SourceControlledFile[] = [ { diff --git a/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts b/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts index 0a1db892f60c9..f92b0bfb1f02f 100644 --- a/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts +++ b/packages/cli/src/environments/sourceControl/sourceControl.controller.ee.ts @@ -12,7 +12,7 @@ import type { SourceControlPreferences } from './types/sourceControlPreferences' import type { SourceControlledFile } from './types/sourceControlledFile'; import { SOURCE_CONTROL_DEFAULT_BRANCH } from './constants'; import type { ImportResult } from './types/importResult'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; import { getRepoType } from './sourceControlHelper.ee'; import { SourceControlGetStatus } from './types/sourceControlGetStatus'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; diff --git a/packages/cli/src/environments/sourceControl/sourceControl.service.ee.ts b/packages/cli/src/environments/sourceControl/sourceControl.service.ee.ts index ac226a1b2eacc..0c9279ffbe7a3 100644 --- a/packages/cli/src/environments/sourceControl/sourceControl.service.ee.ts +++ b/packages/cli/src/environments/sourceControl/sourceControl.service.ee.ts @@ -30,7 +30,7 @@ import type { TagEntity } from '@db/entities/TagEntity'; import type { Variables } from '@db/entities/Variables'; import type { SourceControlWorkflowVersionId } from './types/sourceControlWorkflowVersionId'; import type { ExportableCredential } from './types/exportableCredential'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; import { TagRepository } from '@db/repositories/tag.repository'; import { Logger } from '@/Logger'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; diff --git a/packages/cli/src/environments/variables/variables.service.ee.ts b/packages/cli/src/environments/variables/variables.service.ee.ts index 94233da065839..78a3d23fbe55d 100644 --- a/packages/cli/src/environments/variables/variables.service.ee.ts +++ b/packages/cli/src/environments/variables/variables.service.ee.ts @@ -6,7 +6,7 @@ import { CacheService } from '@/services/cache/cache.service'; import { VariablesRepository } from '@db/repositories/variables.repository'; import { VariableCountLimitReachedError } from '@/errors/variable-count-limit-reached.error'; import { VariableValidationError } from '@/errors/variable-validation.error'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; @Service() export class VariablesService { diff --git a/packages/cli/src/errors/redactable.error.ts b/packages/cli/src/errors/redactable.error.ts index 0f6697a0652d6..0d5b07ac504fb 100644 --- a/packages/cli/src/errors/redactable.error.ts +++ b/packages/cli/src/errors/redactable.error.ts @@ -3,7 +3,7 @@ import { ApplicationError } from 'n8n-workflow'; export class RedactableError extends ApplicationError { constructor(fieldName: string, args: string) { super( - `Failed to find "${fieldName}" property in argument "${args.toString()}". Please set the decorator \`@Redactable()\` only on \`AuditEventRelay\` methods where the argument contains a "${fieldName}" property.`, + `Failed to find "${fieldName}" property in argument "${args.toString()}". Please set the decorator \`@Redactable()\` only on \`LogStreamingEventRelay\` methods where the argument contains a "${fieldName}" property.`, ); } } diff --git a/packages/cli/test/unit/utils.test.ts b/packages/cli/src/errors/response-errors/__tests__/webhook-not-found.error.test.ts similarity index 100% rename from packages/cli/test/unit/utils.test.ts rename to packages/cli/src/errors/response-errors/__tests__/webhook-not-found.error.test.ts diff --git a/packages/cli/src/eventbus/MessageEventBus/MessageEventBus.ts b/packages/cli/src/eventbus/MessageEventBus/MessageEventBus.ts index 758eeb5ae590f..eeb868798bf6b 100644 --- a/packages/cli/src/eventbus/MessageEventBus/MessageEventBus.ts +++ b/packages/cli/src/eventbus/MessageEventBus/MessageEventBus.ts @@ -52,6 +52,8 @@ export interface MessageEventBusInitializeOptions { } @Service() +// TODO: Convert to TypedEventEmitter +// eslint-disable-next-line n8n-local-rules/no-type-unsafe-event-emitter export class MessageEventBus extends EventEmitter { private isInitialized = false; diff --git a/packages/cli/src/eventbus/MessageEventBusWriter/MessageEventBusLogWriterWorker.ts b/packages/cli/src/eventbus/MessageEventBusWriter/MessageEventBusLogWriterWorker.ts index 4686a1cf3c860..69d2e8ce26f73 100644 --- a/packages/cli/src/eventbus/MessageEventBusWriter/MessageEventBusLogWriterWorker.ts +++ b/packages/cli/src/eventbus/MessageEventBusWriter/MessageEventBusLogWriterWorker.ts @@ -6,7 +6,6 @@ import type { MessageEventBusLogWriterOptions } from './MessageEventBusLogWriter let logFileBasePath = ''; let loggingPaused = true; let keepFiles = 10; -let fileStatTimer: NodeJS.Timer; let maxLogFileSizeInKB = 102400; function setLogFileBasePath(basePath: string) { @@ -117,7 +116,7 @@ if (!isMainThread) { if (logFileBasePath) { renameAndCreateLogs(); loggingPaused = false; - fileStatTimer = setInterval(async () => { + setInterval(async () => { await checkFileSize(buildLogFileNameWithCounter()); }, 5000); } diff --git a/packages/cli/src/eventbus/__tests__/audit-event-relay.service.test.ts b/packages/cli/src/eventbus/__tests__/audit-event-relay.service.test.ts deleted file mode 100644 index 80392206079b7..0000000000000 --- a/packages/cli/src/eventbus/__tests__/audit-event-relay.service.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { mock } from 'jest-mock-extended'; -import { AuditEventRelay } from '../audit-event-relay.service'; -import type { MessageEventBus } from '../MessageEventBus/MessageEventBus'; -import type { Event } from '../event.types'; -import type { EventService } from '../event.service'; - -describe('AuditorService', () => { - const eventBus = mock(); - const eventService = mock(); - const auditor = new AuditEventRelay(eventService, eventBus); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should handle `user-deleted` event', () => { - const arg: Event['user-deleted'] = { - user: { - id: '123', - email: 'john@n8n.io', - firstName: 'John', - lastName: 'Doe', - role: 'some-role', - }, - }; - - // @ts-expect-error Private method - auditor.userDeleted(arg); - - expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ - eventName: 'n8n.audit.user.deleted', - payload: { - userId: '123', - _email: 'john@n8n.io', - _firstName: 'John', - _lastName: 'Doe', - globalRole: 'some-role', - }, - }); - }); - - it('should handle `user-invite-email-click` event', () => { - const arg: Event['user-invite-email-click'] = { - inviter: { - id: '123', - email: 'john@n8n.io', - firstName: 'John', - lastName: 'Doe', - role: 'some-role', - }, - invitee: { - id: '456', - email: 'jane@n8n.io', - firstName: 'Jane', - lastName: 'Doe', - role: 'some-other-role', - }, - }; - - // @ts-expect-error Private method - auditor.userInviteEmailClick(arg); - - expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ - eventName: 'n8n.audit.user.invitation.accepted', - payload: { - inviter: { - userId: '123', - _email: 'john@n8n.io', - _firstName: 'John', - _lastName: 'Doe', - globalRole: 'some-role', - }, - invitee: { - userId: '456', - _email: 'jane@n8n.io', - _firstName: 'Jane', - _lastName: 'Doe', - globalRole: 'some-other-role', - }, - }, - }); - }); -}); diff --git a/packages/cli/src/eventbus/audit-event-relay.service.ts b/packages/cli/src/eventbus/audit-event-relay.service.ts deleted file mode 100644 index 9c73494520dbe..0000000000000 --- a/packages/cli/src/eventbus/audit-event-relay.service.ts +++ /dev/null @@ -1,368 +0,0 @@ -import { Service } from 'typedi'; -import { MessageEventBus } from './MessageEventBus/MessageEventBus'; -import { Redactable } from '@/decorators/Redactable'; -import { EventService } from './event.service'; -import type { Event } from './event.types'; -import type { IWorkflowBase } from 'n8n-workflow'; - -@Service() -export class AuditEventRelay { - constructor( - private readonly eventService: EventService, - private readonly eventBus: MessageEventBus, - ) {} - - init() { - this.setupHandlers(); - } - - private setupHandlers() { - this.eventService.on('workflow-created', (event) => this.workflowCreated(event)); - this.eventService.on('workflow-deleted', (event) => this.workflowDeleted(event)); - this.eventService.on('workflow-saved', (event) => this.workflowSaved(event)); - this.eventService.on('workflow-pre-execute', (event) => this.workflowPreExecute(event)); - this.eventService.on('workflow-post-execute', (event) => this.workflowPostExecute(event)); - this.eventService.on('node-pre-execute', (event) => this.nodePreExecute(event)); - this.eventService.on('node-post-execute', (event) => this.nodePostExecute(event)); - this.eventService.on('user-deleted', (event) => this.userDeleted(event)); - this.eventService.on('user-invited', (event) => this.userInvited(event)); - this.eventService.on('user-reinvited', (event) => this.userReinvited(event)); - this.eventService.on('user-updated', (event) => this.userUpdated(event)); - this.eventService.on('user-signed-up', (event) => this.userSignedUp(event)); - this.eventService.on('user-logged-in', (event) => this.userLoggedIn(event)); - this.eventService.on('user-login-failed', (event) => this.userLoginFailed(event)); - this.eventService.on('user-invite-email-click', (event) => this.userInviteEmailClick(event)); - this.eventService.on('user-password-reset-email-click', (event) => - this.userPasswordResetEmailClick(event), - ); - this.eventService.on('user-password-reset-request-click', (event) => - this.userPasswordResetRequestClick(event), - ); - this.eventService.on('public-api-key-created', (event) => this.publicApiKeyCreated(event)); - this.eventService.on('public-api-key-deleted', (event) => this.publicApiKeyDeleted(event)); - this.eventService.on('email-failed', (event) => this.emailFailed(event)); - this.eventService.on('credentials-created', (event) => this.credentialsCreated(event)); - this.eventService.on('credentials-deleted', (event) => this.credentialsDeleted(event)); - this.eventService.on('credentials-shared', (event) => this.credentialsShared(event)); - this.eventService.on('credentials-updated', (event) => this.credentialsUpdated(event)); - this.eventService.on('credentials-deleted', (event) => this.credentialsDeleted(event)); - this.eventService.on('community-package-installed', (event) => - this.communityPackageInstalled(event), - ); - this.eventService.on('community-package-updated', (event) => - this.communityPackageUpdated(event), - ); - this.eventService.on('community-package-deleted', (event) => - this.communityPackageDeleted(event), - ); - this.eventService.on('execution-throttled', (event) => this.executionThrottled(event)); - this.eventService.on('execution-started-during-bootup', (event) => - this.executionStartedDuringBootup(event), - ); - } - - /** - * Workflow - */ - - @Redactable() - private workflowCreated({ user, workflow }: Event['workflow-created']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.workflow.created', - payload: { - ...user, - workflowId: workflow.id, - workflowName: workflow.name, - }, - }); - } - - @Redactable() - private workflowDeleted({ user, workflowId }: Event['workflow-deleted']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.workflow.deleted', - payload: { ...user, workflowId }, - }); - } - - @Redactable() - private workflowSaved({ user, workflowId, workflowName }: Event['workflow-saved']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.workflow.updated', - payload: { - ...user, - workflowId, - workflowName, - }, - }); - } - - private workflowPreExecute({ data, executionId }: Event['workflow-pre-execute']) { - const payload = - 'executionData' in data - ? { - executionId, - userId: data.userId, - workflowId: data.workflowData.id, - isManual: data.executionMode === 'manual', - workflowName: data.workflowData.name, - } - : { - executionId, - userId: undefined, - workflowId: (data as IWorkflowBase).id, - isManual: false, - workflowName: (data as IWorkflowBase).name, - }; - - void this.eventBus.sendWorkflowEvent({ - eventName: 'n8n.workflow.started', - payload, - }); - } - - private workflowPostExecute(event: Event['workflow-post-execute']) { - void this.eventBus.sendWorkflowEvent({ - eventName: 'n8n.workflow.success', - payload: event, - }); - } - - /** - * Node - */ - - private nodePreExecute({ workflow, executionId, nodeName }: Event['node-pre-execute']) { - void this.eventBus.sendNodeEvent({ - eventName: 'n8n.node.started', - payload: { - workflowId: workflow.id, - workflowName: workflow.name, - executionId, - nodeType: workflow.nodes.find((n) => n.name === nodeName)?.type, - nodeName, - }, - }); - } - - private nodePostExecute({ workflow, executionId, nodeName }: Event['node-post-execute']) { - void this.eventBus.sendNodeEvent({ - eventName: 'n8n.node.finished', - payload: { - workflowId: workflow.id, - workflowName: workflow.name, - executionId, - nodeType: workflow.nodes.find((n) => n.name === nodeName)?.type, - nodeName, - }, - }); - } - - /** - * User - */ - - @Redactable() - private userDeleted({ user }: Event['user-deleted']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.deleted', - payload: user, - }); - } - - @Redactable() - private userInvited({ user, targetUserId }: Event['user-invited']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.invited', - payload: { ...user, targetUserId }, - }); - } - - @Redactable() - private userReinvited({ user, targetUserId }: Event['user-reinvited']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.reinvited', - payload: { ...user, targetUserId }, - }); - } - - @Redactable() - private userUpdated({ user, fieldsChanged }: Event['user-updated']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.updated', - payload: { ...user, fieldsChanged }, - }); - } - - /** - * Auth - */ - - @Redactable() - private userSignedUp({ user }: Event['user-signed-up']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.signedup', - payload: user, - }); - } - - @Redactable() - private userLoggedIn({ user, authenticationMethod }: Event['user-logged-in']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.login.success', - payload: { ...user, authenticationMethod }, - }); - } - - private userLoginFailed( - event: Event['user-login-failed'] /* exception: no `UserLike` to redact */, - ) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.login.failed', - payload: event, - }); - } - - /** - * Click - */ - - @Redactable('inviter') - @Redactable('invitee') - private userInviteEmailClick(event: Event['user-invite-email-click']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.invitation.accepted', - payload: event, - }); - } - - @Redactable() - private userPasswordResetEmailClick({ user }: Event['user-password-reset-email-click']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.reset', - payload: user, - }); - } - - @Redactable() - private userPasswordResetRequestClick({ user }: Event['user-password-reset-request-click']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.reset.requested', - payload: user, - }); - } - - /** - * API key - */ - - @Redactable() - private publicApiKeyCreated({ user }: Event['public-api-key-created']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.api.created', - payload: user, - }); - } - - @Redactable() - private publicApiKeyDeleted({ user }: Event['public-api-key-deleted']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.api.deleted', - payload: user, - }); - } - - /** - * Emailing - */ - - @Redactable() - private emailFailed({ user, messageType }: Event['email-failed']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.email.failed', - payload: { ...user, messageType }, - }); - } - - /** - * Credentials - */ - - @Redactable() - private credentialsCreated({ user, ...rest }: Event['credentials-created']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.credentials.created', - payload: { ...user, ...rest }, - }); - } - - @Redactable() - private credentialsDeleted({ user, ...rest }: Event['credentials-deleted']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.credentials.deleted', - payload: { ...user, ...rest }, - }); - } - - @Redactable() - private credentialsShared({ user, ...rest }: Event['credentials-shared']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.credentials.shared', - payload: { ...user, ...rest }, - }); - } - - @Redactable() - private credentialsUpdated({ user, ...rest }: Event['credentials-updated']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.user.credentials.updated', - payload: { ...user, ...rest }, - }); - } - - /** - * Community package - */ - - @Redactable() - private communityPackageInstalled({ user, ...rest }: Event['community-package-installed']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.package.installed', - payload: { ...user, ...rest }, - }); - } - - @Redactable() - private communityPackageUpdated({ user, ...rest }: Event['community-package-updated']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.package.updated', - payload: { ...user, ...rest }, - }); - } - - @Redactable() - private communityPackageDeleted({ user, ...rest }: Event['community-package-deleted']) { - void this.eventBus.sendAuditEvent({ - eventName: 'n8n.audit.package.deleted', - payload: { ...user, ...rest }, - }); - } - - /** - * Execution - */ - - private executionThrottled({ executionId }: Event['execution-throttled']) { - void this.eventBus.sendExecutionEvent({ - eventName: 'n8n.execution.throttled', - payload: { executionId }, - }); - } - - private executionStartedDuringBootup({ executionId }: Event['execution-started-during-bootup']) { - void this.eventBus.sendExecutionEvent({ - eventName: 'n8n.execution.started-during-bootup', - payload: { executionId }, - }); - } -} diff --git a/packages/cli/src/eventbus/event.service.ts b/packages/cli/src/eventbus/event.service.ts deleted file mode 100644 index 2df51af22cad1..0000000000000 --- a/packages/cli/src/eventbus/event.service.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { EventEmitter } from 'node:events'; -import { Service } from 'typedi'; -import type { Event } from './event.types'; - -@Service() -export class EventService extends EventEmitter { - emit(eventName: K, arg?: Event[K]) { - super.emit(eventName, arg); - return true; - } - - on(eventName: K, handler: (arg: Event[K]) => void) { - super.on(eventName, handler); - return this; - } -} diff --git a/packages/cli/src/events/__tests__/log-streaming-event-relay.test.ts b/packages/cli/src/events/__tests__/log-streaming-event-relay.test.ts new file mode 100644 index 0000000000000..ebd56ee514c7e --- /dev/null +++ b/packages/cli/src/events/__tests__/log-streaming-event-relay.test.ts @@ -0,0 +1,671 @@ +import { mock } from 'jest-mock-extended'; +import { LogStreamingEventRelay } from '@/events/log-streaming-event-relay'; +import { EventService } from '@/events/event.service'; +import type { INode, IRun, IWorkflowBase } from 'n8n-workflow'; +import type { IWorkflowDb } from '@/Interfaces'; +import type { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import type { RelayEventMap } from '@/events/relay-event-map'; + +describe('LogStreamingEventRelay', () => { + const eventBus = mock(); + const eventService = new EventService(); + new LogStreamingEventRelay(eventService, eventBus).init(); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('workflow events', () => { + it('should log on `workflow-created` event', () => { + const event: RelayEventMap['workflow-created'] = { + user: { + id: '123', + email: 'john@n8n.io', + firstName: 'John', + lastName: 'Doe', + role: 'owner', + }, + workflow: mock({ + id: 'wf123', + name: 'Test Workflow', + }), + publicApi: false, + projectId: 'proj123', + projectType: 'personal', + }; + + eventService.emit('workflow-created', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.workflow.created', + payload: { + userId: '123', + _email: 'john@n8n.io', + _firstName: 'John', + _lastName: 'Doe', + globalRole: 'owner', + workflowId: 'wf123', + workflowName: 'Test Workflow', + }, + }); + }); + + it('should log on `workflow-deleted` event', () => { + const event: RelayEventMap['workflow-deleted'] = { + user: { + id: '456', + email: 'jane@n8n.io', + firstName: 'Jane', + lastName: 'Smith', + role: 'user', + }, + workflowId: 'wf789', + publicApi: false, + }; + + eventService.emit('workflow-deleted', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.workflow.deleted', + payload: { + userId: '456', + _email: 'jane@n8n.io', + _firstName: 'Jane', + _lastName: 'Smith', + globalRole: 'user', + workflowId: 'wf789', + }, + }); + }); + + it('should log on `workflow-saved` event', () => { + const event: RelayEventMap['workflow-saved'] = { + user: { + id: '789', + email: 'alex@n8n.io', + firstName: 'Alex', + lastName: 'Johnson', + role: 'editor', + }, + workflow: mock({ id: 'wf101', name: 'Updated Workflow' }), + publicApi: false, + }; + + eventService.emit('workflow-saved', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.workflow.updated', + payload: { + userId: '789', + _email: 'alex@n8n.io', + _firstName: 'Alex', + _lastName: 'Johnson', + globalRole: 'editor', + workflowId: 'wf101', + workflowName: 'Updated Workflow', + }, + }); + }); + + it('should log on `workflow-pre-execute` event', () => { + const workflow = mock({ + id: 'wf202', + name: 'Test Workflow', + active: true, + nodes: [], + connections: {}, + staticData: undefined, + settings: {}, + }); + + const event: RelayEventMap['workflow-pre-execute'] = { + executionId: 'exec123', + data: workflow, + }; + + eventService.emit('workflow-pre-execute', event); + + expect(eventBus.sendWorkflowEvent).toHaveBeenCalledWith({ + eventName: 'n8n.workflow.started', + payload: { + executionId: 'exec123', + userId: undefined, + workflowId: 'wf202', + isManual: false, + workflowName: 'Test Workflow', + }, + }); + }); + + it('should log on `workflow-post-execute` for successful execution', () => { + const payload = mock({ + executionId: 'some-id', + userId: 'some-id', + workflow: mock({ id: 'some-id', name: 'some-name' }), + runData: mock({ status: 'success', mode: 'manual', data: { resultData: {} } }), + }); + + eventService.emit('workflow-post-execute', payload); + + const { runData: _, workflow: __, ...rest } = payload; + + expect(eventBus.sendWorkflowEvent).toHaveBeenCalledWith({ + eventName: 'n8n.workflow.success', + payload: { + ...rest, + success: true, + isManual: true, + workflowName: 'some-name', + workflowId: 'some-id', + }, + }); + }); + + it('should log on `workflow-post-execute` event for unsuccessful execution', () => { + const runData = mock({ + status: 'error', + mode: 'manual', + data: { + resultData: { + lastNodeExecuted: 'some-node', + // @ts-expect-error Partial mock + error: { + node: mock({ type: 'some-type' }), + message: 'some-message', + }, + errorMessage: 'some-message', + }, + }, + }) as unknown as IRun; + + const event = { + executionId: 'some-id', + userId: 'some-id', + workflow: mock({ id: 'some-id', name: 'some-name' }), + runData, + }; + + eventService.emit('workflow-post-execute', event); + + const { runData: _, workflow: __, ...rest } = event; + + expect(eventBus.sendWorkflowEvent).toHaveBeenCalledWith({ + eventName: 'n8n.workflow.failed', + payload: { + ...rest, + success: false, + isManual: true, + workflowName: 'some-name', + workflowId: 'some-id', + lastNodeExecuted: 'some-node', + errorNodeType: 'some-type', + errorMessage: 'some-message', + }, + }); + }); + }); + + describe('user events', () => { + it('should log on `user-updated` event', () => { + const event: RelayEventMap['user-updated'] = { + user: { + id: 'user456', + email: 'updated@example.com', + firstName: 'Updated', + lastName: 'User', + role: 'global:member', + }, + fieldsChanged: ['firstName', 'lastName', 'password'], + }; + + eventService.emit('user-updated', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.user.updated', + payload: { + userId: 'user456', + _email: 'updated@example.com', + _firstName: 'Updated', + _lastName: 'User', + globalRole: 'global:member', + fieldsChanged: ['firstName', 'lastName', 'password'], + }, + }); + }); + + it('should log on `user-deleted` event', () => { + const event: RelayEventMap['user-deleted'] = { + user: { + id: '123', + email: 'john@n8n.io', + firstName: 'John', + lastName: 'Doe', + role: 'some-role', + }, + targetUserOldStatus: 'active', + publicApi: false, + migrationStrategy: 'transfer_data', + targetUserId: '456', + migrationUserId: '789', + }; + + eventService.emit('user-deleted', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.user.deleted', + payload: { + userId: '123', + _email: 'john@n8n.io', + _firstName: 'John', + _lastName: 'Doe', + globalRole: 'some-role', + }, + }); + }); + }); + + describe('click events', () => { + it('should log on `user-password-reset-request-click` event', () => { + const event: RelayEventMap['user-password-reset-request-click'] = { + user: { + id: 'user101', + email: 'user101@example.com', + firstName: 'John', + lastName: 'Doe', + role: 'global:member', + }, + }; + + eventService.emit('user-password-reset-request-click', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.user.reset.requested', + payload: { + userId: 'user101', + _email: 'user101@example.com', + _firstName: 'John', + _lastName: 'Doe', + globalRole: 'global:member', + }, + }); + }); + + it('should log on `user-invite-email-click` event', () => { + const event: RelayEventMap['user-invite-email-click'] = { + inviter: { + id: '123', + email: 'john@n8n.io', + firstName: 'John', + lastName: 'Doe', + role: 'some-role', + }, + invitee: { + id: '456', + email: 'jane@n8n.io', + firstName: 'Jane', + lastName: 'Doe', + role: 'some-other-role', + }, + }; + + eventService.emit('user-invite-email-click', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.user.invitation.accepted', + payload: { + inviter: { + userId: '123', + _email: 'john@n8n.io', + _firstName: 'John', + _lastName: 'Doe', + globalRole: 'some-role', + }, + invitee: { + userId: '456', + _email: 'jane@n8n.io', + _firstName: 'Jane', + _lastName: 'Doe', + globalRole: 'some-other-role', + }, + }, + }); + }); + }); + + describe('node events', () => { + it('should log on `node-pre-execute` event', () => { + const workflow = mock({ + id: 'wf303', + name: 'Test Workflow with Nodes', + active: true, + nodes: [ + { + id: 'node1', + name: 'Start Node', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [100, 200], + }, + { + id: 'node2', + name: 'HTTP Request', + type: 'n8n-nodes-base.httpRequest', + typeVersion: 1, + position: [300, 200], + }, + ], + connections: {}, + settings: {}, + }); + + const event: RelayEventMap['node-pre-execute'] = { + executionId: 'exec456', + nodeName: 'HTTP Request', + workflow, + }; + + eventService.emit('node-pre-execute', event); + + expect(eventBus.sendNodeEvent).toHaveBeenCalledWith({ + eventName: 'n8n.node.started', + payload: { + executionId: 'exec456', + nodeName: 'HTTP Request', + workflowId: 'wf303', + workflowName: 'Test Workflow with Nodes', + nodeType: 'n8n-nodes-base.httpRequest', + }, + }); + }); + + it('should log on `node-post-execute` event', () => { + const workflow = mock({ + id: 'wf404', + name: 'Test Workflow with Completed Node', + active: true, + nodes: [ + { + id: 'node1', + name: 'Start Node', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [100, 200], + }, + { + id: 'node2', + name: 'HTTP Response', + type: 'n8n-nodes-base.httpResponse', + typeVersion: 1, + position: [300, 200], + }, + ], + connections: {}, + settings: {}, + }); + + const event: RelayEventMap['node-post-execute'] = { + executionId: 'exec789', + nodeName: 'HTTP Response', + workflow, + }; + + eventService.emit('node-post-execute', event); + + expect(eventBus.sendNodeEvent).toHaveBeenCalledWith({ + eventName: 'n8n.node.finished', + payload: { + executionId: 'exec789', + nodeName: 'HTTP Response', + workflowId: 'wf404', + workflowName: 'Test Workflow with Completed Node', + nodeType: 'n8n-nodes-base.httpResponse', + }, + }); + }); + }); + + describe('credentials events', () => { + it('should log on `credentials-shared` event', () => { + const event: RelayEventMap['credentials-shared'] = { + user: { + id: 'user123', + email: 'sharer@example.com', + firstName: 'Alice', + lastName: 'Sharer', + role: 'global:owner', + }, + credentialId: 'cred789', + credentialType: 'githubApi', + userIdSharer: 'user123', + userIdsShareesAdded: ['user456', 'user789'], + shareesRemoved: null, + }; + + eventService.emit('credentials-shared', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.user.credentials.shared', + payload: { + userId: 'user123', + _email: 'sharer@example.com', + _firstName: 'Alice', + _lastName: 'Sharer', + globalRole: 'global:owner', + credentialId: 'cred789', + credentialType: 'githubApi', + userIdSharer: 'user123', + userIdsShareesAdded: ['user456', 'user789'], + shareesRemoved: null, + }, + }); + }); + + it('should log on `credentials-created` event', () => { + const event: RelayEventMap['credentials-created'] = { + user: { + id: 'user123', + email: 'user@example.com', + firstName: 'Test', + lastName: 'User', + role: 'global:owner', + }, + credentialType: 'githubApi', + credentialId: 'cred456', + publicApi: false, + projectId: 'proj789', + projectType: 'Personal', + }; + + eventService.emit('credentials-created', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.user.credentials.created', + payload: { + userId: 'user123', + _email: 'user@example.com', + _firstName: 'Test', + _lastName: 'User', + globalRole: 'global:owner', + credentialType: 'githubApi', + credentialId: 'cred456', + publicApi: false, + projectId: 'proj789', + projectType: 'Personal', + }, + }); + }); + }); + + describe('auth events', () => { + it('should log on `user-login-failed` event', () => { + const event: RelayEventMap['user-login-failed'] = { + userEmail: 'user@example.com', + authenticationMethod: 'email', + reason: 'Invalid password', + }; + + eventService.emit('user-login-failed', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.user.login.failed', + payload: { + userEmail: 'user@example.com', + authenticationMethod: 'email', + reason: 'Invalid password', + }, + }); + }); + }); + + describe('community package events', () => { + it('should log on `community-package-updated` event', () => { + const event: RelayEventMap['community-package-updated'] = { + user: { + id: 'user202', + email: 'packageupdater@example.com', + firstName: 'Package', + lastName: 'Updater', + role: 'global:admin', + }, + packageName: 'n8n-nodes-awesome-package', + packageVersionCurrent: '1.0.0', + packageVersionNew: '1.1.0', + packageNodeNames: ['AwesomeNode1', 'AwesomeNode2'], + packageAuthor: 'Jane Doe', + packageAuthorEmail: 'jane@example.com', + }; + + eventService.emit('community-package-updated', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.package.updated', + payload: { + userId: 'user202', + _email: 'packageupdater@example.com', + _firstName: 'Package', + _lastName: 'Updater', + globalRole: 'global:admin', + packageName: 'n8n-nodes-awesome-package', + packageVersionCurrent: '1.0.0', + packageVersionNew: '1.1.0', + packageNodeNames: ['AwesomeNode1', 'AwesomeNode2'], + packageAuthor: 'Jane Doe', + packageAuthorEmail: 'jane@example.com', + }, + }); + }); + + it('should log on `community-package-installed` event', () => { + const event: RelayEventMap['community-package-installed'] = { + user: { + id: 'user789', + email: 'admin@example.com', + firstName: 'Admin', + lastName: 'User', + role: 'global:admin', + }, + inputString: 'n8n-nodes-custom-package', + packageName: 'n8n-nodes-custom-package', + success: true, + packageVersion: '1.0.0', + packageNodeNames: ['CustomNode1', 'CustomNode2'], + packageAuthor: 'John Doe', + packageAuthorEmail: 'john@example.com', + }; + + eventService.emit('community-package-installed', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.package.installed', + payload: { + userId: 'user789', + _email: 'admin@example.com', + _firstName: 'Admin', + _lastName: 'User', + globalRole: 'global:admin', + inputString: 'n8n-nodes-custom-package', + packageName: 'n8n-nodes-custom-package', + success: true, + packageVersion: '1.0.0', + packageNodeNames: ['CustomNode1', 'CustomNode2'], + packageAuthor: 'John Doe', + packageAuthorEmail: 'john@example.com', + }, + }); + }); + }); + + describe('email events', () => { + it('should log on `email-failed` event', () => { + const event: RelayEventMap['email-failed'] = { + user: { + id: 'user789', + email: 'recipient@example.com', + firstName: 'Failed', + lastName: 'Recipient', + role: 'global:member', + }, + messageType: 'New user invite', + }; + + eventService.emit('email-failed', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.user.email.failed', + payload: { + userId: 'user789', + _email: 'recipient@example.com', + _firstName: 'Failed', + _lastName: 'Recipient', + globalRole: 'global:member', + messageType: 'New user invite', + }, + }); + }); + }); + + describe('public API events', () => { + it('should log on `public-api-key-created` event', () => { + const event: RelayEventMap['public-api-key-created'] = { + user: { + id: 'user101', + email: 'apiuser@example.com', + firstName: 'API', + lastName: 'User', + role: 'global:owner', + }, + publicApi: true, + }; + + eventService.emit('public-api-key-created', event); + + expect(eventBus.sendAuditEvent).toHaveBeenCalledWith({ + eventName: 'n8n.audit.user.api.created', + payload: { + userId: 'user101', + _email: 'apiuser@example.com', + _firstName: 'API', + _lastName: 'User', + globalRole: 'global:owner', + }, + }); + }); + }); + + describe('execution events', () => { + it('should log on `execution-throttled` event', () => { + const event: RelayEventMap['execution-throttled'] = { + executionId: 'exec123456', + }; + + eventService.emit('execution-throttled', event); + + expect(eventBus.sendExecutionEvent).toHaveBeenCalledWith({ + eventName: 'n8n.execution.throttled', + payload: { + executionId: 'exec123456', + }, + }); + }); + }); +}); diff --git a/packages/cli/src/events/event-relay.ts b/packages/cli/src/events/event-relay.ts new file mode 100644 index 0000000000000..1a8a17b8930f2 --- /dev/null +++ b/packages/cli/src/events/event-relay.ts @@ -0,0 +1,20 @@ +import { EventService } from './event.service'; +import { Service } from 'typedi'; +import type { RelayEventMap } from '@/events/relay-event-map'; + +@Service() +export class EventRelay { + constructor(readonly eventService: EventService) {} + + protected setupListeners(map: { + [EventName in EventNames]?: (event: RelayEventMap[EventName]) => void | Promise; + }) { + for (const [eventName, handler] of Object.entries(map) as Array< + [EventNames, (event: RelayEventMap[EventNames]) => void | Promise] + >) { + this.eventService.on(eventName, async (event) => { + await handler(event); + }); + } + } +} diff --git a/packages/cli/src/events/event.service.ts b/packages/cli/src/events/event.service.ts new file mode 100644 index 0000000000000..6744103a07799 --- /dev/null +++ b/packages/cli/src/events/event.service.ts @@ -0,0 +1,6 @@ +import { Service } from 'typedi'; +import { TypedEmitter } from '@/TypedEmitter'; +import type { RelayEventMap } from './relay-event-map'; + +@Service() +export class EventService extends TypedEmitter {} diff --git a/packages/cli/src/events/log-streaming-event-relay.ts b/packages/cli/src/events/log-streaming-event-relay.ts new file mode 100644 index 0000000000000..85d5a8cb8fb3c --- /dev/null +++ b/packages/cli/src/events/log-streaming-event-relay.ts @@ -0,0 +1,390 @@ +import { Service } from 'typedi'; +import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { Redactable } from '@/decorators/Redactable'; +import { EventRelay } from '@/events/event-relay'; +import type { RelayEventMap } from '@/events/relay-event-map'; +import type { IWorkflowBase } from 'n8n-workflow'; +import { EventService } from './event.service'; + +@Service() +export class LogStreamingEventRelay extends EventRelay { + constructor( + readonly eventService: EventService, + private readonly eventBus: MessageEventBus, + ) { + super(eventService); + } + + init() { + this.setupListeners({ + 'workflow-created': (event) => this.workflowCreated(event), + 'workflow-deleted': (event) => this.workflowDeleted(event), + 'workflow-saved': (event) => this.workflowSaved(event), + 'workflow-pre-execute': (event) => this.workflowPreExecute(event), + 'workflow-post-execute': (event) => this.workflowPostExecute(event), + 'node-pre-execute': (event) => this.nodePreExecute(event), + 'node-post-execute': (event) => this.nodePostExecute(event), + 'user-deleted': (event) => this.userDeleted(event), + 'user-invited': (event) => this.userInvited(event), + 'user-reinvited': (event) => this.userReinvited(event), + 'user-updated': (event) => this.userUpdated(event), + 'user-signed-up': (event) => this.userSignedUp(event), + 'user-logged-in': (event) => this.userLoggedIn(event), + 'user-login-failed': (event) => this.userLoginFailed(event), + 'user-invite-email-click': (event) => this.userInviteEmailClick(event), + 'user-password-reset-email-click': (event) => this.userPasswordResetEmailClick(event), + 'user-password-reset-request-click': (event) => this.userPasswordResetRequestClick(event), + 'public-api-key-created': (event) => this.publicApiKeyCreated(event), + 'public-api-key-deleted': (event) => this.publicApiKeyDeleted(event), + 'email-failed': (event) => this.emailFailed(event), + 'credentials-created': (event) => this.credentialsCreated(event), + 'credentials-deleted': (event) => this.credentialsDeleted(event), + 'credentials-shared': (event) => this.credentialsShared(event), + 'credentials-updated': (event) => this.credentialsUpdated(event), + 'community-package-installed': (event) => this.communityPackageInstalled(event), + 'community-package-updated': (event) => this.communityPackageUpdated(event), + 'community-package-deleted': (event) => this.communityPackageDeleted(event), + 'execution-throttled': (event) => this.executionThrottled(event), + 'execution-started-during-bootup': (event) => this.executionStartedDuringBootup(event), + }); + } + + // #region Workflow + + @Redactable() + private workflowCreated({ user, workflow }: RelayEventMap['workflow-created']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.workflow.created', + payload: { + ...user, + workflowId: workflow.id, + workflowName: workflow.name, + }, + }); + } + + @Redactable() + private workflowDeleted({ user, workflowId }: RelayEventMap['workflow-deleted']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.workflow.deleted', + payload: { ...user, workflowId }, + }); + } + + @Redactable() + private workflowSaved({ user, workflow }: RelayEventMap['workflow-saved']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.workflow.updated', + payload: { + ...user, + workflowId: workflow.id, + workflowName: workflow.name, + }, + }); + } + + private workflowPreExecute({ data, executionId }: RelayEventMap['workflow-pre-execute']) { + const payload = + 'executionData' in data + ? { + executionId, + userId: data.userId, + workflowId: data.workflowData.id, + isManual: data.executionMode === 'manual', + workflowName: data.workflowData.name, + } + : { + executionId, + userId: undefined, + workflowId: (data as IWorkflowBase).id, + isManual: false, + workflowName: (data as IWorkflowBase).name, + }; + + void this.eventBus.sendWorkflowEvent({ + eventName: 'n8n.workflow.started', + payload, + }); + } + + private workflowPostExecute(event: RelayEventMap['workflow-post-execute']) { + const { runData, workflow, ...rest } = event; + + const payload = { + ...rest, + success: runData?.status === 'success', + isManual: runData?.mode === 'manual', + workflowId: workflow.id, + workflowName: workflow.name, + }; + + if (payload.success) { + void this.eventBus.sendWorkflowEvent({ + eventName: 'n8n.workflow.success', + payload, + }); + + return; + } + + void this.eventBus.sendWorkflowEvent({ + eventName: 'n8n.workflow.failed', + payload: { + ...payload, + lastNodeExecuted: runData?.data.resultData.lastNodeExecuted, + errorNodeType: + runData?.data.resultData.error && 'node' in runData?.data.resultData.error + ? runData?.data.resultData.error.node?.type + : undefined, + errorMessage: runData?.data.resultData.error?.message.toString(), + }, + }); + } + + // #endregion + + // #region Node + + private nodePreExecute({ workflow, executionId, nodeName }: RelayEventMap['node-pre-execute']) { + void this.eventBus.sendNodeEvent({ + eventName: 'n8n.node.started', + payload: { + workflowId: workflow.id, + workflowName: workflow.name, + executionId, + nodeType: workflow.nodes.find((n) => n.name === nodeName)?.type, + nodeName, + }, + }); + } + + private nodePostExecute({ workflow, executionId, nodeName }: RelayEventMap['node-post-execute']) { + void this.eventBus.sendNodeEvent({ + eventName: 'n8n.node.finished', + payload: { + workflowId: workflow.id, + workflowName: workflow.name, + executionId, + nodeType: workflow.nodes.find((n) => n.name === nodeName)?.type, + nodeName, + }, + }); + } + + // #endregion + + // #region User + + @Redactable() + private userDeleted({ user }: RelayEventMap['user-deleted']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.deleted', + payload: user, + }); + } + + @Redactable() + private userInvited({ user, targetUserId }: RelayEventMap['user-invited']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.invited', + payload: { ...user, targetUserId }, + }); + } + + @Redactable() + private userReinvited({ user, targetUserId }: RelayEventMap['user-reinvited']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.reinvited', + payload: { ...user, targetUserId }, + }); + } + + @Redactable() + private userUpdated({ user, fieldsChanged }: RelayEventMap['user-updated']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.updated', + payload: { ...user, fieldsChanged }, + }); + } + + // #endregion + + // #region Auth + + @Redactable() + private userSignedUp({ user }: RelayEventMap['user-signed-up']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.signedup', + payload: user, + }); + } + + @Redactable() + private userLoggedIn({ user, authenticationMethod }: RelayEventMap['user-logged-in']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.login.success', + payload: { ...user, authenticationMethod }, + }); + } + + private userLoginFailed( + event: RelayEventMap['user-login-failed'] /* exception: no `UserLike` to redact */, + ) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.login.failed', + payload: event, + }); + } + + // #endregion + + // #region Click + + @Redactable('inviter') + @Redactable('invitee') + private userInviteEmailClick(event: RelayEventMap['user-invite-email-click']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.invitation.accepted', + payload: event, + }); + } + + @Redactable() + private userPasswordResetEmailClick({ user }: RelayEventMap['user-password-reset-email-click']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.reset', + payload: user, + }); + } + + @Redactable() + private userPasswordResetRequestClick({ + user, + }: RelayEventMap['user-password-reset-request-click']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.reset.requested', + payload: user, + }); + } + + // #endregion + + // #region Public API + + @Redactable() + private publicApiKeyCreated({ user }: RelayEventMap['public-api-key-created']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.api.created', + payload: user, + }); + } + + @Redactable() + private publicApiKeyDeleted({ user }: RelayEventMap['public-api-key-deleted']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.api.deleted', + payload: user, + }); + } + + // #endregion + + // #region Email + + @Redactable() + private emailFailed({ user, messageType }: RelayEventMap['email-failed']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.email.failed', + payload: { ...user, messageType }, + }); + } + + // #endregion + + // #region Credentials + + @Redactable() + private credentialsCreated({ user, ...rest }: RelayEventMap['credentials-created']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.credentials.created', + payload: { ...user, ...rest }, + }); + } + + @Redactable() + private credentialsDeleted({ user, ...rest }: RelayEventMap['credentials-deleted']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.credentials.deleted', + payload: { ...user, ...rest }, + }); + } + + @Redactable() + private credentialsShared({ user, ...rest }: RelayEventMap['credentials-shared']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.credentials.shared', + payload: { ...user, ...rest }, + }); + } + + @Redactable() + private credentialsUpdated({ user, ...rest }: RelayEventMap['credentials-updated']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.user.credentials.updated', + payload: { ...user, ...rest }, + }); + } + + // #endregion + + // #region Community package + + @Redactable() + private communityPackageInstalled({ + user, + ...rest + }: RelayEventMap['community-package-installed']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.package.installed', + payload: { ...user, ...rest }, + }); + } + + @Redactable() + private communityPackageUpdated({ user, ...rest }: RelayEventMap['community-package-updated']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.package.updated', + payload: { ...user, ...rest }, + }); + } + + @Redactable() + private communityPackageDeleted({ user, ...rest }: RelayEventMap['community-package-deleted']) { + void this.eventBus.sendAuditEvent({ + eventName: 'n8n.audit.package.deleted', + payload: { ...user, ...rest }, + }); + } + + // #endregion + + // #region Execution + + private executionThrottled({ executionId }: RelayEventMap['execution-throttled']) { + void this.eventBus.sendExecutionEvent({ + eventName: 'n8n.execution.throttled', + payload: { executionId }, + }); + } + + private executionStartedDuringBootup({ + executionId, + }: RelayEventMap['execution-started-during-bootup']) { + void this.eventBus.sendExecutionEvent({ + eventName: 'n8n.execution.started-during-bootup', + payload: { executionId }, + }); + } + + // #endregion +} diff --git a/packages/cli/src/eventbus/event.types.ts b/packages/cli/src/events/relay-event-map.ts similarity index 71% rename from packages/cli/src/eventbus/event.types.ts rename to packages/cli/src/events/relay-event-map.ts index 225f9aca8c873..0d329508b413e 100644 --- a/packages/cli/src/eventbus/event.types.ts +++ b/packages/cli/src/events/relay-event-map.ts @@ -1,7 +1,8 @@ -import type { AuthenticationMethod, IWorkflowBase } from 'n8n-workflow'; -import type { IWorkflowExecutionDataProcess } from '@/Interfaces'; +import type { AuthenticationMethod, IRun, IWorkflowBase } from 'n8n-workflow'; +import type { IWorkflowDb, IWorkflowExecutionDataProcess } from '@/Interfaces'; import type { ProjectRole } from '@/databases/entities/ProjectRelation'; import type { GlobalRole } from '@/databases/entities/User'; +import type { AuthProviderType } from '@/databases/entities/AuthIdentity'; export type UserLike = { id: string; @@ -11,26 +12,33 @@ export type UserLike = { role: string; }; -/** - * Events sent by `EventService` and forwarded by relays, e.g. `AuditEventRelay` and `TelemetryEventRelay`. - */ -export type Event = { +export type RelayEventMap = { + // #region Server + 'server-started': {}; + // #endregion + + // #region Workflow + 'workflow-created': { user: UserLike; workflow: IWorkflowBase; + publicApi: boolean; + projectId: string; + projectType: string; }; 'workflow-deleted': { user: UserLike; workflowId: string; + publicApi: boolean; }; 'workflow-saved': { user: UserLike; - workflowId: string; - workflowName: string; + workflow: IWorkflowDb; + publicApi: boolean; }; 'workflow-pre-execute': { @@ -40,14 +48,15 @@ export type Event = { 'workflow-post-execute': { executionId: string; - success: boolean; userId?: string; - workflowId: string; - isManual: boolean; - workflowName: string; - metadata?: Record; + workflow: IWorkflowBase; + runData?: IRun; }; + // #endregion + + // #region Node + 'node-pre-execute': { executionId: string; workflow: IWorkflowBase; @@ -60,13 +69,30 @@ export type Event = { nodeName: string; }; + // #endregion + + // #region User + + 'user-submitted-personalization-survey': { + userId: string; + answers: Record; + }; + 'user-deleted': { user: UserLike; + publicApi: boolean; + targetUserOldStatus: 'active' | 'invited'; + migrationStrategy?: 'transfer_data' | 'delete_data'; + targetUserId?: string; + migrationUserId?: string; }; 'user-invited': { user: UserLike; targetUserId: string[]; + publicApi: boolean; + emailSent: boolean; + inviteeRole: string; }; 'user-reinvited': { @@ -81,6 +107,8 @@ export type Event = { 'user-signed-up': { user: UserLike; + userType: AuthProviderType; + wasDisabledLdapUser: boolean; }; 'user-logged-in': { @@ -94,6 +122,47 @@ export type Event = { reason?: string; }; + 'user-changed-role': { + userId: string; + targetUserId: string; + publicApi: boolean; + targetUserNewRole: string; + }; + + 'user-retrieved-user': { + userId: string; + publicApi: boolean; + }; + + 'user-retrieved-all-users': { + userId: string; + publicApi: boolean; + }; + + 'user-retrieved-execution': { + userId: string; + publicApi: boolean; + }; + + 'user-retrieved-all-executions': { + userId: string; + publicApi: boolean; + }; + + 'user-retrieved-workflow': { + userId: string; + publicApi: boolean; + }; + + 'user-retrieved-all-workflows': { + userId: string; + publicApi: boolean; + }; + + // #endregion + + // #region Click + 'user-invite-email-click': { inviter: UserLike; invitee: UserLike; @@ -107,6 +176,20 @@ export type Event = { user: UserLike; }; + // #endregion + + // #region Public API + + 'public-api-key-created': { + user: UserLike; + publicApi: boolean; + }; + + 'public-api-key-deleted': { + user: UserLike; + publicApi: boolean; + }; + 'public-api-invoked': { userId: string; path: string; @@ -114,6 +197,10 @@ export type Event = { apiVersion: string; }; + // #endregion + + // #region Email + 'email-failed': { user: UserLike; messageType: @@ -124,6 +211,10 @@ export type Event = { | 'Credentials shared'; }; + // #endregion + + // #region Credentials + 'credentials-created': { user: UserLike; credentialType: string; @@ -154,6 +245,10 @@ export type Event = { credentialId: string; }; + // #endregion + + // #region Community package + 'community-package-installed': { user: UserLike; inputString: string; @@ -185,6 +280,10 @@ export type Event = { packageAuthorEmail?: string; }; + // #endregion + + // #region Execution + 'execution-throttled': { executionId: string; }; @@ -193,6 +292,10 @@ export type Event = { executionId: string; }; + // #endregion + + // #region Project + 'team-project-updated': { userId: string; role: GlobalRole; @@ -216,6 +319,10 @@ export type Event = { role: GlobalRole; }; + // #endregion + + // #region Source control + 'source-control-settings-updated': { branchName: string; readOnlyInstance: boolean; @@ -253,12 +360,24 @@ export type Event = { variablesPushed: number; }; + // #endregion + + // #region License + 'license-renewal-attempted': { success: boolean; }; + // #endregion + + // #region Variable + 'variable-created': {}; + // #endregion + + // #region External secrets + 'external-secrets-provider-settings-saved': { userId?: string; vaultType: string; @@ -267,6 +386,10 @@ export type Event = { errorMessage?: string; }; + // #endregion + + // #region LDAP + 'ldap-general-sync-finished': { type: string; succeeded: boolean; @@ -297,17 +420,5 @@ export type Event = { userId: string; }; - /** - * Events listened to by more than one relay - */ - - 'public-api-key-created': { - user: UserLike; // audit and telemetry - publicApi: boolean; // telemetry only - }; - - 'public-api-key-deleted': { - user: UserLike; // audit and telemetry - publicApi: boolean; // telemetry only - }; + // #endregion }; diff --git a/packages/cli/src/events/telemetry-event-relay.ts b/packages/cli/src/events/telemetry-event-relay.ts new file mode 100644 index 0000000000000..bc7a19479e721 --- /dev/null +++ b/packages/cli/src/events/telemetry-event-relay.ts @@ -0,0 +1,885 @@ +import { Service } from 'typedi'; +import { EventService } from '@/events/event.service'; +import type { RelayEventMap } from '@/events/relay-event-map'; +import { Telemetry } from '../telemetry'; +import config from '@/config'; +import os from 'node:os'; +import { License } from '@/License'; +import { GlobalConfig } from '@n8n/config'; +import { N8N_VERSION } from '@/constants'; +import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; +import type { ExecutionStatus, INodesGraphResult, ITelemetryTrackProperties } from 'n8n-workflow'; +import { get as pslGet } from 'psl'; +import { TelemetryHelpers } from 'n8n-workflow'; +import { NodeTypes } from '@/NodeTypes'; +import { SharedWorkflowRepository } from '@/databases/repositories/sharedWorkflow.repository'; +import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository'; +import type { IExecutionTrackProperties } from '@/Interfaces'; +import { determineFinalExecutionStatus } from '@/executionLifecycleHooks/shared/sharedHookFunctions'; +import { EventRelay } from './event-relay'; +import { snakeCase } from 'change-case'; + +@Service() +export class TelemetryEventRelay extends EventRelay { + constructor( + readonly eventService: EventService, + private readonly telemetry: Telemetry, + private readonly license: License, + private readonly globalConfig: GlobalConfig, + private readonly workflowRepository: WorkflowRepository, + private readonly nodeTypes: NodeTypes, + private readonly sharedWorkflowRepository: SharedWorkflowRepository, + private readonly projectRelationRepository: ProjectRelationRepository, + ) { + super(eventService); + } + + async init() { + if (!config.getEnv('diagnostics.enabled')) return; + + await this.telemetry.init(); + + this.setupListeners({ + 'team-project-updated': (event) => this.teamProjectUpdated(event), + 'team-project-deleted': (event) => this.teamProjectDeleted(event), + 'team-project-created': (event) => this.teamProjectCreated(event), + 'source-control-settings-updated': (event) => this.sourceControlSettingsUpdated(event), + 'source-control-user-started-pull-ui': (event) => this.sourceControlUserStartedPullUi(event), + 'source-control-user-finished-pull-ui': (event) => + this.sourceControlUserFinishedPullUi(event), + 'source-control-user-pulled-api': (event) => this.sourceControlUserPulledApi(event), + 'source-control-user-started-push-ui': (event) => this.sourceControlUserStartedPushUi(event), + 'source-control-user-finished-push-ui': (event) => + this.sourceControlUserFinishedPushUi(event), + 'license-renewal-attempted': (event) => this.licenseRenewalAttempted(event), + 'variable-created': () => this.variableCreated(), + 'external-secrets-provider-settings-saved': (event) => + this.externalSecretsProviderSettingsSaved(event), + 'public-api-invoked': (event) => this.publicApiInvoked(event), + 'public-api-key-created': (event) => this.publicApiKeyCreated(event), + 'public-api-key-deleted': (event) => this.publicApiKeyDeleted(event), + 'community-package-installed': (event) => this.communityPackageInstalled(event), + 'community-package-updated': (event) => this.communityPackageUpdated(event), + 'community-package-deleted': (event) => this.communityPackageDeleted(event), + 'credentials-created': (event) => this.credentialsCreated(event), + 'credentials-shared': (event) => this.credentialsShared(event), + 'credentials-updated': (event) => this.credentialsUpdated(event), + 'credentials-deleted': (event) => this.credentialsDeleted(event), + 'ldap-general-sync-finished': (event) => this.ldapGeneralSyncFinished(event), + 'ldap-settings-updated': (event) => this.ldapSettingsUpdated(event), + 'ldap-login-sync-failed': (event) => this.ldapLoginSyncFailed(event), + 'login-failed-due-to-ldap-disabled': (event) => this.loginFailedDueToLdapDisabled(event), + 'workflow-created': (event) => this.workflowCreated(event), + 'workflow-deleted': (event) => this.workflowDeleted(event), + 'workflow-saved': async (event) => await this.workflowSaved(event), + 'server-started': async () => await this.serverStarted(), + 'workflow-post-execute': async (event) => await this.workflowPostExecute(event), + 'user-changed-role': (event) => this.userChangedRole(event), + 'user-retrieved-user': (event) => this.userRetrievedUser(event), + 'user-retrieved-all-users': (event) => this.userRetrievedAllUsers(event), + 'user-retrieved-execution': (event) => this.userRetrievedExecution(event), + 'user-retrieved-all-executions': (event) => this.userRetrievedAllExecutions(event), + 'user-retrieved-workflow': (event) => this.userRetrievedWorkflow(event), + 'user-retrieved-all-workflows': (event) => this.userRetrievedAllWorkflows(event), + 'user-updated': (event) => this.userUpdated(event), + 'user-deleted': (event) => this.userDeleted(event), + 'user-invited': (event) => this.userInvited(event), + 'user-signed-up': (event) => this.userSignedUp(event), + 'user-submitted-personalization-survey': (event) => + this.userSubmittedPersonalizationSurvey(event), + }); + } + + // #endregion + + // #region Team + + private teamProjectUpdated({ + userId, + role, + members, + projectId, + }: RelayEventMap['team-project-updated']) { + this.telemetry.track('Project settings updated', { + user_id: userId, + role, + // eslint-disable-next-line @typescript-eslint/no-shadow + members: members.map(({ userId: user_id, role }) => ({ user_id, role })), + project_id: projectId, + }); + } + + private teamProjectDeleted({ + userId, + role, + projectId, + removalType, + targetProjectId, + }: RelayEventMap['team-project-deleted']) { + this.telemetry.track('User deleted project', { + user_id: userId, + role, + project_id: projectId, + removal_type: removalType, + target_project_id: targetProjectId, + }); + } + + private teamProjectCreated({ userId, role }: RelayEventMap['team-project-created']) { + this.telemetry.track('User created project', { + user_id: userId, + role, + }); + } + + // #endregion + + // #region Source control + + private sourceControlSettingsUpdated({ + branchName, + readOnlyInstance, + repoType, + connected, + }: RelayEventMap['source-control-settings-updated']) { + this.telemetry.track('User updated source control settings', { + branch_name: branchName, + read_only_instance: readOnlyInstance, + repo_type: repoType, + connected, + }); + } + + private sourceControlUserStartedPullUi({ + workflowUpdates, + workflowConflicts, + credConflicts, + }: RelayEventMap['source-control-user-started-pull-ui']) { + this.telemetry.track('User started pull via UI', { + workflow_updates: workflowUpdates, + workflow_conflicts: workflowConflicts, + cred_conflicts: credConflicts, + }); + } + + private sourceControlUserFinishedPullUi({ + workflowUpdates, + }: RelayEventMap['source-control-user-finished-pull-ui']) { + this.telemetry.track('User finished pull via UI', { + workflow_updates: workflowUpdates, + }); + } + + private sourceControlUserPulledApi({ + workflowUpdates, + forced, + }: RelayEventMap['source-control-user-pulled-api']) { + this.telemetry.track('User pulled via API', { + workflow_updates: workflowUpdates, + forced, + }); + } + + private sourceControlUserStartedPushUi({ + workflowsEligible, + workflowsEligibleWithConflicts, + credsEligible, + credsEligibleWithConflicts, + variablesEligible, + }: RelayEventMap['source-control-user-started-push-ui']) { + this.telemetry.track('User started push via UI', { + workflows_eligible: workflowsEligible, + workflows_eligible_with_conflicts: workflowsEligibleWithConflicts, + creds_eligible: credsEligible, + creds_eligible_with_conflicts: credsEligibleWithConflicts, + variables_eligible: variablesEligible, + }); + } + + private sourceControlUserFinishedPushUi({ + workflowsEligible, + workflowsPushed, + credsPushed, + variablesPushed, + }: RelayEventMap['source-control-user-finished-push-ui']) { + this.telemetry.track('User finished push via UI', { + workflows_eligible: workflowsEligible, + workflows_pushed: workflowsPushed, + creds_pushed: credsPushed, + variables_pushed: variablesPushed, + }); + } + + // #endregion + + // #region License + + private licenseRenewalAttempted({ success }: RelayEventMap['license-renewal-attempted']) { + this.telemetry.track('Instance attempted to refresh license', { + success, + }); + } + + // #endregion + + // #region Variable + + private variableCreated() { + this.telemetry.track('User created variable'); + } + + // #endregion + + // #region External secrets + + private externalSecretsProviderSettingsSaved({ + userId, + vaultType, + isValid, + isNew, + errorMessage, + }: RelayEventMap['external-secrets-provider-settings-saved']) { + this.telemetry.track('User updated external secrets settings', { + user_id: userId, + vault_type: vaultType, + is_valid: isValid, + is_new: isNew, + error_message: errorMessage, + }); + } + + // #endregion + + // #region Public API + + private publicApiInvoked({ + userId, + path, + method, + apiVersion, + }: RelayEventMap['public-api-invoked']) { + this.telemetry.track('User invoked API', { + user_id: userId, + path, + method, + api_version: apiVersion, + }); + } + + private publicApiKeyCreated(event: RelayEventMap['public-api-key-created']) { + const { user, publicApi } = event; + + this.telemetry.track('API key created', { + user_id: user.id, + public_api: publicApi, + }); + } + + private publicApiKeyDeleted(event: RelayEventMap['public-api-key-deleted']) { + const { user, publicApi } = event; + + this.telemetry.track('API key deleted', { + user_id: user.id, + public_api: publicApi, + }); + } + + // #endregion + + // #region Community package + + private communityPackageInstalled({ + user, + inputString, + packageName, + success, + packageVersion, + packageNodeNames, + packageAuthor, + packageAuthorEmail, + failureReason, + }: RelayEventMap['community-package-installed']) { + this.telemetry.track('cnr package install finished', { + user_id: user.id, + input_string: inputString, + package_name: packageName, + success, + package_version: packageVersion, + package_node_names: packageNodeNames, + package_author: packageAuthor, + package_author_email: packageAuthorEmail, + failure_reason: failureReason, + }); + } + + private communityPackageUpdated({ + user, + packageName, + packageVersionCurrent, + packageVersionNew, + packageNodeNames, + packageAuthor, + packageAuthorEmail, + }: RelayEventMap['community-package-updated']) { + this.telemetry.track('cnr package updated', { + user_id: user.id, + package_name: packageName, + package_version_current: packageVersionCurrent, + package_version_new: packageVersionNew, + package_node_names: packageNodeNames, + package_author: packageAuthor, + package_author_email: packageAuthorEmail, + }); + } + + private communityPackageDeleted({ + user, + packageName, + packageVersion, + packageNodeNames, + packageAuthor, + packageAuthorEmail, + }: RelayEventMap['community-package-deleted']) { + this.telemetry.track('cnr package deleted', { + user_id: user.id, + package_name: packageName, + package_version: packageVersion, + package_node_names: packageNodeNames, + package_author: packageAuthor, + package_author_email: packageAuthorEmail, + }); + } + + // #endregion + + // #region Credentials + + private credentialsCreated({ + user, + credentialType, + credentialId, + projectId, + projectType, + }: RelayEventMap['credentials-created']) { + this.telemetry.track('User created credentials', { + user_id: user.id, + credential_type: credentialType, + credential_id: credentialId, + project_id: projectId, + project_type: projectType, + }); + } + + private credentialsShared({ + user, + credentialType, + credentialId, + userIdSharer, + userIdsShareesAdded, + shareesRemoved, + }: RelayEventMap['credentials-shared']) { + this.telemetry.track('User updated cred sharing', { + user_id: user.id, + credential_type: credentialType, + credential_id: credentialId, + user_id_sharer: userIdSharer, + user_ids_sharees_added: userIdsShareesAdded, + sharees_removed: shareesRemoved, + }); + } + + private credentialsUpdated({ + user, + credentialId, + credentialType, + }: RelayEventMap['credentials-updated']) { + this.telemetry.track('User updated credentials', { + user_id: user.id, + credential_type: credentialType, + credential_id: credentialId, + }); + } + + private credentialsDeleted({ + user, + credentialId, + credentialType, + }: RelayEventMap['credentials-deleted']) { + this.telemetry.track('User deleted credentials', { + user_id: user.id, + credential_type: credentialType, + credential_id: credentialId, + }); + } + + // #endregion + + // #region LDAP + + private ldapGeneralSyncFinished({ + type, + succeeded, + usersSynced, + error, + }: RelayEventMap['ldap-general-sync-finished']) { + this.telemetry.track('Ldap general sync finished', { + type, + succeeded, + users_synced: usersSynced, + error, + }); + } + + private ldapSettingsUpdated({ + userId, + loginIdAttribute, + firstNameAttribute, + lastNameAttribute, + emailAttribute, + ldapIdAttribute, + searchPageSize, + searchTimeout, + synchronizationEnabled, + synchronizationInterval, + loginLabel, + loginEnabled, + }: RelayEventMap['ldap-settings-updated']) { + this.telemetry.track('User updated Ldap settings', { + user_id: userId, + loginIdAttribute, + firstNameAttribute, + lastNameAttribute, + emailAttribute, + ldapIdAttribute, + searchPageSize, + searchTimeout, + synchronizationEnabled, + synchronizationInterval, + loginLabel, + loginEnabled, + }); + } + + private ldapLoginSyncFailed({ error }: RelayEventMap['ldap-login-sync-failed']) { + this.telemetry.track('Ldap login sync failed', { error }); + } + + private loginFailedDueToLdapDisabled({ + userId, + }: RelayEventMap['login-failed-due-to-ldap-disabled']) { + this.telemetry.track('User login failed since ldap disabled', { user_ud: userId }); + } + + // #endregion + + // #region Workflow + + private workflowCreated({ + user, + workflow, + publicApi, + projectId, + projectType, + }: RelayEventMap['workflow-created']) { + const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes); + + this.telemetry.track('User created workflow', { + user_id: user.id, + workflow_id: workflow.id, + node_graph_string: JSON.stringify(nodeGraph), + public_api: publicApi, + project_id: projectId, + project_type: projectType, + }); + } + + private workflowDeleted({ user, workflowId, publicApi }: RelayEventMap['workflow-deleted']) { + this.telemetry.track('User deleted workflow', { + user_id: user.id, + workflow_id: workflowId, + public_api: publicApi, + }); + } + + private async workflowSaved({ user, workflow, publicApi }: RelayEventMap['workflow-saved']) { + const isCloudDeployment = config.getEnv('deployment.type') === 'cloud'; + + const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes, { + isCloudDeployment, + }); + + let userRole: 'owner' | 'sharee' | 'member' | undefined = undefined; + const role = await this.sharedWorkflowRepository.findSharingRole(user.id, workflow.id); + if (role) { + userRole = role === 'workflow:owner' ? 'owner' : 'sharee'; + } else { + const workflowOwner = await this.sharedWorkflowRepository.getWorkflowOwningProject( + workflow.id, + ); + + if (workflowOwner) { + const projectRole = await this.projectRelationRepository.findProjectRole({ + userId: user.id, + projectId: workflowOwner.id, + }); + + if (projectRole && projectRole !== 'project:personalOwner') { + userRole = 'member'; + } + } + } + + const notesCount = Object.keys(nodeGraph.notes).length; + const overlappingCount = Object.values(nodeGraph.notes).filter( + (note) => note.overlapping, + ).length; + + this.telemetry.track('User saved workflow', { + user_id: user.id, + workflow_id: workflow.id, + node_graph_string: JSON.stringify(nodeGraph), + notes_count_overlapping: overlappingCount, + notes_count_non_overlapping: notesCount - overlappingCount, + version_cli: N8N_VERSION, + num_tags: workflow.tags?.length ?? 0, + public_api: publicApi, + sharing_role: userRole, + }); + } + + // eslint-disable-next-line complexity + private async workflowPostExecute({ + workflow, + runData, + userId, + }: RelayEventMap['workflow-post-execute']) { + if (!workflow.id) { + return; + } + + if (runData?.status === 'waiting') { + // No need to send telemetry or logs when the workflow hasn't finished yet. + return; + } + + const telemetryProperties: IExecutionTrackProperties = { + workflow_id: workflow.id, + is_manual: false, + version_cli: N8N_VERSION, + success: false, + }; + + if (userId) { + telemetryProperties.user_id = userId; + } + + if (runData?.data.resultData.error?.message?.includes('canceled')) { + runData.status = 'canceled'; + } + + telemetryProperties.success = !!runData?.finished; + + // const executionStatus: ExecutionStatus = runData?.status ?? 'unknown'; + const executionStatus: ExecutionStatus = runData + ? determineFinalExecutionStatus(runData) + : 'unknown'; + + if (runData !== undefined) { + telemetryProperties.execution_mode = runData.mode; + telemetryProperties.is_manual = runData.mode === 'manual'; + + let nodeGraphResult: INodesGraphResult | null = null; + + if (!telemetryProperties.success && runData?.data.resultData.error) { + telemetryProperties.error_message = runData?.data.resultData.error.message; + let errorNodeName = + 'node' in runData?.data.resultData.error + ? runData?.data.resultData.error.node?.name + : undefined; + telemetryProperties.error_node_type = + 'node' in runData?.data.resultData.error + ? runData?.data.resultData.error.node?.type + : undefined; + + if (runData.data.resultData.lastNodeExecuted) { + const lastNode = TelemetryHelpers.getNodeTypeForName( + workflow, + runData.data.resultData.lastNodeExecuted, + ); + + if (lastNode !== undefined) { + telemetryProperties.error_node_type = lastNode.type; + errorNodeName = lastNode.name; + } + } + + if (telemetryProperties.is_manual) { + nodeGraphResult = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes); + telemetryProperties.node_graph = nodeGraphResult.nodeGraph; + telemetryProperties.node_graph_string = JSON.stringify(nodeGraphResult.nodeGraph); + + if (errorNodeName) { + telemetryProperties.error_node_id = nodeGraphResult.nameIndices[errorNodeName]; + } + } + } + + if (telemetryProperties.is_manual) { + if (!nodeGraphResult) { + nodeGraphResult = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes); + } + + let userRole: 'owner' | 'sharee' | undefined = undefined; + if (userId) { + const role = await this.sharedWorkflowRepository.findSharingRole(userId, workflow.id); + if (role) { + userRole = role === 'workflow:owner' ? 'owner' : 'sharee'; + } + } + + const manualExecEventProperties: ITelemetryTrackProperties = { + user_id: userId, + workflow_id: workflow.id, + status: executionStatus, + executionStatus: runData?.status ?? 'unknown', + error_message: telemetryProperties.error_message as string, + error_node_type: telemetryProperties.error_node_type, + node_graph_string: telemetryProperties.node_graph_string as string, + error_node_id: telemetryProperties.error_node_id as string, + webhook_domain: null, + sharing_role: userRole, + }; + + if (!manualExecEventProperties.node_graph_string) { + nodeGraphResult = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes); + manualExecEventProperties.node_graph_string = JSON.stringify(nodeGraphResult.nodeGraph); + } + + if (runData.data.startData?.destinationNode) { + const telemetryPayload = { + ...manualExecEventProperties, + node_type: TelemetryHelpers.getNodeTypeForName( + workflow, + runData.data.startData?.destinationNode, + )?.type, + node_id: nodeGraphResult.nameIndices[runData.data.startData?.destinationNode], + }; + + this.telemetry.track('Manual node exec finished', telemetryPayload); + } else { + nodeGraphResult.webhookNodeNames.forEach((name: string) => { + const execJson = runData.data.resultData.runData[name]?.[0]?.data?.main?.[0]?.[0] + ?.json as { headers?: { origin?: string } }; + if (execJson?.headers?.origin && execJson.headers.origin !== '') { + manualExecEventProperties.webhook_domain = pslGet( + execJson.headers.origin.replace(/^https?:\/\//, ''), + ); + } + }); + + this.telemetry.track('Manual workflow exec finished', manualExecEventProperties); + } + } + } + + this.telemetry.trackWorkflowExecution(telemetryProperties); + } + + // #endregion + + // #region Server + + private async serverStarted() { + const cpus = os.cpus(); + const binaryDataConfig = config.getEnv('binaryDataManager'); + + const isS3Selected = config.getEnv('binaryDataManager.mode') === 's3'; + const isS3Available = config.getEnv('binaryDataManager.availableModes').includes('s3'); + const isS3Licensed = this.license.isBinaryDataS3Licensed(); + const authenticationMethod = config.getEnv('userManagement.authenticationMethod'); + + const info = { + version_cli: N8N_VERSION, + db_type: this.globalConfig.database.type, + n8n_version_notifications_enabled: this.globalConfig.versionNotifications.enabled, + n8n_disable_production_main_process: + this.globalConfig.endpoints.disableProductionWebhooksOnMainProcess, + system_info: { + os: { + type: os.type(), + version: os.version(), + }, + memory: os.totalmem() / 1024, + cpus: { + count: cpus.length, + model: cpus[0].model, + speed: cpus[0].speed, + }, + }, + execution_variables: { + executions_mode: config.getEnv('executions.mode'), + executions_timeout: config.getEnv('executions.timeout'), + executions_timeout_max: config.getEnv('executions.maxTimeout'), + executions_data_save_on_error: config.getEnv('executions.saveDataOnError'), + executions_data_save_on_success: config.getEnv('executions.saveDataOnSuccess'), + executions_data_save_on_progress: config.getEnv('executions.saveExecutionProgress'), + executions_data_save_manual_executions: config.getEnv( + 'executions.saveDataManualExecutions', + ), + executions_data_prune: config.getEnv('executions.pruneData'), + executions_data_max_age: config.getEnv('executions.pruneDataMaxAge'), + }, + n8n_deployment_type: config.getEnv('deployment.type'), + n8n_binary_data_mode: binaryDataConfig.mode, + smtp_set_up: this.globalConfig.userManagement.emails.mode === 'smtp', + ldap_allowed: authenticationMethod === 'ldap', + saml_enabled: authenticationMethod === 'saml', + license_plan_name: this.license.getPlanName(), + license_tenant_id: config.getEnv('license.tenantId'), + binary_data_s3: isS3Available && isS3Selected && isS3Licensed, + multi_main_setup_enabled: config.getEnv('multiMainSetup.enabled'), + }; + + const firstWorkflow = await this.workflowRepository.findOne({ + select: ['createdAt'], + order: { createdAt: 'ASC' }, + where: {}, + }); + + this.telemetry.identify(info); + this.telemetry.track('Instance started', { + ...info, + earliest_workflow_created: firstWorkflow?.createdAt, + }); + } + + // #endregion + + // #region User + + private userChangedRole({ + userId, + targetUserId, + targetUserNewRole, + publicApi, + }: RelayEventMap['user-changed-role']) { + this.telemetry.track('User changed role', { + user_id: userId, + target_user_id: targetUserId, + target_user_new_role: targetUserNewRole, + public_api: publicApi, + }); + } + + private userRetrievedUser({ userId, publicApi }: RelayEventMap['user-retrieved-user']) { + this.telemetry.track('User retrieved user', { + user_id: userId, + public_api: publicApi, + }); + } + + private userRetrievedAllUsers({ userId, publicApi }: RelayEventMap['user-retrieved-all-users']) { + this.telemetry.track('User retrieved all users', { + user_id: userId, + public_api: publicApi, + }); + } + + private userRetrievedExecution({ userId, publicApi }: RelayEventMap['user-retrieved-execution']) { + this.telemetry.track('User retrieved execution', { + user_id: userId, + public_api: publicApi, + }); + } + + private userRetrievedAllExecutions({ + userId, + publicApi, + }: RelayEventMap['user-retrieved-all-executions']) { + this.telemetry.track('User retrieved all executions', { + user_id: userId, + public_api: publicApi, + }); + } + + private userRetrievedWorkflow({ userId, publicApi }: RelayEventMap['user-retrieved-workflow']) { + this.telemetry.track('User retrieved workflow', { + user_id: userId, + public_api: publicApi, + }); + } + + private userRetrievedAllWorkflows({ + userId, + publicApi, + }: RelayEventMap['user-retrieved-all-workflows']) { + this.telemetry.track('User retrieved all workflows', { + user_id: userId, + public_api: publicApi, + }); + } + + private userUpdated({ user, fieldsChanged }: RelayEventMap['user-updated']) { + this.telemetry.track('User changed personal settings', { + user_id: user.id, + fields_changed: fieldsChanged, + }); + } + + private userDeleted({ + user, + publicApi, + targetUserOldStatus, + migrationStrategy, + targetUserId, + migrationUserId, + }: RelayEventMap['user-deleted']) { + this.telemetry.track('User deleted user', { + user_id: user.id, + public_api: publicApi, + target_user_old_status: targetUserOldStatus, + migration_strategy: migrationStrategy, + target_user_id: targetUserId, + migration_user_id: migrationUserId, + }); + } + + private userInvited({ + user, + targetUserId, + publicApi, + emailSent, + inviteeRole, + }: RelayEventMap['user-invited']) { + this.telemetry.track('User invited new user', { + user_id: user.id, + target_user_id: targetUserId, + public_api: publicApi, + email_sent: emailSent, + invitee_role: inviteeRole, + }); + } + + private userSignedUp({ user, userType, wasDisabledLdapUser }: RelayEventMap['user-signed-up']) { + this.telemetry.track('User signed up', { + user_id: user.id, + user_type: userType, + was_disabled_ldap_user: wasDisabledLdapUser, + }); + } + + private userSubmittedPersonalizationSurvey({ + userId, + answers, + }: RelayEventMap['user-submitted-personalization-survey']) { + const camelCaseKeys = Object.keys(answers); + const personalizationSurveyData = { user_id: userId } as Record; + camelCaseKeys.forEach((camelCaseKey) => { + personalizationSurveyData[snakeCase(camelCaseKey)] = answers[camelCaseKey]; + }); + + this.telemetry.track('User responded to personalization questions', personalizationSurveyData); + } + + // #endregion +} diff --git a/packages/cli/test/unit/execution-lifecyle/restoreBinaryDataId.test.ts b/packages/cli/src/executionLifecycleHooks/__tests__/restoreBinaryDataId.test.ts similarity index 97% rename from packages/cli/test/unit/execution-lifecyle/restoreBinaryDataId.test.ts rename to packages/cli/src/executionLifecycleHooks/__tests__/restoreBinaryDataId.test.ts index 3fcdb79c72c7e..ea962882bd782 100644 --- a/packages/cli/test/unit/execution-lifecyle/restoreBinaryDataId.test.ts +++ b/packages/cli/src/executionLifecycleHooks/__tests__/restoreBinaryDataId.test.ts @@ -1,6 +1,6 @@ import { restoreBinaryDataId } from '@/executionLifecycleHooks/restoreBinaryDataId'; import { BinaryDataService } from 'n8n-core'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance } from '@test/mocking'; import type { IRun } from 'n8n-workflow'; import config from '@/config'; @@ -24,7 +24,7 @@ function toIRun(item?: object) { } function getDataId(run: IRun, kind: 'binary' | 'json') { - // @ts-ignore + // @ts-expect-error The type doesn't have the correct structure return run.data.resultData.runData.myNode[0].data.main[0][0][kind].data.id; } diff --git a/packages/cli/test/unit/execution-lifecyle/saveExecutionProgress.test.ts b/packages/cli/src/executionLifecycleHooks/__tests__/saveExecutionProgress.test.ts similarity index 98% rename from packages/cli/test/unit/execution-lifecyle/saveExecutionProgress.test.ts rename to packages/cli/src/executionLifecycleHooks/__tests__/saveExecutionProgress.test.ts index 4235e56ecbad3..9b1faa7f60540 100644 --- a/packages/cli/test/unit/execution-lifecyle/saveExecutionProgress.test.ts +++ b/packages/cli/src/executionLifecycleHooks/__tests__/saveExecutionProgress.test.ts @@ -1,5 +1,5 @@ import { ExecutionRepository } from '@/databases/repositories/execution.repository'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance } from '@test/mocking'; import { Logger } from '@/Logger'; import { saveExecutionProgress } from '@/executionLifecycleHooks/saveExecutionProgress'; import * as fnModule from '@/executionLifecycleHooks/toSaveSettings'; diff --git a/packages/cli/test/unit/execution-lifecyle/toSaveSettings.test.ts b/packages/cli/src/executionLifecycleHooks/__tests__/toSaveSettings.test.ts similarity index 98% rename from packages/cli/test/unit/execution-lifecyle/toSaveSettings.test.ts rename to packages/cli/src/executionLifecycleHooks/__tests__/toSaveSettings.test.ts index 6fc516a0ea214..57379e0e73055 100644 --- a/packages/cli/test/unit/execution-lifecyle/toSaveSettings.test.ts +++ b/packages/cli/src/executionLifecycleHooks/__tests__/toSaveSettings.test.ts @@ -35,7 +35,7 @@ describe('failed production executions', () => { }); }); -describe('sucessful production executions', () => { +describe('successful production executions', () => { it('should favor workflow settings over defaults', () => { config.set('executions.saveDataOnSuccess', 'none'); diff --git a/packages/cli/src/executions/__tests__/execution-recovery.service.test.ts b/packages/cli/src/executions/__tests__/execution-recovery.service.test.ts index c1ad38538e337..39a84d27a78c1 100644 --- a/packages/cli/src/executions/__tests__/execution-recovery.service.test.ts +++ b/packages/cli/src/executions/__tests__/execution-recovery.service.test.ts @@ -1,6 +1,7 @@ import Container from 'typedi'; import { stringify } from 'flatted'; import { randomInt } from 'n8n-workflow'; +import { InstanceSettings } from 'n8n-core'; import { mockInstance } from '@test/mocking'; import { createWorkflow } from '@test-integration/db/workflows'; @@ -21,38 +22,37 @@ import { EventMessageNode } from '@/eventbus/EventMessageClasses/EventMessageNod import { IN_PROGRESS_EXECUTION_DATA, OOM_WORKFLOW } from './constants'; import { setupMessages } from './utils'; -import type { EventService } from '@/eventbus/event.service'; import type { EventMessageTypes as EventMessage } from '@/eventbus/EventMessageClasses'; -import type { Logger } from '@/Logger'; describe('ExecutionRecoveryService', () => { - let push: Push; + const push = mockInstance(Push); + mockInstance(InternalHooks); + const instanceSettings = new InstanceSettings(); + let executionRecoveryService: ExecutionRecoveryService; let orchestrationService: OrchestrationService; let executionRepository: ExecutionRepository; beforeAll(async () => { await testDb.init(); - push = mockInstance(Push); executionRepository = Container.get(ExecutionRepository); orchestrationService = Container.get(OrchestrationService); - mockInstance(InternalHooks); executionRecoveryService = new ExecutionRecoveryService( - mock(), + mock(), + instanceSettings, push, executionRepository, orchestrationService, - mock(), + mock(), ); }); beforeEach(() => { - config.set('multiMainSetup.instanceType', 'leader'); + instanceSettings.markAsLeader(); }); afterEach(async () => { - config.load(config.default); jest.restoreAllMocks(); await testDb.truncate(['Execution', 'ExecutionData', 'Workflow']); executionRecoveryService.shutdown(); @@ -69,7 +69,6 @@ describe('ExecutionRecoveryService', () => { * Arrange */ config.set('executions.mode', 'queue'); - jest.spyOn(orchestrationService, 'isLeader', 'get').mockReturnValue(true); const scheduleSpy = jest.spyOn(executionRecoveryService, 'scheduleQueueRecovery'); /** @@ -88,7 +87,7 @@ describe('ExecutionRecoveryService', () => { * Arrange */ config.set('executions.mode', 'queue'); - jest.spyOn(orchestrationService, 'isLeader', 'get').mockReturnValue(false); + instanceSettings.markAsFollower(); const scheduleSpy = jest.spyOn(executionRecoveryService, 'scheduleQueueRecovery'); /** @@ -130,7 +129,7 @@ describe('ExecutionRecoveryService', () => { /** * Arrange */ - config.set('multiMainSetup.instanceType', 'follower'); + instanceSettings.markAsFollower(); // @ts-expect-error Private method const amendSpy = jest.spyOn(executionRecoveryService, 'amend'); const messages = setupMessages('123', 'Some workflow'); diff --git a/packages/cli/test/unit/controllers/executions.controller.test.ts b/packages/cli/src/executions/__tests__/executions.controller.test.ts similarity index 100% rename from packages/cli/test/unit/controllers/executions.controller.test.ts rename to packages/cli/src/executions/__tests__/executions.controller.test.ts diff --git a/packages/cli/test/unit/middleware/executions/parse-range-query.middleware.test.ts b/packages/cli/src/executions/__tests__/parse-range-query.middleware.test.ts similarity index 100% rename from packages/cli/test/unit/middleware/executions/parse-range-query.middleware.test.ts rename to packages/cli/src/executions/__tests__/parse-range-query.middleware.test.ts diff --git a/packages/cli/src/executions/execution-recovery.service.ts b/packages/cli/src/executions/execution-recovery.service.ts index 4b4d90de54ad4..435145545ab16 100644 --- a/packages/cli/src/executions/execution-recovery.service.ts +++ b/packages/cli/src/executions/execution-recovery.service.ts @@ -3,9 +3,9 @@ import { Push } from '@/push'; import { jsonStringify, sleep } from 'n8n-workflow'; import { ExecutionRepository } from '@db/repositories/execution.repository'; import { getWorkflowHooksMain } from '@/WorkflowExecuteAdditionalData'; // @TODO: Dependency cycle -import { InternalHooks } from '@/InternalHooks'; // @TODO: Dependency cycle if injected import type { DateTime } from 'luxon'; import type { IRun, ITaskData } from 'n8n-workflow'; +import { InstanceSettings } from 'n8n-core'; import type { EventMessageTypes } from '../eventbus/EventMessageClasses'; import type { IExecutionResponse } from '@/Interfaces'; import { NodeCrashedError } from '@/errors/node-crashed.error'; @@ -16,7 +16,7 @@ import config from '@/config'; import { OnShutdown } from '@/decorators/OnShutdown'; import type { QueueRecoverySettings } from './execution.types'; import { OrchestrationService } from '@/services/orchestration.service'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; /** * Service for recovering key properties in executions. @@ -25,6 +25,7 @@ import { EventService } from '@/eventbus/event.service'; export class ExecutionRecoveryService { constructor( private readonly logger: Logger, + private readonly instanceSettings: InstanceSettings, private readonly push: Push, private readonly executionRepository: ExecutionRepository, private readonly orchestrationService: OrchestrationService, @@ -37,10 +38,10 @@ export class ExecutionRecoveryService { init() { if (config.getEnv('executions.mode') === 'regular') return; - const { isLeader, isMultiMainSetupEnabled } = this.orchestrationService; - + const { isLeader } = this.instanceSettings; if (isLeader) this.scheduleQueueRecovery(); + const { isMultiMainSetupEnabled } = this.orchestrationService; if (isMultiMainSetupEnabled) { this.orchestrationService.multiMainSetup .on('leader-takeover', () => this.scheduleQueueRecovery()) @@ -59,7 +60,7 @@ export class ExecutionRecoveryService { * Recover key properties of a truncated execution using event logs. */ async recoverFromLogs(executionId: string, messages: EventMessageTypes[]) { - if (this.orchestrationService.isFollower) return; + if (this.instanceSettings.isFollower) return; const amendedExecution = await this.amend(executionId, messages); @@ -280,22 +281,10 @@ export class ExecutionRecoveryService { private async runHooks(execution: IExecutionResponse) { execution.data ??= { resultData: { runData: {} } }; - await Container.get(InternalHooks).onWorkflowPostExecute(execution.id, execution.workflowData, { - data: execution.data, - finished: false, - mode: execution.mode, - waitTill: execution.waitTill, - startedAt: execution.startedAt, - stoppedAt: execution.stoppedAt, - status: execution.status, - }); - this.eventService.emit('workflow-post-execute', { - workflowId: execution.workflowData.id, - workflowName: execution.workflowData.name, + workflow: execution.workflowData, executionId: execution.id, - success: execution.status === 'success', - isManual: execution.mode === 'manual', + runData: execution, }); const externalHooks = getWorkflowHooksMain( @@ -332,7 +321,7 @@ export class ExecutionRecoveryService { private shouldScheduleQueueRecovery() { return ( config.getEnv('executions.mode') === 'queue' && - config.getEnv('multiMainSetup.instanceType') === 'leader' && + this.instanceSettings.isLeader && !this.isShuttingDown ); } diff --git a/packages/cli/test/unit/license/license.service.test.ts b/packages/cli/src/license/__tests__/license.service.test.ts similarity index 97% rename from packages/cli/test/unit/license/license.service.test.ts rename to packages/cli/src/license/__tests__/license.service.test.ts index e28895025ffbe..fb75c6a27d692 100644 --- a/packages/cli/test/unit/license/license.service.test.ts +++ b/packages/cli/src/license/__tests__/license.service.test.ts @@ -1,6 +1,6 @@ import { LicenseErrors, LicenseService } from '@/license/license.service'; import type { License } from '@/License'; -import type { EventService } from '@/eventbus/event.service'; +import type { EventService } from '@/events/event.service'; import type { WorkflowRepository } from '@db/repositories/workflow.repository'; import type { TEntitlement } from '@n8n_io/license-sdk'; import { mock } from 'jest-mock-extended'; diff --git a/packages/cli/src/license/license.controller.ts b/packages/cli/src/license/license.controller.ts index 1c0ca8c0d68ed..945f3650ea340 100644 --- a/packages/cli/src/license/license.controller.ts +++ b/packages/cli/src/license/license.controller.ts @@ -1,6 +1,8 @@ import { Get, Post, RestController, GlobalScope } from '@/decorators'; import { AuthenticatedRequest, LicenseRequest } from '@/requests'; import { LicenseService } from './license.service'; +import { BadRequestError } from '@/errors/response-errors/bad-request.error'; +import type { AxiosError } from 'axios'; @RestController('/license') export class LicenseController { @@ -14,7 +16,18 @@ export class LicenseController { @Post('/enterprise/request_trial') @GlobalScope('license:manage') async requestEnterpriseTrial(req: AuthenticatedRequest) { - await this.licenseService.requestEnterpriseTrial(req.user); + try { + await this.licenseService.requestEnterpriseTrial(req.user); + } catch (error: unknown) { + if (error instanceof Error) { + const errorMsg = + (error as AxiosError<{ message: string }>).response?.data?.message ?? error.message; + + throw new BadRequestError(errorMsg); + } else { + throw new BadRequestError('Failed to request trial'); + } + } } @Post('/activate') diff --git a/packages/cli/src/license/license.service.ts b/packages/cli/src/license/license.service.ts index 01d2a73c48941..0555597a9d3b3 100644 --- a/packages/cli/src/license/license.service.ts +++ b/packages/cli/src/license/license.service.ts @@ -3,7 +3,7 @@ import axios from 'axios'; import { Logger } from '@/Logger'; import { License } from '@/License'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; import type { User } from '@db/entities/User'; import { WorkflowRepository } from '@db/repositories/workflow.repository'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; diff --git a/packages/cli/src/metrics/__tests__/prometheus-metrics.service.test.ts b/packages/cli/src/metrics/__tests__/prometheus-metrics.service.test.ts index 8b89878203ed5..219170ac085a8 100644 --- a/packages/cli/src/metrics/__tests__/prometheus-metrics.service.test.ts +++ b/packages/cli/src/metrics/__tests__/prometheus-metrics.service.test.ts @@ -5,6 +5,8 @@ import { mock } from 'jest-mock-extended'; import { PrometheusMetricsService } from '../prometheus-metrics.service'; import type express from 'express'; import type { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; +import { mockInstance } from '@test/mocking'; +import { GlobalConfig } from '@n8n/config'; const mockMiddleware = ( _req: express.Request, @@ -16,13 +18,27 @@ jest.mock('prom-client'); jest.mock('express-prom-bundle', () => jest.fn(() => mockMiddleware)); describe('PrometheusMetricsService', () => { - beforeEach(() => { - config.load(config.default); + const globalConfig = mockInstance(GlobalConfig, { + endpoints: { + metrics: { + prefix: 'n8n_', + includeDefaultMetrics: true, + includeApiEndpoints: true, + includeCacheMetrics: true, + includeMessageEventBusMetrics: true, + includeCredentialTypeLabel: false, + includeNodeTypeLabel: false, + includeWorkflowIdLabel: false, + includeApiPathLabel: true, + includeApiMethodLabel: true, + includeApiStatusCodeLabel: true, + }, + }, }); describe('init', () => { it('should set up `n8n_version_info`', async () => { - const service = new PrometheusMetricsService(mock(), mock()); + const service = new PrometheusMetricsService(mock(), mock(), globalConfig); await service.init(mock()); @@ -34,7 +50,7 @@ describe('PrometheusMetricsService', () => { }); it('should set up default metrics collection with `prom-client`', async () => { - const service = new PrometheusMetricsService(mock(), mock()); + const service = new PrometheusMetricsService(mock(), mock(), globalConfig); await service.init(mock()); @@ -43,7 +59,7 @@ describe('PrometheusMetricsService', () => { it('should set up `n8n_cache_hits_total`', async () => { config.set('endpoints.metrics.includeCacheMetrics', true); - const service = new PrometheusMetricsService(mock(), mock()); + const service = new PrometheusMetricsService(mock(), mock(), globalConfig); await service.init(mock()); @@ -58,7 +74,7 @@ describe('PrometheusMetricsService', () => { it('should set up `n8n_cache_misses_total`', async () => { config.set('endpoints.metrics.includeCacheMetrics', true); - const service = new PrometheusMetricsService(mock(), mock()); + const service = new PrometheusMetricsService(mock(), mock(), globalConfig); await service.init(mock()); @@ -73,7 +89,7 @@ describe('PrometheusMetricsService', () => { it('should set up `n8n_cache_updates_total`', async () => { config.set('endpoints.metrics.includeCacheMetrics', true); - const service = new PrometheusMetricsService(mock(), mock()); + const service = new PrometheusMetricsService(mock(), mock(), globalConfig); await service.init(mock()); @@ -91,7 +107,7 @@ describe('PrometheusMetricsService', () => { config.set('endpoints.metrics.includeApiPathLabel', true); config.set('endpoints.metrics.includeApiMethodLabel', true); config.set('endpoints.metrics.includeApiStatusCodeLabel', true); - const service = new PrometheusMetricsService(mock(), mock()); + const service = new PrometheusMetricsService(mock(), mock(), globalConfig); const app = mock(); @@ -122,7 +138,7 @@ describe('PrometheusMetricsService', () => { it('should set up event bus metrics', async () => { const eventBus = mock(); - const service = new PrometheusMetricsService(mock(), eventBus); + const service = new PrometheusMetricsService(mock(), eventBus, globalConfig); await service.init(mock()); diff --git a/packages/cli/src/metrics/prometheus-metrics.service.ts b/packages/cli/src/metrics/prometheus-metrics.service.ts index b2d38424bccf7..1444f6f694bbd 100644 --- a/packages/cli/src/metrics/prometheus-metrics.service.ts +++ b/packages/cli/src/metrics/prometheus-metrics.service.ts @@ -1,4 +1,3 @@ -import config from '@/config'; import { N8N_VERSION } from '@/constants'; import type express from 'express'; import promBundle from 'express-prom-bundle'; @@ -11,32 +10,34 @@ import { MessageEventBus } from '@/eventbus/MessageEventBus/MessageEventBus'; import { EventMessageTypeNames } from 'n8n-workflow'; import type { EventMessageTypes } from '@/eventbus'; import type { Includes, MetricCategory, MetricLabel } from './types'; +import { GlobalConfig } from '@n8n/config'; @Service() export class PrometheusMetricsService { constructor( private readonly cacheService: CacheService, private readonly eventBus: MessageEventBus, + private readonly globalConfig: GlobalConfig, ) {} private readonly counters: { [key: string]: Counter | null } = {}; - private readonly prefix = config.getEnv('endpoints.metrics.prefix'); + private readonly prefix = this.globalConfig.endpoints.metrics.prefix; private readonly includes: Includes = { metrics: { - default: config.getEnv('endpoints.metrics.includeDefaultMetrics'), - routes: config.getEnv('endpoints.metrics.includeApiEndpoints'), - cache: config.getEnv('endpoints.metrics.includeCacheMetrics'), - logs: config.getEnv('endpoints.metrics.includeMessageEventBusMetrics'), + default: this.globalConfig.endpoints.metrics.includeDefaultMetrics, + routes: this.globalConfig.endpoints.metrics.includeApiEndpoints, + cache: this.globalConfig.endpoints.metrics.includeCacheMetrics, + logs: this.globalConfig.endpoints.metrics.includeMessageEventBusMetrics, }, labels: { - credentialsType: config.getEnv('endpoints.metrics.includeCredentialTypeLabel'), - nodeType: config.getEnv('endpoints.metrics.includeNodeTypeLabel'), - workflowId: config.getEnv('endpoints.metrics.includeWorkflowIdLabel'), - apiPath: config.getEnv('endpoints.metrics.includeApiPathLabel'), - apiMethod: config.getEnv('endpoints.metrics.includeApiMethodLabel'), - apiStatusCode: config.getEnv('endpoints.metrics.includeApiStatusCodeLabel'), + credentialsType: this.globalConfig.endpoints.metrics.includeCredentialTypeLabel, + nodeType: this.globalConfig.endpoints.metrics.includeNodeTypeLabel, + workflowId: this.globalConfig.endpoints.metrics.includeWorkflowIdLabel, + apiPath: this.globalConfig.endpoints.metrics.includeApiPathLabel, + apiMethod: this.globalConfig.endpoints.metrics.includeApiMethodLabel, + apiStatusCode: this.globalConfig.endpoints.metrics.includeApiStatusCodeLabel, }, }; diff --git a/packages/cli/src/middlewares/bodyParser.ts b/packages/cli/src/middlewares/bodyParser.ts index d48bf593cceee..5efe388e6f1e1 100644 --- a/packages/cli/src/middlewares/bodyParser.ts +++ b/packages/cli/src/middlewares/bodyParser.ts @@ -6,8 +6,9 @@ import { parse as parseQueryString } from 'querystring'; import { Parser as XmlParser } from 'xml2js'; import { parseIncomingMessage } from 'n8n-core'; import { jsonParse } from 'n8n-workflow'; -import config from '@/config'; import { UnprocessableRequestError } from '@/errors/response-errors/unprocessable.error'; +import { GlobalConfig } from '@n8n/config'; +import Container from 'typedi'; const xmlParser = new XmlParser({ async: true, @@ -16,7 +17,7 @@ const xmlParser = new XmlParser({ explicitArray: false, // Only put properties in array if length > 1 }); -const payloadSizeMax = config.getEnv('endpoints.payloadSizeMax'); +const payloadSizeMax = Container.get(GlobalConfig).endpoints.payloadSizeMax; export const rawBodyReader: RequestHandler = async (req, _res, next) => { parseIncomingMessage(req); diff --git a/packages/cli/test/unit/middleware/listQuery.test.ts b/packages/cli/src/middlewares/listQuery/__tests__/listQuery.test.ts similarity index 100% rename from packages/cli/test/unit/middleware/listQuery.test.ts rename to packages/cli/src/middlewares/listQuery/__tests__/listQuery.test.ts diff --git a/packages/cli/src/middlewares/listQuery/index.ts b/packages/cli/src/middlewares/listQuery/index.ts index 0c8e2c7427c54..524dcb268e96d 100644 --- a/packages/cli/src/middlewares/listQuery/index.ts +++ b/packages/cli/src/middlewares/listQuery/index.ts @@ -1,8 +1,16 @@ import { filterListQueryMiddleware } from './filter'; import { selectListQueryMiddleware } from './select'; import { paginationListQueryMiddleware } from './pagination'; +import type { ListQuery } from '@/requests'; +import type { NextFunction, Response } from 'express'; -export const listQueryMiddleware = [ +export type ListQueryMiddleware = ( + req: ListQuery.Request, + res: Response, + next: NextFunction, +) => void; + +export const listQueryMiddleware: ListQueryMiddleware[] = [ filterListQueryMiddleware, selectListQueryMiddleware, paginationListQueryMiddleware, diff --git a/packages/cli/test/unit/PostHog.test.ts b/packages/cli/src/posthog/__tests__/PostHog.test.ts similarity index 97% rename from packages/cli/test/unit/PostHog.test.ts rename to packages/cli/src/posthog/__tests__/PostHog.test.ts index 5798c0cce2217..f1604f7253df1 100644 --- a/packages/cli/test/unit/PostHog.test.ts +++ b/packages/cli/src/posthog/__tests__/PostHog.test.ts @@ -2,7 +2,7 @@ import { PostHog } from 'posthog-node'; import { InstanceSettings } from 'n8n-core'; import { PostHogClient } from '@/posthog'; import config from '@/config'; -import { mockInstance } from '../shared/mocking'; +import { mockInstance } from '@test/mocking'; jest.mock('posthog-node'); diff --git a/packages/cli/test/unit/push/index.test.ts b/packages/cli/src/push/__tests__/index.test.ts similarity index 96% rename from packages/cli/test/unit/push/index.test.ts rename to packages/cli/src/push/__tests__/index.test.ts index 1736693509374..a61496b0c9248 100644 --- a/packages/cli/test/unit/push/index.test.ts +++ b/packages/cli/src/push/__tests__/index.test.ts @@ -9,7 +9,7 @@ import { WebSocketPush } from '@/push/websocket.push'; import type { WebSocketPushRequest, SSEPushRequest } from '@/push/types'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance } from '@test/mocking'; jest.unmock('@/push'); diff --git a/packages/cli/test/unit/push/websocket.push.test.ts b/packages/cli/src/push/__tests__/websocket.push.test.ts similarity index 98% rename from packages/cli/test/unit/push/websocket.push.test.ts rename to packages/cli/src/push/__tests__/websocket.push.test.ts index 7531e43776c2c..f1a0e577f9ad8 100644 --- a/packages/cli/test/unit/push/websocket.push.test.ts +++ b/packages/cli/src/push/__tests__/websocket.push.test.ts @@ -6,7 +6,7 @@ import { WebSocketPush } from '@/push/websocket.push'; import { Logger } from '@/Logger'; import type { PushDataExecutionRecovered } from '@/Interfaces'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance } from '@test/mocking'; jest.useFakeTimers(); diff --git a/packages/cli/src/push/index.ts b/packages/cli/src/push/index.ts index 647ab40acf0c5..a946348430133 100644 --- a/packages/cli/src/push/index.ts +++ b/packages/cli/src/push/index.ts @@ -1,4 +1,3 @@ -import { EventEmitter } from 'events'; import { ServerResponse } from 'http'; import type { Server } from 'http'; import type { Socket } from 'net'; @@ -17,6 +16,11 @@ import { OrchestrationService } from '@/services/orchestration.service'; import { SSEPush } from './sse.push'; import { WebSocketPush } from './websocket.push'; import type { PushResponse, SSEPushRequest, WebSocketPushRequest } from './types'; +import { TypedEmitter } from '@/TypedEmitter'; + +type PushEvents = { + editorUiConnected: string; +}; const useWebSockets = config.getEnv('push.backend') === 'websocket'; @@ -28,7 +32,7 @@ const useWebSockets = config.getEnv('push.backend') === 'websocket'; * @emits message when a message is received from a client */ @Service() -export class Push extends EventEmitter { +export class Push extends TypedEmitter { private backend = useWebSockets ? Container.get(WebSocketPush) : Container.get(SSEPush); constructor(private readonly orchestrationService: OrchestrationService) { @@ -37,7 +41,6 @@ export class Push extends EventEmitter { handleRequest(req: SSEPushRequest | WebSocketPushRequest, res: PushResponse) { const { - user, ws, query: { pushRef }, } = req; diff --git a/packages/cli/src/requests.ts b/packages/cli/src/requests.ts index 1ff6537257529..4fe369857eb7a 100644 --- a/packages/cli/src/requests.ts +++ b/packages/cli/src/requests.ts @@ -76,9 +76,7 @@ export type AuthlessRequest< ResponseBody = {}, RequestBody = {}, RequestQuery = {}, -> = APIRequest & { - user: never; -}; +> = APIRequest; export type AuthenticatedRequest< RouteParams = {}, @@ -308,7 +306,7 @@ export declare namespace UserRequest { { id: string; email: string; identifier: string }, {}, {}, - { limit?: number; offset?: number; cursor?: string; includeRole?: boolean } + { limit?: number; offset?: number; cursor?: string; includeRole?: boolean; projectId?: string } >; export type PasswordResetLink = AuthenticatedRequest<{ id: string }, {}, {}, {}>; @@ -371,7 +369,7 @@ export declare namespace MFA { export declare namespace OAuthRequest { namespace OAuth1Credential { type Auth = AuthenticatedRequest<{}, {}, {}, { id: string }>; - type Callback = AuthenticatedRequest< + type Callback = AuthlessRequest< {}, {}, {}, @@ -383,7 +381,7 @@ export declare namespace OAuthRequest { namespace OAuth2Credential { type Auth = AuthenticatedRequest<{}, {}, {}, { id: string }>; - type Callback = AuthenticatedRequest<{}, {}, {}, { code: string; state: string }>; + type Callback = AuthlessRequest<{}, {}, {}, { code: string; state: string }>; } } diff --git a/packages/cli/test/unit/ExecutionMetadataService.test.ts b/packages/cli/src/services/__tests__/ExecutionMetadataService.test.ts similarity index 94% rename from packages/cli/test/unit/ExecutionMetadataService.test.ts rename to packages/cli/src/services/__tests__/ExecutionMetadataService.test.ts index 826aae5e25324..8b77b8b1681d4 100644 --- a/packages/cli/test/unit/ExecutionMetadataService.test.ts +++ b/packages/cli/src/services/__tests__/ExecutionMetadataService.test.ts @@ -1,7 +1,7 @@ import { Container } from 'typedi'; import { ExecutionMetadataRepository } from '@db/repositories/executionMetadata.repository'; import { ExecutionMetadataService } from '@/services/executionMetadata.service'; -import { mockInstance } from '../shared/mocking'; +import { mockInstance } from '@test/mocking'; describe('ExecutionMetadataService', () => { const repository = mockInstance(ExecutionMetadataRepository); diff --git a/packages/cli/test/unit/services/activeWorkflows.service.test.ts b/packages/cli/src/services/__tests__/activeWorkflows.service.test.ts similarity index 100% rename from packages/cli/test/unit/services/activeWorkflows.service.test.ts rename to packages/cli/src/services/__tests__/activeWorkflows.service.test.ts diff --git a/packages/cli/test/unit/services/communityPackages.service.test.ts b/packages/cli/src/services/__tests__/communityPackages.service.test.ts similarity index 89% rename from packages/cli/test/unit/services/communityPackages.service.test.ts rename to packages/cli/src/services/__tests__/communityPackages.service.test.ts index 2b0d8bf0d452a..8d3289a8695b2 100644 --- a/packages/cli/test/unit/services/communityPackages.service.test.ts +++ b/packages/cli/src/services/__tests__/communityPackages.service.test.ts @@ -2,8 +2,10 @@ import { exec } from 'child_process'; import { access as fsAccess, mkdir as fsMkdir } from 'fs/promises'; import axios from 'axios'; import { mocked } from 'jest-mock'; -import Container from 'typedi'; +import { mock } from 'jest-mock-extended'; +import type { GlobalConfig } from '@n8n/config'; import type { PublicInstalledPackage } from 'n8n-workflow'; +import type { PackageDirectoryLoader } from 'n8n-core'; import { NODE_PACKAGE_PREFIX, @@ -11,24 +13,18 @@ import { NPM_PACKAGE_STATUS_GOOD, RESPONSE_ERROR_MESSAGES, } from '@/constants'; -import config from '@/config'; import { InstalledPackages } from '@db/entities/InstalledPackages'; import type { CommunityPackages } from '@/Interfaces'; import { CommunityPackagesService } from '@/services/communityPackages.service'; import { InstalledNodesRepository } from '@db/repositories/installedNodes.repository'; import { InstalledPackagesRepository } from '@db/repositories/installedPackages.repository'; import { InstalledNodes } from '@db/entities/InstalledNodes'; -import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; +import type { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; -import { mockInstance } from '../../shared/mocking'; -import { - COMMUNITY_NODE_VERSION, - COMMUNITY_PACKAGE_VERSION, -} from '../../integration/shared/constants'; -import { randomName } from '../../integration/shared/random'; -import { mockPackageName, mockPackagePair } from '../../integration/shared/utils'; -import { InstanceSettings, PackageDirectoryLoader } from 'n8n-core'; -import { Logger } from '@/Logger'; +import { mockInstance } from '@test/mocking'; +import { COMMUNITY_NODE_VERSION, COMMUNITY_PACKAGE_VERSION } from '@test-integration/constants'; +import { randomName } from '@test-integration/random'; +import { mockPackageName, mockPackagePair } from '@test-integration/utils'; jest.mock('fs/promises'); jest.mock('child_process'); @@ -43,6 +39,15 @@ const execMock = ((...args) => { }) as typeof exec; describe('CommunityPackagesService', () => { + const globalConfig = mock({ + nodes: { + communityPackages: { + reinstallMissing: false, + }, + }, + }); + const loadNodesAndCredentials = mock(); + const installedNodesRepository = mockInstance(InstalledNodesRepository); installedNodesRepository.create.mockImplementation(() => { const nodeName = randomName(); @@ -63,13 +68,14 @@ describe('CommunityPackagesService', () => { }); }); - mockInstance(LoadNodesAndCredentials); - - const communityPackagesService = Container.get(CommunityPackagesService); - - beforeEach(() => { - config.load(config.default); - }); + const communityPackagesService = new CommunityPackagesService( + mock(), + mock(), + mock(), + loadNodesAndCredentials, + mock(), + globalConfig, + ); describe('parseNpmPackageName()', () => { test('should fail with empty package name', () => { @@ -368,29 +374,12 @@ describe('CommunityPackagesService', () => { }; describe('updateNpmModule', () => { - let packageDirectoryLoader: PackageDirectoryLoader; - let communityPackagesService: CommunityPackagesService; + const packageDirectoryLoader = mock(); beforeEach(async () => { - jest.restoreAllMocks(); + jest.clearAllMocks(); - packageDirectoryLoader = mockInstance(PackageDirectoryLoader); - const loadNodesAndCredentials = mockInstance(LoadNodesAndCredentials); loadNodesAndCredentials.loadPackage.mockResolvedValue(packageDirectoryLoader); - const instanceSettings = mockInstance(InstanceSettings); - const logger = mockInstance(Logger); - const installedPackagesRepository = mockInstance(InstalledPackagesRepository); - - communityPackagesService = new CommunityPackagesService( - instanceSettings, - logger, - installedPackagesRepository, - loadNodesAndCredentials, - ); - }); - - afterEach(async () => { - jest.restoreAllMocks(); }); test('should call `exec` with the correct command ', async () => { @@ -408,10 +397,7 @@ describe('CommunityPackagesService', () => { // // ACT // - await communityPackagesService.updateNpmModule( - installedPackage.packageName, - installedPackage, - ); + await communityPackagesService.updatePackage(installedPackage.packageName, installedPackage); // // ASSERT diff --git a/packages/cli/test/unit/credentials-tester.unit.test.ts b/packages/cli/src/services/__tests__/credentials-tester.service.test.ts similarity index 100% rename from packages/cli/test/unit/credentials-tester.unit.test.ts rename to packages/cli/src/services/__tests__/credentials-tester.service.test.ts diff --git a/packages/cli/test/unit/services/curl.service.test.ts b/packages/cli/src/services/__tests__/curl.service.test.ts similarity index 100% rename from packages/cli/test/unit/services/curl.service.test.ts rename to packages/cli/src/services/__tests__/curl.service.test.ts diff --git a/packages/cli/test/unit/services/hooks.service.test.ts b/packages/cli/src/services/__tests__/hooks.service.test.ts similarity index 100% rename from packages/cli/test/unit/services/hooks.service.test.ts rename to packages/cli/src/services/__tests__/hooks.service.test.ts diff --git a/packages/cli/test/unit/services/jwt.service.test.ts b/packages/cli/src/services/__tests__/jwt.service.test.ts similarity index 100% rename from packages/cli/test/unit/services/jwt.service.test.ts rename to packages/cli/src/services/__tests__/jwt.service.test.ts diff --git a/packages/cli/test/unit/services/naming.service.test.ts b/packages/cli/src/services/__tests__/naming.service.test.ts similarity index 98% rename from packages/cli/test/unit/services/naming.service.test.ts rename to packages/cli/src/services/__tests__/naming.service.test.ts index ea2c34fb8c1ee..1ca216734630e 100644 --- a/packages/cli/test/unit/services/naming.service.test.ts +++ b/packages/cli/src/services/__tests__/naming.service.test.ts @@ -1,6 +1,6 @@ import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; import { CredentialsRepository } from '@/databases/repositories/credentials.repository'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance } from '@test/mocking'; import { NamingService } from '@/services/naming.service'; import type { WorkflowEntity } from '@/databases/entities/WorkflowEntity'; import type { CredentialsEntity } from '@/databases/entities/CredentialsEntity'; diff --git a/packages/cli/test/unit/services/orchestration.service.test.ts b/packages/cli/src/services/__tests__/orchestration.service.test.ts similarity index 92% rename from packages/cli/test/unit/services/orchestration.service.test.ts rename to packages/cli/src/services/__tests__/orchestration.service.test.ts index 43aba8d4843c7..c69e674613e25 100644 --- a/packages/cli/test/unit/services/orchestration.service.test.ts +++ b/packages/cli/src/services/__tests__/orchestration.service.test.ts @@ -1,4 +1,9 @@ import Container from 'typedi'; +import type Redis from 'ioredis'; +import { mock } from 'jest-mock-extended'; +import { InstanceSettings } from 'n8n-core'; +import type { WorkflowActivateMode } from 'n8n-workflow'; + import config from '@/config'; import { OrchestrationService } from '@/services/orchestration.service'; import type { RedisServiceWorkerResponseObject } from '@/services/redis/RedisServiceCommands'; @@ -12,12 +17,10 @@ import { ExternalSecretsManager } from '@/ExternalSecrets/ExternalSecretsManager import { Logger } from '@/Logger'; import { Push } from '@/push'; import { ActiveWorkflowManager } from '@/ActiveWorkflowManager'; -import { mockInstance } from '../../shared/mocking'; -import type { WorkflowActivateMode } from 'n8n-workflow'; +import { mockInstance } from '@test/mocking'; import { RedisClientService } from '@/services/redis/redis-client.service'; -import type Redis from 'ioredis'; -import { mock } from 'jest-mock-extended'; +const instanceSettings = Container.get(InstanceSettings); const redisClientService = mockInstance(RedisClientService); const mockRedisClient = mock(); redisClientService.createClient.mockReturnValue(mockRedisClient); @@ -33,7 +36,7 @@ function setDefaultConfig() { config.set('generic.instanceType', 'main'); } -const workerRestartEventbusResponse: RedisServiceWorkerResponseObject = { +const workerRestartEventBusResponse: RedisServiceWorkerResponseObject = { senderId: 'test', workerId: 'test', command: 'restartEventBus', @@ -72,6 +75,10 @@ describe('Orchestration Service', () => { queueModeId = config.get('redis.queueModeId'); }); + beforeEach(() => { + instanceSettings.markAsLeader(); + }); + afterAll(async () => { jest.mock('@/services/redis/RedisServicePubSubPublisher').restoreAllMocks(); jest.mock('@/services/redis/RedisServicePubSubSubscriber').restoreAllMocks(); @@ -88,7 +95,7 @@ describe('Orchestration Service', () => { test('should handle worker responses', async () => { const response = await handleWorkerResponseMessageMain( - JSON.stringify(workerRestartEventbusResponse), + JSON.stringify(workerRestartEventBusResponse), ); expect(response.command).toEqual('restartEventBus'); }); @@ -108,7 +115,7 @@ describe('Orchestration Service', () => { test('should reject command messages from itself', async () => { const response = await handleCommandMessageMain( - JSON.stringify({ ...workerRestartEventbusResponse, senderId: queueModeId }), + JSON.stringify({ ...workerRestartEventBusResponse, senderId: queueModeId }), ); expect(response).toBeDefined(); expect(response!.command).toEqual('restartEventBus'); @@ -141,13 +148,10 @@ describe('Orchestration Service', () => { ); expect(helpers.debounceMessageReceiver).toHaveBeenCalledTimes(2); expect(res1!.payload).toBeUndefined(); - expect(res2!.payload!.result).toEqual('debounced'); + expect(res2!.payload).toEqual({ result: 'debounced' }); }); describe('shouldAddWebhooks', () => { - beforeEach(() => { - config.set('multiMainSetup.instanceType', 'leader'); - }); test('should return true for init', () => { // We want to ensure that webhooks are populated on init // more https://github.com/n8n-io/n8n/pull/8830 @@ -169,7 +173,7 @@ describe('Orchestration Service', () => { }); test('should return false for update or activate when not leader', () => { - config.set('multiMainSetup.instanceType', 'follower'); + instanceSettings.markAsFollower(); const modes = ['update', 'activate'] as WorkflowActivateMode[]; for (const mode of modes) { const result = os.shouldAddWebhooks(mode); diff --git a/packages/cli/test/unit/services/ownership.service.test.ts b/packages/cli/src/services/__tests__/ownership.service.test.ts similarity index 98% rename from packages/cli/test/unit/services/ownership.service.test.ts rename to packages/cli/src/services/__tests__/ownership.service.test.ts index d1a722da19625..8a3d40eb60f8d 100644 --- a/packages/cli/test/unit/services/ownership.service.test.ts +++ b/packages/cli/src/services/__tests__/ownership.service.test.ts @@ -3,14 +3,14 @@ import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.reposi import { SharedWorkflow } from '@db/entities/SharedWorkflow'; import { User } from '@db/entities/User'; import type { SharedCredentials } from '@db/entities/SharedCredentials'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance } from '@test/mocking'; import { WorkflowEntity } from '@/databases/entities/WorkflowEntity'; import { UserRepository } from '@/databases/repositories/user.repository'; import { mock } from 'jest-mock-extended'; import { Project } from '@/databases/entities/Project'; import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository'; import { ProjectRelation } from '@/databases/entities/ProjectRelation'; -import { mockCredential, mockProject } from '../shared/mockObjects'; +import { mockCredential, mockProject } from '@test/mockObjects'; describe('OwnershipService', () => { const userRepository = mockInstance(UserRepository); diff --git a/packages/cli/test/unit/utilities/password.utility.test.ts b/packages/cli/src/services/__tests__/password.utility.test.ts similarity index 100% rename from packages/cli/test/unit/utilities/password.utility.test.ts rename to packages/cli/src/services/__tests__/password.utility.test.ts diff --git a/packages/cli/test/unit/services/redis.service.test.ts b/packages/cli/src/services/__tests__/redis.service.test.ts similarity index 94% rename from packages/cli/test/unit/services/redis.service.test.ts rename to packages/cli/src/services/__tests__/redis.service.test.ts index cb963ad535567..1d9652983735c 100644 --- a/packages/cli/test/unit/services/redis.service.test.ts +++ b/packages/cli/src/services/__tests__/redis.service.test.ts @@ -2,7 +2,7 @@ import Container from 'typedi'; import { Logger } from '@/Logger'; import config from '@/config'; import { RedisService } from '@/services/redis.service'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance } from '@test/mocking'; jest.mock('ioredis', () => { const Redis = require('ioredis-mock'); @@ -14,7 +14,7 @@ jest.mock('ioredis', () => { }; } // second mock for our code - return function (...args: any) { + return function (...args: unknown[]) { return new Redis(args); }; }); diff --git a/packages/cli/test/unit/services/test-webhook-registrations.service.test.ts b/packages/cli/src/services/__tests__/test-webhook-registrations.service.test.ts similarity index 100% rename from packages/cli/test/unit/services/test-webhook-registrations.service.test.ts rename to packages/cli/src/services/__tests__/test-webhook-registrations.service.test.ts diff --git a/packages/cli/test/unit/services/user.service.test.ts b/packages/cli/src/services/__tests__/user.service.test.ts similarity index 98% rename from packages/cli/test/unit/services/user.service.test.ts rename to packages/cli/src/services/__tests__/user.service.test.ts index 89929f57b0b89..5aeb919220c23 100644 --- a/packages/cli/test/unit/services/user.service.test.ts +++ b/packages/cli/src/services/__tests__/user.service.test.ts @@ -4,7 +4,7 @@ import { v4 as uuid } from 'uuid'; import { User } from '@db/entities/User'; import { UserService } from '@/services/user.service'; import { UrlService } from '@/services/url.service'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance } from '@test/mocking'; import { UserRepository } from '@/databases/repositories/user.repository'; import { GlobalConfig } from '@n8n/config'; diff --git a/packages/cli/test/unit/services/webhook.service.test.ts b/packages/cli/src/services/__tests__/webhook.service.test.ts similarity index 99% rename from packages/cli/test/unit/services/webhook.service.test.ts rename to packages/cli/src/services/__tests__/webhook.service.test.ts index adb18de4b309b..181bc60752956 100644 --- a/packages/cli/test/unit/services/webhook.service.test.ts +++ b/packages/cli/src/services/__tests__/webhook.service.test.ts @@ -4,7 +4,7 @@ import { WebhookRepository } from '@db/repositories/webhook.repository'; import { CacheService } from '@/services/cache/cache.service'; import { WebhookService } from '@/services/webhook.service'; import { WebhookEntity } from '@db/entities/WebhookEntity'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance } from '@test/mocking'; const createWebhook = (method: string, path: string, webhookId?: string, pathSegments?: number) => Object.assign(new WebhookEntity(), { diff --git a/packages/cli/test/unit/services/workflow-statistics.service.test.ts b/packages/cli/src/services/__tests__/workflow-statistics.service.test.ts similarity index 98% rename from packages/cli/test/unit/services/workflow-statistics.service.test.ts rename to packages/cli/src/services/__tests__/workflow-statistics.service.test.ts index da1338f8eb764..24c3bce560d32 100644 --- a/packages/cli/test/unit/services/workflow-statistics.service.test.ts +++ b/packages/cli/src/services/__tests__/workflow-statistics.service.test.ts @@ -17,7 +17,7 @@ import { WorkflowStatisticsRepository } from '@db/repositories/workflowStatistic import { WorkflowStatisticsService } from '@/services/workflow-statistics.service'; import { UserService } from '@/services/user.service'; import { OwnershipService } from '@/services/ownership.service'; -import { mockInstance } from '../../shared/mocking'; +import { mockInstance } from '@test/mocking'; import type { Project } from '@/databases/entities/Project'; describe('WorkflowStatisticsService', () => { @@ -48,6 +48,7 @@ describe('WorkflowStatisticsService', () => { mock(), new WorkflowStatisticsRepository(dataSource, globalConfig), ownershipService, + userService, ); const onFirstProductionWorkflowSuccess = jest.fn(); @@ -117,7 +118,7 @@ describe('WorkflowStatisticsService', () => { }; const runData: IRun = { finished: false, - status: 'failed', + status: 'error', data: { resultData: { runData: {} } }, mode: 'internal' as WorkflowExecuteMode, startedAt: new Date(), @@ -205,7 +206,7 @@ describe('WorkflowStatisticsService', () => { test('should not send metrics for entries that already have the flag set', async () => { // Fetch data for workflow 2 which is set up to not be altered in the mocks - entityManager.insert.mockRejectedValueOnce(new QueryFailedError('', undefined, '')); + entityManager.insert.mockRejectedValueOnce(new QueryFailedError('', undefined, new Error())); const workflowId = '1'; const node = { id: 'abcde', diff --git a/packages/cli/test/unit/services/cache-mock.service.test.ts b/packages/cli/src/services/cache/__tests__/cache-mock.service.test.ts similarity index 100% rename from packages/cli/test/unit/services/cache-mock.service.test.ts rename to packages/cli/src/services/cache/__tests__/cache-mock.service.test.ts diff --git a/packages/cli/test/unit/services/cache.service.test.ts b/packages/cli/src/services/cache/__tests__/cache.service.test.ts similarity index 92% rename from packages/cli/test/unit/services/cache.service.test.ts rename to packages/cli/src/services/cache/__tests__/cache.service.test.ts index a742b20698d46..5c024b2903cc7 100644 --- a/packages/cli/test/unit/services/cache.service.test.ts +++ b/packages/cli/src/services/cache/__tests__/cache.service.test.ts @@ -1,6 +1,8 @@ import { CacheService } from '@/services/cache/cache.service'; import config from '@/config'; import { sleep } from 'n8n-workflow'; +import { GlobalConfig } from '@n8n/config'; +import Container from 'typedi'; jest.mock('ioredis', () => { const Redis = require('ioredis-mock'); @@ -13,10 +15,12 @@ jest.mock('ioredis', () => { for (const backend of ['memory', 'redis'] as const) { describe(backend, () => { let cacheService: CacheService; + let globalConfig: GlobalConfig; beforeAll(async () => { - config.set('cache.backend', backend); - cacheService = new CacheService(); + globalConfig = Container.get(GlobalConfig); + globalConfig.cache.backend = backend; + cacheService = new CacheService(globalConfig); await cacheService.init(); }); @@ -43,7 +47,7 @@ for (const backend of ['memory', 'redis'] as const) { if (backend === 'memory') { test('should honor max size when enough', async () => { - config.set('cache.memory.maxSize', 16); // enough bytes for "withoutUnicode" + globalConfig.cache.memory.maxSize = 16; // enough bytes for "withoutUnicode" await cacheService.init(); await cacheService.set('key', 'withoutUnicode'); @@ -51,12 +55,12 @@ for (const backend of ['memory', 'redis'] as const) { await expect(cacheService.get('key')).resolves.toBe('withoutUnicode'); // restore - config.set('cache.memory.maxSize', 3 * 1024 * 1024); + globalConfig.cache.memory.maxSize = 3 * 1024 * 1024; await cacheService.init(); }); test('should honor max size when not enough', async () => { - config.set('cache.memory.maxSize', 16); // not enough bytes for "withUnicodeԱԲԳ" + globalConfig.cache.memory.maxSize = 16; // not enough bytes for "withUnicodeԱԲԳ" await cacheService.init(); await cacheService.set('key', 'withUnicodeԱԲԳ'); @@ -64,7 +68,8 @@ for (const backend of ['memory', 'redis'] as const) { await expect(cacheService.get('key')).resolves.toBeUndefined(); // restore - config.set('cache.memory.maxSize', 3 * 1024 * 1024); + globalConfig.cache.memory.maxSize = 3 * 1024 * 1024; + // restore await cacheService.init(); }); } diff --git a/packages/cli/src/services/cache/cache.service.ts b/packages/cli/src/services/cache/cache.service.ts index 8e9a4dc95c009..daf51911ff362 100644 --- a/packages/cli/src/services/cache/cache.service.ts +++ b/packages/cli/src/services/cache/cache.service.ts @@ -1,5 +1,3 @@ -import EventEmitter from 'node:events'; - import Container, { Service } from 'typedi'; import { caching } from 'cache-manager'; import { ApplicationError, jsonStringify } from 'n8n-workflow'; @@ -10,20 +8,30 @@ import { MalformedRefreshValueError } from '@/errors/cache-errors/malformed-refr import type { TaggedRedisCache, TaggedMemoryCache, - CacheEvent, MaybeHash, Hash, } from '@/services/cache/cache.types'; import { TIME } from '@/constants'; +import { TypedEmitter } from '@/TypedEmitter'; +import { GlobalConfig } from '@n8n/config'; + +type CacheEvents = { + 'metrics.cache.hit': never; + 'metrics.cache.miss': never; + 'metrics.cache.update': never; +}; @Service() -export class CacheService extends EventEmitter { +export class CacheService extends TypedEmitter { + constructor(private readonly globalConfig: GlobalConfig) { + super(); + } + private cache: TaggedRedisCache | TaggedMemoryCache; async init() { - const backend = config.getEnv('cache.backend'); + const { backend } = this.globalConfig.cache; const mode = config.getEnv('executions.mode'); - const ttl = config.getEnv('cache.redis.ttl'); const useRedis = backend === 'redis' || (backend === 'auto' && mode === 'queue'); @@ -32,8 +40,9 @@ export class CacheService extends EventEmitter { const redisClientService = Container.get(RedisClientService); const prefixBase = config.getEnv('redis.prefix'); - const cachePrefix = config.getEnv('cache.redis.prefix'); - const prefix = redisClientService.toValidPrefix(`${prefixBase}:${cachePrefix}:`); + const prefix = redisClientService.toValidPrefix( + `${prefixBase}:${this.globalConfig.cache.redis.prefix}:`, + ); const redisClient = redisClientService.createClient({ type: 'client(cache)', @@ -41,7 +50,9 @@ export class CacheService extends EventEmitter { }); const { redisStoreUsingClient } = await import('@/services/cache/redis.cache-manager'); - const redisStore = redisStoreUsingClient(redisClient, { ttl }); + const redisStore = redisStoreUsingClient(redisClient, { + ttl: this.globalConfig.cache.redis.ttl, + }); const redisCache = await caching(redisStore); @@ -50,7 +61,7 @@ export class CacheService extends EventEmitter { return; } - const maxSize = config.getEnv('cache.memory.maxSize'); + const { maxSize, ttl } = this.globalConfig.cache.memory; const sizeCalculation = (item: unknown) => { const str = jsonStringify(item, { replaceCircularRefs: true }); @@ -66,10 +77,6 @@ export class CacheService extends EventEmitter { await this.cache.store.reset(); } - emit(event: CacheEvent, ...args: unknown[]) { - return super.emit(event, ...args); - } - isRedis() { return this.cache.kind === 'redis'; } diff --git a/packages/cli/src/services/cache/cache.types.ts b/packages/cli/src/services/cache/cache.types.ts index 4e96b8012a799..f598e22494446 100644 --- a/packages/cli/src/services/cache/cache.types.ts +++ b/packages/cli/src/services/cache/cache.types.ts @@ -8,5 +8,3 @@ export type TaggedMemoryCache = MemoryCache & { kind: 'memory' }; export type Hash = Record; export type MaybeHash = Hash | undefined; - -export type CacheEvent = `metrics.cache.${'hit' | 'miss' | 'update'}`; diff --git a/packages/cli/src/services/communityPackages.service.ts b/packages/cli/src/services/communityPackages.service.ts index 23bf9461d697d..b1f46d52fb434 100644 --- a/packages/cli/src/services/communityPackages.service.ts +++ b/packages/cli/src/services/communityPackages.service.ts @@ -5,6 +5,7 @@ import { Service } from 'typedi'; import { promisify } from 'util'; import axios from 'axios'; +import { GlobalConfig } from '@n8n/config'; import { ApplicationError, type PublicInstalledPackage } from 'n8n-workflow'; import { InstanceSettings } from 'n8n-core'; import type { PackageDirectoryLoader } from 'n8n-core'; @@ -22,6 +23,7 @@ import { import type { CommunityPackages } from '@/Interfaces'; import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials'; import { Logger } from '@/Logger'; +import { OrchestrationService } from './orchestration.service'; const { PACKAGE_NAME_NOT_PROVIDED, @@ -45,6 +47,8 @@ const INVALID_OR_SUSPICIOUS_PACKAGE_NAME = /[^0-9a-z@\-./]/; @Service() export class CommunityPackagesService { + reinstallMissingPackages = false; + missingPackages: string[] = []; constructor( @@ -52,7 +56,11 @@ export class CommunityPackagesService { private readonly logger: Logger, private readonly installedPackageRepository: InstalledPackagesRepository, private readonly loadNodesAndCredentials: LoadNodesAndCredentials, - ) {} + private readonly orchestrationService: OrchestrationService, + globalConfig: GlobalConfig, + ) { + this.reinstallMissingPackages = globalConfig.nodes.communityPackages.reinstallMissing; + } get hasMissingPackages() { return this.missingPackages.length > 0; @@ -73,11 +81,11 @@ export class CommunityPackagesService { return await this.installedPackageRepository.find({ relations: ['installedNodes'] }); } - async removePackageFromDatabase(packageName: InstalledPackages) { + private async removePackageFromDatabase(packageName: InstalledPackages) { return await this.installedPackageRepository.remove(packageName); } - async persistInstalledPackage(packageLoader: PackageDirectoryLoader) { + private async persistInstalledPackage(packageLoader: PackageDirectoryLoader) { try { return await this.installedPackageRepository.saveInstalledPackageWithNodes(packageLoader); } catch (maybeError) { @@ -251,7 +259,7 @@ export class CommunityPackagesService { } } - async setMissingPackages({ reinstallMissingPackages }: { reinstallMissingPackages: boolean }) { + async checkForMissingPackages() { const installedPackages = await this.getAllInstalledPackages(); const missingPackages = new Set<{ packageName: string; version: string }>(); @@ -271,24 +279,24 @@ export class CommunityPackagesService { if (missingPackages.size === 0) return; - this.logger.error( - 'n8n detected that some packages are missing. For more information, visit https://docs.n8n.io/integrations/community-nodes/troubleshooting/', - ); - - if (reinstallMissingPackages || process.env.N8N_REINSTALL_MISSING_PACKAGES) { + if (this.reinstallMissingPackages) { this.logger.info('Attempting to reinstall missing packages', { missingPackages }); try { // Optimistic approach - stop if any installation fails - for (const missingPackage of missingPackages) { - await this.installNpmModule(missingPackage.packageName, missingPackage.version); + await this.installPackage(missingPackage.packageName, missingPackage.version); missingPackages.delete(missingPackage); } this.logger.info('Packages reinstalled successfully. Resuming regular initialization.'); + await this.loadNodesAndCredentials.postProcessLoaders(); } catch (error) { this.logger.error('n8n was unable to install the missing packages.'); } + } else { + this.logger.warn( + 'n8n detected that some packages are missing. For more information, visit https://docs.n8n.io/integrations/community-nodes/troubleshooting/', + ); } this.missingPackages = [...missingPackages].map( @@ -296,32 +304,30 @@ export class CommunityPackagesService { ); } - async installNpmModule(packageName: string, version?: string): Promise { - return await this.installOrUpdateNpmModule(packageName, { version }); + async installPackage(packageName: string, version?: string): Promise { + return await this.installOrUpdatePackage(packageName, { version }); } - async updateNpmModule( + async updatePackage( packageName: string, installedPackage: InstalledPackages, ): Promise { - return await this.installOrUpdateNpmModule(packageName, { installedPackage }); + return await this.installOrUpdatePackage(packageName, { installedPackage }); } - async removeNpmModule(packageName: string, installedPackage: InstalledPackages): Promise { - await this.executeNpmCommand(`npm remove ${packageName}`); + async removePackage(packageName: string, installedPackage: InstalledPackages): Promise { + await this.removeNpmPackage(packageName); await this.removePackageFromDatabase(installedPackage); - await this.loadNodesAndCredentials.unloadPackage(packageName); - await this.loadNodesAndCredentials.postProcessLoaders(); + await this.orchestrationService.publish('community-package-uninstall', { packageName }); } - private async installOrUpdateNpmModule( + private async installOrUpdatePackage( packageName: string, options: { version?: string } | { installedPackage: InstalledPackages }, ) { const isUpdate = 'installedPackage' in options; - const command = isUpdate - ? `npm install ${packageName}@latest` - : `npm install ${packageName}${options.version ? `@${options.version}` : ''}`; + const packageVersion = isUpdate || !options.version ? 'latest' : options.version; + const command = `npm install ${packageName}@${packageVersion}`; try { await this.executeNpmCommand(command); @@ -337,9 +343,8 @@ export class CommunityPackagesService { loader = await this.loadNodesAndCredentials.loadPackage(packageName); } catch (error) { // Remove this package since loading it failed - const removeCommand = `npm remove ${packageName}`; try { - await this.executeNpmCommand(removeCommand); + await this.executeNpmCommand(`npm remove ${packageName}`); } catch {} throw new ApplicationError(RESPONSE_ERROR_MESSAGES.PACKAGE_LOADING_FAILED, { cause: error }); } @@ -351,7 +356,12 @@ export class CommunityPackagesService { await this.removePackageFromDatabase(options.installedPackage); } const installedPackage = await this.persistInstalledPackage(loader); + await this.orchestrationService.publish( + isUpdate ? 'community-package-update' : 'community-package-install', + { packageName, packageVersion }, + ); await this.loadNodesAndCredentials.postProcessLoaders(); + this.logger.info(`Community package installed: ${packageName}`); return installedPackage; } catch (error) { throw new ApplicationError('Failed to save installed package', { @@ -361,12 +371,24 @@ export class CommunityPackagesService { } } else { // Remove this package since it contains no loadable nodes - const removeCommand = `npm remove ${packageName}`; try { - await this.executeNpmCommand(removeCommand); + await this.executeNpmCommand(`npm remove ${packageName}`); } catch {} - throw new ApplicationError(RESPONSE_ERROR_MESSAGES.PACKAGE_DOES_NOT_CONTAIN_NODES); } } + + async installOrUpdateNpmPackage(packageName: string, packageVersion: string) { + await this.executeNpmCommand(`npm install ${packageName}@${packageVersion}`); + await this.loadNodesAndCredentials.loadPackage(packageName); + await this.loadNodesAndCredentials.postProcessLoaders(); + this.logger.info(`Community package installed: ${packageName}`); + } + + async removeNpmPackage(packageName: string) { + await this.executeNpmCommand(`npm remove ${packageName}`); + await this.loadNodesAndCredentials.unloadPackage(packageName); + await this.loadNodesAndCredentials.postProcessLoaders(); + this.logger.info(`Community package uninstalled: ${packageName}`); + } } diff --git a/packages/cli/src/services/frontend.service.ts b/packages/cli/src/services/frontend.service.ts index 918de1793518c..7cd9843c1d97f 100644 --- a/packages/cli/src/services/frontend.service.ts +++ b/packages/cli/src/services/frontend.service.ts @@ -66,7 +66,7 @@ export class FrontendService { private initSettings() { const instanceBaseUrl = this.urlService.getInstanceBaseUrl(); - const restEndpoint = config.getEnv('endpoints.rest'); + const restEndpoint = this.globalConfig.endpoints.rest; const telemetrySettings: ITelemetrySettings = { enabled: config.getEnv('diagnostics.enabled'), @@ -88,11 +88,11 @@ export class FrontendService { isDocker: this.isDocker(), databaseType: this.globalConfig.database.type, previewMode: process.env.N8N_PREVIEW_MODE === 'true', - endpointForm: config.getEnv('endpoints.form'), - endpointFormTest: config.getEnv('endpoints.formTest'), - endpointFormWaiting: config.getEnv('endpoints.formWaiting'), - endpointWebhook: config.getEnv('endpoints.webhook'), - endpointWebhookTest: config.getEnv('endpoints.webhookTest'), + endpointForm: this.globalConfig.endpoints.form, + endpointFormTest: this.globalConfig.endpoints.formTest, + endpointFormWaiting: this.globalConfig.endpoints.formWaiting, + endpointWebhook: this.globalConfig.endpoints.webhook, + endpointWebhookTest: this.globalConfig.endpoints.webhookTest, saveDataErrorExecution: config.getEnv('executions.saveDataOnError'), saveDataSuccessExecution: config.getEnv('executions.saveDataOnSuccess'), saveManualExecutions: config.getEnv('executions.saveDataManualExecutions'), @@ -244,9 +244,9 @@ export class FrontendService { } getSettings(pushRef?: string): IN8nUISettings { - void this.internalHooks.onFrontendSettingsAPI(pushRef); + this.internalHooks.onFrontendSettingsAPI(pushRef); - const restEndpoint = config.getEnv('endpoints.rest'); + const restEndpoint = this.globalConfig.endpoints.rest; // Update all urls, in case `WEBHOOK_URL` was updated by `--tunnel` const instanceBaseUrl = this.urlService.getInstanceBaseUrl(); diff --git a/packages/cli/src/services/orchestration.service.ts b/packages/cli/src/services/orchestration.service.ts index 3d74f187acced..283470f4d2114 100644 --- a/packages/cli/src/services/orchestration.service.ts +++ b/packages/cli/src/services/orchestration.service.ts @@ -7,11 +7,13 @@ import type { RedisServiceBaseCommand, RedisServiceCommand } from './redis/Redis import { RedisService } from './redis.service'; import { MultiMainSetup } from './orchestration/main/MultiMainSetup.ee'; import type { WorkflowActivateMode } from 'n8n-workflow'; +import { InstanceSettings } from 'n8n-core'; @Service() export class OrchestrationService { constructor( private readonly logger: Logger, + private readonly instanceSettings: InstanceSettings, private readonly redisService: RedisService, readonly multiMainSetup: MultiMainSetup, ) {} @@ -43,15 +45,14 @@ export class OrchestrationService { return config.getEnv('redis.queueModeId'); } - /** - * Whether this instance is the leader in a multi-main setup. Always `false` in single-main setup. - */ + /** @deprecated use InstanceSettings.isLeader */ get isLeader() { - return config.getEnv('multiMainSetup.instanceType') === 'leader'; + return this.instanceSettings.isLeader; } + /** @deprecated use InstanceSettings.isFollower */ get isFollower() { - return config.getEnv('multiMainSetup.instanceType') !== 'leader'; + return this.instanceSettings.isFollower; } sanityCheck() { @@ -66,7 +67,7 @@ export class OrchestrationService { if (this.isMultiMainSetupEnabled) { await this.multiMainSetup.init(); } else { - config.set('multiMainSetup.instanceType', 'leader'); + this.instanceSettings.markAsLeader(); } this.isInitialized = true; diff --git a/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts b/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts index 19cd14c4a87b9..89c6ef725ab45 100644 --- a/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts +++ b/packages/cli/src/services/orchestration/main/MultiMainSetup.ee.ts @@ -1,16 +1,23 @@ -import { EventEmitter } from 'node:events'; import config from '@/config'; import { Service } from 'typedi'; import { TIME } from '@/constants'; +import { InstanceSettings } from 'n8n-core'; import { ErrorReporterProxy as EventReporter } from 'n8n-workflow'; import { Logger } from '@/Logger'; import { RedisServicePubSubPublisher } from '@/services/redis/RedisServicePubSubPublisher'; import { RedisClientService } from '@/services/redis/redis-client.service'; +import { TypedEmitter } from '@/TypedEmitter'; + +type MultiMainEvents = { + 'leader-stepdown': never; + 'leader-takeover': never; +}; @Service() -export class MultiMainSetup extends EventEmitter { +export class MultiMainSetup extends TypedEmitter { constructor( private readonly logger: Logger, + private readonly instanceSettings: InstanceSettings, private readonly redisPublisher: RedisServicePubSubPublisher, private readonly redisClientService: RedisClientService, ) { @@ -45,7 +52,7 @@ export class MultiMainSetup extends EventEmitter { async shutdown() { clearInterval(this.leaderCheckInterval); - const isLeader = config.getEnv('multiMainSetup.instanceType') === 'leader'; + const { isLeader } = this.instanceSettings; if (isLeader) await this.redisPublisher.clear(this.leaderKey); } @@ -64,8 +71,8 @@ export class MultiMainSetup extends EventEmitter { if (leaderId && leaderId !== this.instanceId) { this.logger.debug(`[Instance ID ${this.instanceId}] Leader is other instance "${leaderId}"`); - if (config.getEnv('multiMainSetup.instanceType') === 'leader') { - config.set('multiMainSetup.instanceType', 'follower'); + if (this.instanceSettings.isLeader) { + this.instanceSettings.markAsFollower(); this.emit('leader-stepdown'); // lost leadership - stop triggers, pollers, pruning, wait-tracking, queue recovery @@ -80,7 +87,7 @@ export class MultiMainSetup extends EventEmitter { `[Instance ID ${this.instanceId}] Leadership vacant, attempting to become leader...`, ); - config.set('multiMainSetup.instanceType', 'follower'); + this.instanceSettings.markAsFollower(); /** * Lost leadership - stop triggers, pollers, pruning, wait tracking, license renewal, queue recovery @@ -101,7 +108,7 @@ export class MultiMainSetup extends EventEmitter { if (keySetSuccessfully) { this.logger.debug(`[Instance ID ${this.instanceId}] Leader is now this instance`); - config.set('multiMainSetup.instanceType', 'leader'); + this.instanceSettings.markAsLeader(); await this.redisPublisher.setExpiration(this.leaderKey, this.leaderKeyTtl); @@ -110,7 +117,7 @@ export class MultiMainSetup extends EventEmitter { */ this.emit('leader-takeover'); } else { - config.set('multiMainSetup.instanceType', 'follower'); + this.instanceSettings.markAsFollower(); } } diff --git a/packages/cli/src/services/orchestration/main/handleCommandMessageMain.ts b/packages/cli/src/services/orchestration/main/handleCommandMessageMain.ts index 8abcbe78b2c47..7945f59bc35f8 100644 --- a/packages/cli/src/services/orchestration/main/handleCommandMessageMain.ts +++ b/packages/cli/src/services/orchestration/main/handleCommandMessageMain.ts @@ -10,6 +10,7 @@ import { Push } from '@/push'; import { TestWebhooks } from '@/TestWebhooks'; import { OrchestrationService } from '@/services/orchestration.service'; import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; +import { CommunityPackagesService } from '@/services/communityPackages.service'; // eslint-disable-next-line complexity export async function handleCommandMessageMain(messageString: string) { @@ -77,6 +78,20 @@ export async function handleCommandMessageMain(messageString: string) { } await Container.get(ExternalSecretsManager).reloadAllProviders(); break; + case 'community-package-install': + case 'community-package-update': + case 'community-package-uninstall': + if (!debounceMessageReceiver(message, 200)) { + return message; + } + const { packageName, packageVersion } = message.payload; + const communityPackagesService = Container.get(CommunityPackagesService); + if (message.command === 'community-package-uninstall') { + await communityPackagesService.removeNpmPackage(packageName); + } else { + await communityPackagesService.installOrUpdateNpmPackage(packageName, packageVersion); + } + break; case 'add-webhooks-triggers-and-pollers': { if (!debounceMessageReceiver(message, 100)) { diff --git a/packages/cli/src/services/orchestration/webhook/handleCommandMessageWebhook.ts b/packages/cli/src/services/orchestration/webhook/handleCommandMessageWebhook.ts index 9dc326978d803..e6f6e656280a8 100644 --- a/packages/cli/src/services/orchestration/webhook/handleCommandMessageWebhook.ts +++ b/packages/cli/src/services/orchestration/webhook/handleCommandMessageWebhook.ts @@ -5,6 +5,7 @@ import Container from 'typedi'; import { Logger } from 'winston'; import { messageToRedisServiceCommandObject, debounceMessageReceiver } from '../helpers'; import config from '@/config'; +import { CommunityPackagesService } from '@/services/communityPackages.service'; export async function handleCommandMessageWebhook(messageString: string) { const queueModeId = config.getEnv('redis.queueModeId'); @@ -63,6 +64,20 @@ export async function handleCommandMessageWebhook(messageString: string) { } await Container.get(ExternalSecretsManager).reloadAllProviders(); break; + case 'community-package-install': + case 'community-package-update': + case 'community-package-uninstall': + if (!debounceMessageReceiver(message, 200)) { + return message; + } + const { packageName, packageVersion } = message.payload; + const communityPackagesService = Container.get(CommunityPackagesService); + if (message.command === 'community-package-uninstall') { + await communityPackagesService.removeNpmPackage(packageName); + } else { + await communityPackagesService.installOrUpdateNpmPackage(packageName, packageVersion); + } + break; default: break; diff --git a/packages/cli/src/services/orchestration/worker/handleCommandMessageWorker.ts b/packages/cli/src/services/orchestration/worker/handleCommandMessageWorker.ts index fa9ee67675e83..23c96e1a41cd9 100644 --- a/packages/cli/src/services/orchestration/worker/handleCommandMessageWorker.ts +++ b/packages/cli/src/services/orchestration/worker/handleCommandMessageWorker.ts @@ -10,6 +10,7 @@ import { debounceMessageReceiver, getOsCpuString } from '../helpers'; import type { WorkerCommandReceivedHandlerOptions } from './types'; import { Logger } from '@/Logger'; import { N8N_VERSION } from '@/constants'; +import { CommunityPackagesService } from '@/services/communityPackages.service'; export function getWorkerCommandReceivedHandler(options: WorkerCommandReceivedHandlerOptions) { // eslint-disable-next-line complexity @@ -112,6 +113,18 @@ export function getWorkerCommandReceivedHandler(options: WorkerCommandReceivedHa }); } break; + case 'community-package-install': + case 'community-package-update': + case 'community-package-uninstall': + if (!debounceMessageReceiver(message, 500)) return; + const { packageName, packageVersion } = message.payload; + const communityPackagesService = Container.get(CommunityPackagesService); + if (message.command === 'community-package-uninstall') { + await communityPackagesService.removeNpmPackage(packageName); + } else { + await communityPackagesService.installOrUpdateNpmPackage(packageName, packageVersion); + } + break; case 'reloadLicense': if (!debounceMessageReceiver(message, 500)) return; await Container.get(License).reload(); diff --git a/packages/cli/src/services/pruning.service.ts b/packages/cli/src/services/pruning.service.ts index daa0883e1f861..7f824836ae4ee 100644 --- a/packages/cli/src/services/pruning.service.ts +++ b/packages/cli/src/services/pruning.service.ts @@ -1,5 +1,5 @@ import { Service } from 'typedi'; -import { BinaryDataService } from 'n8n-core'; +import { BinaryDataService, InstanceSettings } from 'n8n-core'; import { inTest, TIME } from '@/constants'; import config from '@/config'; import { ExecutionRepository } from '@db/repositories/execution.repository'; @@ -25,6 +25,7 @@ export class PruningService { constructor( private readonly logger: Logger, + private readonly instanceSettings: InstanceSettings, private readonly executionRepository: ExecutionRepository, private readonly binaryDataService: BinaryDataService, private readonly orchestrationService: OrchestrationService, @@ -56,7 +57,7 @@ export class PruningService { if ( config.getEnv('multiMainSetup.enabled') && config.getEnv('generic.instanceType') === 'main' && - config.getEnv('multiMainSetup.instanceType') === 'follower' + this.instanceSettings.isFollower ) { return false; } diff --git a/packages/cli/src/services/redis/RedisServiceCommands.ts b/packages/cli/src/services/redis/RedisServiceCommands.ts index 009f39ef650eb..a8ae41c11390b 100644 --- a/packages/cli/src/services/redis/RedisServiceCommands.ts +++ b/packages/cli/src/services/redis/RedisServiceCommands.ts @@ -7,6 +7,9 @@ export type RedisServiceCommand = | 'stopWorker' | 'reloadLicense' | 'reloadExternalSecretsProviders' + | 'community-package-install' + | 'community-package-update' + | 'community-package-uninstall' | 'display-workflow-activation' // multi-main only | 'display-workflow-deactivation' // multi-main only | 'add-webhooks-triggers-and-pollers' // multi-main only @@ -26,7 +29,11 @@ export type RedisServiceBaseCommand = senderId: string; command: Exclude< RedisServiceCommand, - 'relay-execution-lifecycle-event' | 'clear-test-webhooks' + | 'relay-execution-lifecycle-event' + | 'clear-test-webhooks' + | 'community-package-install' + | 'community-package-update' + | 'community-package-uninstall' >; payload?: { [key: string]: string | number | boolean | string[] | number[] | boolean[]; @@ -41,6 +48,14 @@ export type RedisServiceBaseCommand = senderId: string; command: 'clear-test-webhooks'; payload: { webhookKey: string; workflowEntity: IWorkflowDb; pushRef: string }; + } + | { + senderId: string; + command: + | 'community-package-install' + | 'community-package-update' + | 'community-package-uninstall'; + payload: { packageName: string; packageVersion: string }; }; export type RedisServiceWorkerResponseObject = { diff --git a/packages/cli/src/services/user.service.ts b/packages/cli/src/services/user.service.ts index cc50e09f74607..61f76bd596d2f 100644 --- a/packages/cli/src/services/user.service.ts +++ b/packages/cli/src/services/user.service.ts @@ -12,7 +12,7 @@ import { InternalHooks } from '@/InternalHooks'; import { UrlService } from '@/services/url.service'; import type { UserRequest } from '@/requests'; import { InternalServerError } from '@/errors/response-errors/internal-server.error'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; @Service() export class UserService { @@ -144,27 +144,23 @@ export class UserService { if (result.emailSent) { invitedUser.user.emailSent = true; delete invitedUser.user?.inviteAcceptUrl; - void Container.get(InternalHooks).onUserTransactionalEmail({ + Container.get(InternalHooks).onUserTransactionalEmail({ user_id: id, message_type: 'New user invite', public_api: false, }); } - void Container.get(InternalHooks).onUserInvite({ - user: owner, - target_user_id: Object.values(toInviteUsers), - public_api: false, - email_sent: result.emailSent, - invitee_role: role, // same role for all invited users - }); this.eventService.emit('user-invited', { user: owner, targetUserId: Object.values(toInviteUsers), + publicApi: false, + emailSent: result.emailSent, + inviteeRole: role, // same role for all invited users }); } catch (e) { if (e instanceof Error) { - void Container.get(InternalHooks).onEmailFailed({ + Container.get(InternalHooks).onEmailFailed({ user: owner, message_type: 'New user invite', public_api: false, diff --git a/packages/cli/src/services/workflow-statistics.service.ts b/packages/cli/src/services/workflow-statistics.service.ts index 516732add87b4..9311ef885dfbd 100644 --- a/packages/cli/src/services/workflow-statistics.service.ts +++ b/packages/cli/src/services/workflow-statistics.service.ts @@ -1,29 +1,48 @@ -import { EventEmitter } from 'events'; -import { Container, Service } from 'typedi'; +import { Service } from 'typedi'; import type { INode, IRun, IWorkflowBase } from 'n8n-workflow'; import { StatisticsNames } from '@db/entities/WorkflowStatistics'; import { WorkflowStatisticsRepository } from '@db/repositories/workflowStatistics.repository'; import { UserService } from '@/services/user.service'; import { Logger } from '@/Logger'; import { OwnershipService } from './ownership.service'; +import { TypedEmitter } from '@/TypedEmitter'; + +type WorkflowStatisticsEvents = { + nodeFetchedData: { workflowId: string; node: INode }; + workflowExecutionCompleted: { workflowData: IWorkflowBase; fullRunData: IRun }; + 'telemetry.onFirstProductionWorkflowSuccess': { + project_id: string; + workflow_id: string; + user_id: string; + }; + 'telemetry.onFirstWorkflowDataLoad': { + user_id: string; + project_id: string; + workflow_id: string; + node_type: string; + node_id: string; + }; +}; @Service() -export class WorkflowStatisticsService extends EventEmitter { +export class WorkflowStatisticsService extends TypedEmitter { constructor( private readonly logger: Logger, private readonly repository: WorkflowStatisticsRepository, private readonly ownershipService: OwnershipService, + private readonly userService: UserService, ) { super({ captureRejections: true }); if ('SKIP_STATISTICS_EVENTS' in process.env) return; this.on( 'nodeFetchedData', - async (workflowId, node) => await this.nodeFetchedData(workflowId, node), + async ({ workflowId, node }) => await this.nodeFetchedData(workflowId, node), ); this.on( 'workflowExecutionCompleted', - async (workflowData, runData) => await this.workflowExecutionCompleted(workflowData, runData), + async ({ workflowData, fullRunData }) => + await this.workflowExecutionCompleted(workflowData, fullRunData), ); } @@ -49,18 +68,18 @@ export class WorkflowStatisticsService extends EventEmitter { const upsertResult = await this.repository.upsertWorkflowStatistics(name, workflowId); if (name === StatisticsNames.productionSuccess && upsertResult === 'insert') { - const project = await Container.get(OwnershipService).getWorkflowProjectCached(workflowId); + const project = await this.ownershipService.getWorkflowProjectCached(workflowId); if (project.type === 'personal') { - const owner = await Container.get(OwnershipService).getProjectOwnerCached(project.id); + const owner = await this.ownershipService.getProjectOwnerCached(project.id); const metrics = { project_id: project.id, workflow_id: workflowId, - user_id: owner?.id, + user_id: owner!.id, }; if (owner && !owner.settings?.userActivated) { - await Container.get(UserService).updateSettings(owner.id, { + await this.userService.updateSettings(owner.id, { firstSuccessfulWorkflowId: workflowId, userActivated: true, userActivatedAt: runData.startedAt.getTime(), @@ -90,7 +109,7 @@ export class WorkflowStatisticsService extends EventEmitter { const owner = await this.ownershipService.getProjectOwnerCached(project.id); let metrics = { - user_id: owner?.id, + user_id: owner!.id, project_id: project.id, workflow_id: workflowId, node_type: node.type, @@ -111,29 +130,3 @@ export class WorkflowStatisticsService extends EventEmitter { this.emit('telemetry.onFirstWorkflowDataLoad', metrics); } } - -export declare interface WorkflowStatisticsService { - on( - event: 'nodeFetchedData', - listener: (workflowId: string | undefined | null, node: INode) => void, - ): this; - on( - event: 'workflowExecutionCompleted', - listener: (workflowData: IWorkflowBase, runData: IRun) => void, - ): this; - on( - event: 'telemetry.onFirstProductionWorkflowSuccess', - listener: (metrics: { user_id: string; workflow_id: string }) => void, - ): this; - on( - event: 'telemetry.onFirstWorkflowDataLoad', - listener: (metrics: { - user_id: string; - workflow_id: string; - node_type: string; - node_id: string; - credential_type?: string; - credential_id?: string; - }) => void, - ): this; -} diff --git a/packages/cli/test/unit/shutdown/Shutdown.service.test.ts b/packages/cli/src/shutdown/__tests__/Shutdown.service.test.ts similarity index 100% rename from packages/cli/test/unit/shutdown/Shutdown.service.test.ts rename to packages/cli/src/shutdown/__tests__/Shutdown.service.test.ts diff --git a/packages/cli/test/unit/sso/saml/saml.service.ee.test.ts b/packages/cli/src/sso/saml/__tests__/saml.service.ee.test.ts similarity index 97% rename from packages/cli/test/unit/sso/saml/saml.service.ee.test.ts rename to packages/cli/src/sso/saml/__tests__/saml.service.ee.test.ts index 9ba6ddaf2a6bd..89d693fc98037 100644 --- a/packages/cli/test/unit/sso/saml/saml.service.ee.test.ts +++ b/packages/cli/src/sso/saml/__tests__/saml.service.ee.test.ts @@ -1,7 +1,7 @@ import { mock } from 'jest-mock-extended'; import type express from 'express'; import { SamlService } from '@/sso/saml/saml.service.ee'; -import { mockInstance } from '../../../shared/mocking'; +import { mockInstance } from '@test/mocking'; import { UrlService } from '@/services/url.service'; import { Logger } from '@/Logger'; import type { IdentityProviderInstance, ServiceProviderInstance } from 'samlify'; diff --git a/packages/cli/test/unit/sso/saml/samlHelpers.test.ts b/packages/cli/src/sso/saml/__tests__/samlHelpers.test.ts similarity index 96% rename from packages/cli/test/unit/sso/saml/samlHelpers.test.ts rename to packages/cli/src/sso/saml/__tests__/samlHelpers.test.ts index f6c35ff67e514..778b1a0857156 100644 --- a/packages/cli/test/unit/sso/saml/samlHelpers.test.ts +++ b/packages/cli/src/sso/saml/__tests__/samlHelpers.test.ts @@ -2,7 +2,7 @@ import { User } from '@/databases/entities/User'; import { generateNanoId } from '@/databases/utils/generators'; import * as helpers from '@/sso/saml/samlHelpers'; import type { SamlUserAttributes } from '@/sso/saml/types/samlUserAttributes'; -import { mockInstance } from '../../../shared/mocking'; +import { mockInstance } from '@test/mocking'; import { UserRepository } from '@/databases/repositories/user.repository'; import type { AuthIdentity } from '@/databases/entities/AuthIdentity'; import { AuthIdentityRepository } from '@/databases/repositories/authIdentity.repository'; diff --git a/packages/cli/src/sso/saml/routes/saml.controller.ee.ts b/packages/cli/src/sso/saml/routes/saml.controller.ee.ts index 344bd34e92b72..8169ee317bfee 100644 --- a/packages/cli/src/sso/saml/routes/saml.controller.ee.ts +++ b/packages/cli/src/sso/saml/routes/saml.controller.ee.ts @@ -27,7 +27,7 @@ import { import { SamlService } from '../saml.service.ee'; import { SamlConfiguration } from '../types/requests'; import { getInitSSOFormView } from '../views/initSsoPost'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; @RestController('/sso/saml') export class SamlController { diff --git a/packages/cli/test/unit/Telemetry.test.ts b/packages/cli/src/telemetry/__tests__/telemetry.test.ts similarity index 85% rename from packages/cli/test/unit/Telemetry.test.ts rename to packages/cli/src/telemetry/__tests__/telemetry.test.ts index 25f4a808675b0..a0441519eaf6f 100644 --- a/packages/cli/test/unit/Telemetry.test.ts +++ b/packages/cli/src/telemetry/__tests__/telemetry.test.ts @@ -1,11 +1,11 @@ import type RudderStack from '@rudderstack/rudder-sdk-node'; import { Telemetry } from '@/telemetry'; import config from '@/config'; -import { flushPromises } from './Helpers'; +import { flushPromises } from '@test/flushPromises'; import { PostHogClient } from '@/posthog'; import { mock } from 'jest-mock-extended'; import { InstanceSettings } from 'n8n-core'; -import { mockInstance } from '../shared/mocking'; +import { mockInstance } from '@test/mocking'; jest.unmock('@/telemetry'); jest.mock('@/posthog'); @@ -14,11 +14,7 @@ describe('Telemetry', () => { let startPulseSpy: jest.SpyInstance; const spyTrack = jest.spyOn(Telemetry.prototype, 'track').mockName('track'); - const mockRudderStack: Pick = { - flush: (resolve) => resolve?.(), - identify: (data, resolve) => resolve?.(), - track: (data, resolve) => resolve?.(), - }; + const mockRudderStack = mock(); let telemetry: Telemetry; const instanceId = 'Telemetry unit test'; @@ -26,9 +22,9 @@ describe('Telemetry', () => { const instanceSettings = mockInstance(InstanceSettings, { instanceId }); beforeAll(() => { - startPulseSpy = jest - .spyOn(Telemetry.prototype as any, 'startPulse') - .mockImplementation(() => {}); + // @ts-expect-error Spying on private method + startPulseSpy = jest.spyOn(Telemetry.prototype, 'startPulse').mockImplementation(() => {}); + jest.useFakeTimers(); jest.setSystemTime(testDateTime); config.set('diagnostics.enabled', true); @@ -49,7 +45,8 @@ describe('Telemetry', () => { await postHog.init(); telemetry = new Telemetry(mock(), postHog, mock(), instanceSettings, mock()); - (telemetry as any).rudderStack = mockRudderStack; + // @ts-expect-error Assigning to private property + telemetry.rudderStack = mockRudderStack; }); afterEach(async () => { @@ -79,30 +76,30 @@ describe('Telemetry', () => { payload.is_manual = true; payload.success = true; const execTime1 = fakeJestSystemTime('2022-01-01 12:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); fakeJestSystemTime('2022-01-01 12:30:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.is_manual = false; payload.success = true; const execTime2 = fakeJestSystemTime('2022-01-01 13:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); fakeJestSystemTime('2022-01-01 12:30:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.is_manual = true; payload.success = false; const execTime3 = fakeJestSystemTime('2022-01-01 14:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); fakeJestSystemTime('2022-01-01 12:30:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.is_manual = false; payload.success = false; const execTime4 = fakeJestSystemTime('2022-01-01 15:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); fakeJestSystemTime('2022-01-01 12:30:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(0); @@ -127,9 +124,9 @@ describe('Telemetry', () => { }; const execTime1 = fakeJestSystemTime('2022-01-01 12:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); fakeJestSystemTime('2022-01-01 12:30:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); let execBuffer = telemetry.getCountsBuffer(); @@ -140,9 +137,9 @@ describe('Telemetry', () => { payload.error_node_type = 'n8n-nodes-base.node-type'; fakeJestSystemTime('2022-01-01 13:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); fakeJestSystemTime('2022-01-01 12:30:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); execBuffer = telemetry.getCountsBuffer(); @@ -163,7 +160,7 @@ describe('Telemetry', () => { // successful execution const execTime1 = fakeJestSystemTime('2022-01-01 12:00:00'); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(0); @@ -179,7 +176,7 @@ describe('Telemetry', () => { payload.error_node_type = 'n8n-nodes-base.merge'; payload.workflow_id = '2'; - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(0); @@ -198,12 +195,12 @@ describe('Telemetry', () => { payload.error_node_type = 'n8n-nodes-base.merge'; payload.workflow_id = '2'; - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.error_node_type = 'n8n-nodes-base.merge'; payload.workflow_id = '1'; - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(0); execBuffer = telemetry.getCountsBuffer(); @@ -225,7 +222,7 @@ describe('Telemetry', () => { const execTime2 = fakeJestSystemTime('2022-01-01 12:00:00'); payload.error_node_type = 'custom-package.custom-node'; payload.success = false; - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(0); @@ -249,7 +246,7 @@ describe('Telemetry', () => { payload.success = false; payload.error_node_type = 'n8n-nodes-base.merge'; payload.is_manual = true; - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(1); @@ -327,27 +324,27 @@ describe('Telemetry', () => { error_node_type: 'custom-nodes-base.node-type', }; - await telemetry.trackWorkflowExecution(payload); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.is_manual = false; payload.success = true; - await telemetry.trackWorkflowExecution(payload); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.is_manual = true; payload.success = false; - await telemetry.trackWorkflowExecution(payload); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.is_manual = false; payload.success = false; - await telemetry.trackWorkflowExecution(payload); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); payload.workflow_id = '2'; - await telemetry.trackWorkflowExecution(payload); - await telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); + telemetry.trackWorkflowExecution(payload); expect(spyTrack).toHaveBeenCalledTimes(0); expect(pulseSpy).toBeCalledTimes(0); diff --git a/packages/cli/src/telemetry/index.ts b/packages/cli/src/telemetry/index.ts index f63caf9b2c7b6..2d762297304cc 100644 --- a/packages/cli/src/telemetry/index.ts +++ b/packages/cli/src/telemetry/index.ts @@ -88,30 +88,28 @@ export class Telemetry { ); // every 6 hours } - private async pulse(): Promise { + private async pulse() { if (!this.rudderStack) { return; } - const allPromises = Object.keys(this.executionCountsBuffer) - .filter((workflowId) => { - const data = this.executionCountsBuffer[workflowId]; - const sum = - (data.manual_error?.count ?? 0) + - (data.manual_success?.count ?? 0) + - (data.prod_error?.count ?? 0) + - (data.prod_success?.count ?? 0); - return sum > 0; - }) - .map(async (workflowId) => { - const promise = this.track('Workflow execution count', { - event_version: '2', - workflow_id: workflowId, - ...this.executionCountsBuffer[workflowId], - }); - - return await promise; + const workflowIdsToReport = Object.keys(this.executionCountsBuffer).filter((workflowId) => { + const data = this.executionCountsBuffer[workflowId]; + const sum = + (data.manual_error?.count ?? 0) + + (data.manual_success?.count ?? 0) + + (data.prod_error?.count ?? 0) + + (data.prod_success?.count ?? 0); + return sum > 0; + }); + + for (const workflowId of workflowIdsToReport) { + this.track('Workflow execution count', { + event_version: '2', + workflow_id: workflowId, + ...this.executionCountsBuffer[workflowId], }); + } this.executionCountsBuffer = {}; @@ -131,11 +129,11 @@ export class Telemetry { team_projects: (await Container.get(ProjectRepository).getProjectCounts()).team, project_role_count: await Container.get(ProjectRelationRepository).countUsersByRole(), }; - allPromises.push(this.track('pulse', pulsePacket)); - return await Promise.all(allPromises); + + this.track('pulse', pulsePacket); } - async trackWorkflowExecution(properties: IExecutionTrackProperties): Promise { + trackWorkflowExecution(properties: IExecutionTrackProperties) { if (this.rudderStack) { const execTime = new Date(); const workflowId = properties.workflow_id; @@ -164,66 +162,60 @@ export class Telemetry { properties.is_manual && properties.error_node_type?.startsWith('n8n-nodes-base') ) { - void this.track('Workflow execution errored', properties); + this.track('Workflow execution errored', properties); } } } async trackN8nStop(): Promise { clearInterval(this.pulseIntervalReference); - await this.track('User instance stopped'); - void Promise.all([this.postHog.stop(), this.rudderStack?.flush()]); + + this.track('User instance stopped'); + + await Promise.all([this.postHog.stop(), this.rudderStack?.flush()]); } - async identify(traits?: { - [key: string]: string | number | boolean | object | undefined | null; - }): Promise { + identify(traits?: { [key: string]: string | number | boolean | object | undefined | null }) { + if (!this.rudderStack) { + return; + } + const { instanceId } = this.instanceSettings; - return await new Promise((resolve) => { - if (this.rudderStack) { - this.rudderStack.identify( - { - userId: instanceId, - traits: { ...traits, instanceId }, - }, - resolve, - ); - } else { - resolve(); - } + + this.rudderStack.identify({ + userId: instanceId, + traits: { ...traits, instanceId }, }); } - async track( + track( eventName: string, properties: ITelemetryTrackProperties = {}, { withPostHog } = { withPostHog: false }, // whether to additionally track with PostHog - ): Promise { - const { instanceId } = this.instanceSettings; - return await new Promise((resolve) => { - if (this.rudderStack) { - const { user_id } = properties; - const updatedProperties = { - ...properties, - instance_id: instanceId, - version_cli: N8N_VERSION, - }; + ) { + if (!this.rudderStack) { + return; + } - const payload = { - userId: `${instanceId}${user_id ? `#${user_id}` : ''}`, - event: eventName, - properties: updatedProperties, - }; + const { instanceId } = this.instanceSettings; + const { user_id } = properties; + const updatedProperties = { + ...properties, + instance_id: instanceId, + version_cli: N8N_VERSION, + }; - if (withPostHog) { - this.postHog?.track(payload); - } + const payload = { + userId: `${instanceId}${user_id ? `#${user_id}` : ''}`, + event: eventName, + properties: updatedProperties, + }; - return this.rudderStack.track(payload, resolve); - } + if (withPostHog) { + this.postHog?.track(payload); + } - return resolve(); - }); + return this.rudderStack.track(payload); } // test helpers diff --git a/packages/cli/src/telemetry/telemetry-event-relay.service.ts b/packages/cli/src/telemetry/telemetry-event-relay.service.ts deleted file mode 100644 index 63c7fe4d11e74..0000000000000 --- a/packages/cli/src/telemetry/telemetry-event-relay.service.ts +++ /dev/null @@ -1,500 +0,0 @@ -import { Service } from 'typedi'; -import { EventService } from '@/eventbus/event.service'; -import type { Event } from '@/eventbus/event.types'; -import { Telemetry } from '.'; -import config from '@/config'; -import os from 'node:os'; -import { License } from '@/License'; -import { GlobalConfig } from '@n8n/config'; -import { N8N_VERSION } from '@/constants'; -import { WorkflowRepository } from '@/databases/repositories/workflow.repository'; - -@Service() -export class TelemetryEventRelay { - constructor( - private readonly eventService: EventService, - private readonly telemetry: Telemetry, - private readonly license: License, - private readonly globalConfig: GlobalConfig, - private readonly workflowRepository: WorkflowRepository, - ) {} - - async init() { - if (!config.getEnv('diagnostics.enabled')) return; - - await this.telemetry.init(); - - this.setupHandlers(); - } - - private setupHandlers() { - this.eventService.on('server-started', async () => await this.serverStarted()); - - this.eventService.on('team-project-updated', (event) => this.teamProjectUpdated(event)); - this.eventService.on('team-project-deleted', (event) => this.teamProjectDeleted(event)); - this.eventService.on('team-project-created', (event) => this.teamProjectCreated(event)); - this.eventService.on('source-control-settings-updated', (event) => - this.sourceControlSettingsUpdated(event), - ); - this.eventService.on('source-control-user-started-pull-ui', (event) => - this.sourceControlUserStartedPullUi(event), - ); - this.eventService.on('source-control-user-finished-pull-ui', (event) => - this.sourceControlUserFinishedPullUi(event), - ); - this.eventService.on('source-control-user-pulled-api', (event) => - this.sourceControlUserPulledApi(event), - ); - this.eventService.on('source-control-user-started-push-ui', (event) => - this.sourceControlUserStartedPushUi(event), - ); - this.eventService.on('source-control-user-finished-push-ui', (event) => - this.sourceControlUserFinishedPushUi(event), - ); - this.eventService.on('license-renewal-attempted', (event) => { - this.licenseRenewalAttempted(event); - }); - this.eventService.on('variable-created', () => this.variableCreated()); - this.eventService.on('external-secrets-provider-settings-saved', (event) => { - this.externalSecretsProviderSettingsSaved(event); - }); - this.eventService.on('public-api-invoked', (event) => { - this.publicApiInvoked(event); - }); - this.eventService.on('public-api-key-created', (event) => { - this.publicApiKeyCreated(event); - }); - this.eventService.on('public-api-key-deleted', (event) => { - this.publicApiKeyDeleted(event); - }); - this.eventService.on('community-package-installed', (event) => { - this.communityPackageInstalled(event); - }); - this.eventService.on('community-package-updated', (event) => { - this.communityPackageUpdated(event); - }); - this.eventService.on('community-package-deleted', (event) => { - this.communityPackageDeleted(event); - }); - - this.eventService.on('credentials-created', (event) => { - this.credentialsCreated(event); - }); - this.eventService.on('credentials-shared', (event) => { - this.credentialsShared(event); - }); - this.eventService.on('credentials-updated', (event) => { - this.credentialsUpdated(event); - }); - this.eventService.on('credentials-deleted', (event) => { - this.credentialsDeleted(event); - }); - this.eventService.on('ldap-general-sync-finished', (event) => { - this.ldapGeneralSyncFinished(event); - }); - this.eventService.on('ldap-settings-updated', (event) => { - this.ldapSettingsUpdated(event); - }); - this.eventService.on('ldap-login-sync-failed', (event) => { - this.ldapLoginSyncFailed(event); - }); - this.eventService.on('login-failed-due-to-ldap-disabled', (event) => { - this.loginFailedDueToLdapDisabled(event); - }); - } - - private teamProjectUpdated({ userId, role, members, projectId }: Event['team-project-updated']) { - void this.telemetry.track('Project settings updated', { - user_id: userId, - role, - // eslint-disable-next-line @typescript-eslint/no-shadow - members: members.map(({ userId: user_id, role }) => ({ user_id, role })), - project_id: projectId, - }); - } - - private teamProjectDeleted({ - userId, - role, - projectId, - removalType, - targetProjectId, - }: Event['team-project-deleted']) { - void this.telemetry.track('User deleted project', { - user_id: userId, - role, - project_id: projectId, - removal_type: removalType, - target_project_id: targetProjectId, - }); - } - - private teamProjectCreated({ userId, role }: Event['team-project-created']) { - void this.telemetry.track('User created project', { - user_id: userId, - role, - }); - } - - private sourceControlSettingsUpdated({ - branchName, - readOnlyInstance, - repoType, - connected, - }: Event['source-control-settings-updated']) { - void this.telemetry.track('User updated source control settings', { - branch_name: branchName, - read_only_instance: readOnlyInstance, - repo_type: repoType, - connected, - }); - } - - private sourceControlUserStartedPullUi({ - workflowUpdates, - workflowConflicts, - credConflicts, - }: Event['source-control-user-started-pull-ui']) { - void this.telemetry.track('User started pull via UI', { - workflow_updates: workflowUpdates, - workflow_conflicts: workflowConflicts, - cred_conflicts: credConflicts, - }); - } - - private sourceControlUserFinishedPullUi({ - workflowUpdates, - }: Event['source-control-user-finished-pull-ui']) { - void this.telemetry.track('User finished pull via UI', { - workflow_updates: workflowUpdates, - }); - } - - private sourceControlUserPulledApi({ - workflowUpdates, - forced, - }: Event['source-control-user-pulled-api']) { - console.log('source-control-user-pulled-api', { - workflow_updates: workflowUpdates, - forced, - }); - void this.telemetry.track('User pulled via API', { - workflow_updates: workflowUpdates, - forced, - }); - } - - private sourceControlUserStartedPushUi({ - workflowsEligible, - workflowsEligibleWithConflicts, - credsEligible, - credsEligibleWithConflicts, - variablesEligible, - }: Event['source-control-user-started-push-ui']) { - void this.telemetry.track('User started push via UI', { - workflows_eligible: workflowsEligible, - workflows_eligible_with_conflicts: workflowsEligibleWithConflicts, - creds_eligible: credsEligible, - creds_eligible_with_conflicts: credsEligibleWithConflicts, - variables_eligible: variablesEligible, - }); - } - - private sourceControlUserFinishedPushUi({ - workflowsEligible, - workflowsPushed, - credsPushed, - variablesPushed, - }: Event['source-control-user-finished-push-ui']) { - void this.telemetry.track('User finished push via UI', { - workflows_eligible: workflowsEligible, - workflows_pushed: workflowsPushed, - creds_pushed: credsPushed, - variables_pushed: variablesPushed, - }); - } - - private licenseRenewalAttempted({ success }: Event['license-renewal-attempted']) { - void this.telemetry.track('Instance attempted to refresh license', { - success, - }); - } - - private variableCreated() { - void this.telemetry.track('User created variable'); - } - - private externalSecretsProviderSettingsSaved({ - userId, - vaultType, - isValid, - isNew, - errorMessage, - }: Event['external-secrets-provider-settings-saved']) { - void this.telemetry.track('User updated external secrets settings', { - user_id: userId, - vault_type: vaultType, - is_valid: isValid, - is_new: isNew, - error_message: errorMessage, - }); - } - - private publicApiInvoked({ userId, path, method, apiVersion }: Event['public-api-invoked']) { - void this.telemetry.track('User invoked API', { - user_id: userId, - path, - method, - api_version: apiVersion, - }); - } - - private publicApiKeyCreated(event: Event['public-api-key-created']) { - const { user, publicApi } = event; - - void this.telemetry.track('API key created', { - user_id: user.id, - public_api: publicApi, - }); - } - - private publicApiKeyDeleted(event: Event['public-api-key-deleted']) { - const { user, publicApi } = event; - - void this.telemetry.track('API key deleted', { - user_id: user.id, - public_api: publicApi, - }); - } - - private communityPackageInstalled({ - user, - inputString, - packageName, - success, - packageVersion, - packageNodeNames, - packageAuthor, - packageAuthorEmail, - failureReason, - }: Event['community-package-installed']) { - void this.telemetry.track('cnr package install finished', { - user_id: user.id, - input_string: inputString, - package_name: packageName, - success, - package_version: packageVersion, - package_node_names: packageNodeNames, - package_author: packageAuthor, - package_author_email: packageAuthorEmail, - failure_reason: failureReason, - }); - } - - private communityPackageUpdated({ - user, - packageName, - packageVersionCurrent, - packageVersionNew, - packageNodeNames, - packageAuthor, - packageAuthorEmail, - }: Event['community-package-updated']) { - void this.telemetry.track('cnr package updated', { - user_id: user.id, - package_name: packageName, - package_version_current: packageVersionCurrent, - package_version_new: packageVersionNew, - package_node_names: packageNodeNames, - package_author: packageAuthor, - package_author_email: packageAuthorEmail, - }); - } - - private communityPackageDeleted({ - user, - packageName, - packageVersion, - packageNodeNames, - packageAuthor, - packageAuthorEmail, - }: Event['community-package-deleted']) { - void this.telemetry.track('cnr package deleted', { - user_id: user.id, - package_name: packageName, - package_version: packageVersion, - package_node_names: packageNodeNames, - package_author: packageAuthor, - package_author_email: packageAuthorEmail, - }); - } - - private credentialsCreated({ - user, - credentialType, - credentialId, - projectId, - projectType, - }: Event['credentials-created']) { - void this.telemetry.track('User created credentials', { - user_id: user.id, - credential_type: credentialType, - credential_id: credentialId, - project_id: projectId, - project_type: projectType, - }); - } - - private credentialsShared({ - user, - credentialType, - credentialId, - userIdSharer, - userIdsShareesAdded, - shareesRemoved, - }: Event['credentials-shared']) { - void this.telemetry.track('User updated cred sharing', { - user_id: user.id, - credential_type: credentialType, - credential_id: credentialId, - user_id_sharer: userIdSharer, - user_ids_sharees_added: userIdsShareesAdded, - sharees_removed: shareesRemoved, - }); - } - - private credentialsUpdated({ user, credentialId, credentialType }: Event['credentials-updated']) { - void this.telemetry.track('User updated credentials', { - user_id: user.id, - credential_type: credentialType, - credential_id: credentialId, - }); - } - - private credentialsDeleted({ user, credentialId, credentialType }: Event['credentials-deleted']) { - void this.telemetry.track('User deleted credentials', { - user_id: user.id, - credential_type: credentialType, - credential_id: credentialId, - }); - } - - private ldapGeneralSyncFinished({ - type, - succeeded, - usersSynced, - error, - }: Event['ldap-general-sync-finished']) { - void this.telemetry.track('Ldap general sync finished', { - type, - succeeded, - users_synced: usersSynced, - error, - }); - } - - private ldapSettingsUpdated({ - userId, - loginIdAttribute, - firstNameAttribute, - lastNameAttribute, - emailAttribute, - ldapIdAttribute, - searchPageSize, - searchTimeout, - synchronizationEnabled, - synchronizationInterval, - loginLabel, - loginEnabled, - }: Event['ldap-settings-updated']) { - void this.telemetry.track('User updated Ldap settings', { - user_id: userId, - loginIdAttribute, - firstNameAttribute, - lastNameAttribute, - emailAttribute, - ldapIdAttribute, - searchPageSize, - searchTimeout, - synchronizationEnabled, - synchronizationInterval, - loginLabel, - loginEnabled, - }); - } - - private ldapLoginSyncFailed({ error }: Event['ldap-login-sync-failed']) { - void this.telemetry.track('Ldap login sync failed', { error }); - } - - private loginFailedDueToLdapDisabled({ userId }: Event['login-failed-due-to-ldap-disabled']) { - void this.telemetry.track('User login failed since ldap disabled', { user_ud: userId }); - } - - private async serverStarted() { - const cpus = os.cpus(); - const binaryDataConfig = config.getEnv('binaryDataManager'); - - const isS3Selected = config.getEnv('binaryDataManager.mode') === 's3'; - const isS3Available = config.getEnv('binaryDataManager.availableModes').includes('s3'); - const isS3Licensed = this.license.isBinaryDataS3Licensed(); - const authenticationMethod = config.getEnv('userManagement.authenticationMethod'); - - const info = { - version_cli: N8N_VERSION, - db_type: this.globalConfig.database.type, - n8n_version_notifications_enabled: this.globalConfig.versionNotifications.enabled, - n8n_disable_production_main_process: config.getEnv( - 'endpoints.disableProductionWebhooksOnMainProcess', - ), - system_info: { - os: { - type: os.type(), - version: os.version(), - }, - memory: os.totalmem() / 1024, - cpus: { - count: cpus.length, - model: cpus[0].model, - speed: cpus[0].speed, - }, - }, - execution_variables: { - executions_mode: config.getEnv('executions.mode'), - executions_timeout: config.getEnv('executions.timeout'), - executions_timeout_max: config.getEnv('executions.maxTimeout'), - executions_data_save_on_error: config.getEnv('executions.saveDataOnError'), - executions_data_save_on_success: config.getEnv('executions.saveDataOnSuccess'), - executions_data_save_on_progress: config.getEnv('executions.saveExecutionProgress'), - executions_data_save_manual_executions: config.getEnv( - 'executions.saveDataManualExecutions', - ), - executions_data_prune: config.getEnv('executions.pruneData'), - executions_data_max_age: config.getEnv('executions.pruneDataMaxAge'), - }, - n8n_deployment_type: config.getEnv('deployment.type'), - n8n_binary_data_mode: binaryDataConfig.mode, - smtp_set_up: this.globalConfig.userManagement.emails.mode === 'smtp', - ldap_allowed: authenticationMethod === 'ldap', - saml_enabled: authenticationMethod === 'saml', - license_plan_name: this.license.getPlanName(), - license_tenant_id: config.getEnv('license.tenantId'), - binary_data_s3: isS3Available && isS3Selected && isS3Licensed, - multi_main_setup_enabled: config.getEnv('multiMainSetup.enabled'), - }; - - const firstWorkflow = await this.workflowRepository.findOne({ - select: ['createdAt'], - order: { createdAt: 'ASC' }, - where: {}, - }); - - void Promise.all([ - this.telemetry.identify(info), - this.telemetry.track('Instance started', { - ...info, - earliest_workflow_created: firstWorkflow?.createdAt, - }), - ]); - } -} diff --git a/packages/cli/test/unit/workflow-execution.service.test.ts b/packages/cli/src/workflows/__tests__/workflow-execution.service.test.ts similarity index 100% rename from packages/cli/test/unit/workflow-execution.service.test.ts rename to packages/cli/src/workflows/__tests__/workflow-execution.service.test.ts diff --git a/packages/cli/src/workflows/workflow.service.ts b/packages/cli/src/workflows/workflow.service.ts index cf742acecef2e..20917e60cbc16 100644 --- a/packages/cli/src/workflows/workflow.service.ts +++ b/packages/cli/src/workflows/workflow.service.ts @@ -1,4 +1,4 @@ -import Container, { Service } from 'typedi'; +import { Service } from 'typedi'; import { NodeApiError } from 'n8n-workflow'; import pick from 'lodash/pick'; import omit from 'lodash/omit'; @@ -17,7 +17,6 @@ import { validateEntity } from '@/GenericHelpers'; import { ExternalHooks } from '@/ExternalHooks'; import { hasSharing, type ListQuery } from '@/requests'; import { TagService } from '@/services/tag.service'; -import { InternalHooks } from '@/InternalHooks'; import { OwnershipService } from '@/services/ownership.service'; import { WorkflowHistoryService } from './workflowHistory/workflowHistory.service.ee'; import { Logger } from '@/Logger'; @@ -34,7 +33,7 @@ import type { EntityManager } from '@n8n/typeorm'; // eslint-disable-next-line n8n-local-rules/misplaced-n8n-typeorm-import import { In } from '@n8n/typeorm'; import { SharedWorkflow } from '@/databases/entities/SharedWorkflow'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; @Service() export class WorkflowService { @@ -219,11 +218,10 @@ export class WorkflowService { } await this.externalHooks.run('workflow.afterUpdate', [updatedWorkflow]); - void Container.get(InternalHooks).onWorkflowSaved(user, updatedWorkflow, false); this.eventService.emit('workflow-saved', { user, - workflowId: updatedWorkflow.id, - workflowName: updatedWorkflow.name, + workflow: updatedWorkflow, + publicApi: false, }); if (updatedWorkflow.active) { @@ -282,8 +280,7 @@ export class WorkflowService { await this.workflowRepository.delete(workflowId); await this.binaryDataService.deleteMany(idsForDeletion); - void Container.get(InternalHooks).onWorkflowDeleted(user, workflowId, false); - this.eventService.emit('workflow-deleted', { user, workflowId }); + this.eventService.emit('workflow-deleted', { user, workflowId, publicApi: false }); await this.externalHooks.run('workflow.afterDelete', [workflowId]); return workflow; diff --git a/packages/cli/test/unit/services/workflowHistory.service.ee.test.ts b/packages/cli/src/workflows/workflowHistory/__tests__/workflowHistory.service.ee.test.ts similarity index 96% rename from packages/cli/test/unit/services/workflowHistory.service.ee.test.ts rename to packages/cli/src/workflows/workflowHistory/__tests__/workflowHistory.service.ee.test.ts index 05ccae70051a2..5b28b8d171468 100644 --- a/packages/cli/test/unit/services/workflowHistory.service.ee.test.ts +++ b/packages/cli/src/workflows/workflowHistory/__tests__/workflowHistory.service.ee.test.ts @@ -4,8 +4,8 @@ import { WorkflowHistoryRepository } from '@db/repositories/workflowHistory.repo import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.repository'; import { WorkflowHistoryService } from '@/workflows/workflowHistory/workflowHistory.service.ee'; import { Logger } from '@/Logger'; -import { mockInstance } from '../../shared/mocking'; -import { getWorkflow } from '../../integration/shared/workflow'; +import { mockInstance } from '@test/mocking'; +import { getWorkflow } from '@test-integration/workflow'; const workflowHistoryRepository = mockInstance(WorkflowHistoryRepository); const logger = mockInstance(Logger); diff --git a/packages/cli/test/unit/workflowHistoryHelper.test.ts b/packages/cli/src/workflows/workflowHistory/__tests__/workflowHistoryHelper.ee.test.ts similarity index 96% rename from packages/cli/test/unit/workflowHistoryHelper.test.ts rename to packages/cli/src/workflows/workflowHistory/__tests__/workflowHistoryHelper.ee.test.ts index 32a6cdd6f0d52..427f98188495b 100644 --- a/packages/cli/test/unit/workflowHistoryHelper.test.ts +++ b/packages/cli/src/workflows/workflowHistory/__tests__/workflowHistoryHelper.ee.test.ts @@ -1,7 +1,7 @@ import { License } from '@/License'; import config from '@/config'; import { getWorkflowHistoryPruneTime } from '@/workflows/workflowHistory/workflowHistoryHelper.ee'; -import { mockInstance } from '../shared/mocking'; +import { mockInstance } from '@test/mocking'; let licensePruneTime = -1; diff --git a/packages/cli/src/workflows/workflowSharing.service.ts b/packages/cli/src/workflows/workflowSharing.service.ts index 93ef76438f7b0..add5bb31b8fc9 100644 --- a/packages/cli/src/workflows/workflowSharing.service.ts +++ b/packages/cli/src/workflows/workflowSharing.service.ts @@ -27,11 +27,16 @@ export class WorkflowSharingService { async getSharedWorkflowIds( user: User, options: - | { scopes: Scope[] } - | { projectRoles: ProjectRole[]; workflowRoles: WorkflowSharingRole[] }, + | { scopes: Scope[]; projectId?: string } + | { projectRoles: ProjectRole[]; workflowRoles: WorkflowSharingRole[]; projectId?: string }, ): Promise { + const { projectId } = options; + if (user.hasGlobalScope('workflow:read')) { - const sharedWorkflows = await this.sharedWorkflowRepository.find({ select: ['workflowId'] }); + const sharedWorkflows = await this.sharedWorkflowRepository.find({ + select: ['workflowId'], + ...(projectId && { where: { projectId } }), + }); return sharedWorkflows.map(({ workflowId }) => workflowId); } diff --git a/packages/cli/src/workflows/workflows.controller.ts b/packages/cli/src/workflows/workflows.controller.ts index d0ec019586279..60dbbf8191c4f 100644 --- a/packages/cli/src/workflows/workflows.controller.ts +++ b/packages/cli/src/workflows/workflows.controller.ts @@ -42,7 +42,7 @@ import { In, type FindOptionsRelations } from '@n8n/typeorm'; import type { Project } from '@/databases/entities/Project'; import { ProjectRelationRepository } from '@/databases/repositories/projectRelation.repository'; import { z } from 'zod'; -import { EventService } from '@/eventbus/event.service'; +import { EventService } from '@/events/event.service'; import { GlobalConfig } from '@n8n/config'; @RestController('/workflows') @@ -179,8 +179,13 @@ export class WorkflowsController { delete savedWorkflowWithMetaData.shared; await this.externalHooks.run('workflow.afterCreate', [savedWorkflow]); - void this.internalHooks.onWorkflowCreated(req.user, newWorkflow, project!, false); - this.eventService.emit('workflow-created', { user: req.user, workflow: newWorkflow }); + this.eventService.emit('workflow-created', { + user: req.user, + workflow: newWorkflow, + publicApi: false, + projectId: project!.id, + projectType: project!.type, + }); const scopes = await this.workflowService.getWorkflowScopes(req.user, savedWorkflow.id); @@ -454,7 +459,7 @@ export class WorkflowsController { newShareeIds = toShare; }); - void this.internalHooks.onWorkflowSharingUpdate(workflowId, req.user.id, shareWithIds); + this.internalHooks.onWorkflowSharingUpdate(workflowId, req.user.id, shareWithIds); const projectsRelations = await this.projectRelationRepository.findBy({ projectId: In(newShareeIds), diff --git a/packages/cli/templates/form-trigger.handlebars b/packages/cli/templates/form-trigger.handlebars index a1abca87b81a4..67818629f57db 100644 --- a/packages/cli/templates/form-trigger.handlebars +++ b/packages/cli/templates/form-trigger.handlebars @@ -282,7 +282,10 @@ display: inline-block; } - @media only screen and (max-width: 400px) { + @media only screen and (max-width: 500px) { + body { + background-color: white; + } hr { display: block; } @@ -291,16 +294,16 @@ min-height: 100vh; padding: 24px; background-color: white; - border: 1px solid #dbdfe7; - border-radius: 8px; - box-shadow: 0px 4px 16px 0px #634dff0f; + border: 0px solid #dbdfe7; + border-radius: 0px; + box-shadow: 0px 0px 0px 0px white; } .card { padding: 0px; background-color: white; border: 0px solid #dbdfe7; border-radius: 0px; - box-shadow: 0px 0px 10px 0px #634dff0f; + box-shadow: 0px 0px 0px 0px white; margin-bottom: 0px; } } diff --git a/packages/cli/test/integration/ExternalSecrets/externalSecrets.api.test.ts b/packages/cli/test/integration/ExternalSecrets/externalSecrets.api.test.ts index 7a49e61fa1b01..190ab437fa5f6 100644 --- a/packages/cli/test/integration/ExternalSecrets/externalSecrets.api.test.ts +++ b/packages/cli/test/integration/ExternalSecrets/externalSecrets.api.test.ts @@ -21,7 +21,7 @@ import { TestFailProvider, } from '../../shared/ExternalSecrets/utils'; import type { SuperAgentTest } from '../shared/types'; -import type { EventService } from '@/eventbus/event.service'; +import type { EventService } from '@/events/event.service'; let authOwnerAgent: SuperAgentTest; let authMemberAgent: SuperAgentTest; diff --git a/packages/cli/test/integration/PermissionChecker.test.ts b/packages/cli/test/integration/PermissionChecker.test.ts index d5262a50d0672..ff10d0497439a 100644 --- a/packages/cli/test/integration/PermissionChecker.test.ts +++ b/packages/cli/test/integration/PermissionChecker.test.ts @@ -1,6 +1,6 @@ import { v4 as uuid } from 'uuid'; import { Container } from 'typedi'; -import type { INode } from 'n8n-workflow'; +import type { INode, INodeTypeData } from 'n8n-workflow'; import { randomInt } from 'n8n-workflow'; import type { User } from '@db/entities/User'; import { WorkflowRepository } from '@db/repositories/workflow.repository'; @@ -14,7 +14,6 @@ import { mockInstance } from '../shared/mocking'; import { randomCredentialPayload as randomCred } from '../integration/shared/random'; import * as testDb from '../integration/shared/testDb'; import type { SaveCredentialFunction } from '../integration/shared/types'; -import { mockNodeTypesData } from '../unit/Helpers'; import { affixRoleToSaveCredential } from '../integration/shared/db/credentials'; import { createOwner, createUser } from '../integration/shared/db/users'; import { SharedCredentialsRepository } from '@/databases/repositories/sharedCredentials.repository'; @@ -25,6 +24,36 @@ import { ProjectRepository } from '@/databases/repositories/project.repository'; const ownershipService = mockInstance(OwnershipService); +function mockNodeTypesData( + nodeNames: string[], + options?: { + addTrigger?: boolean; + }, +) { + return nodeNames.reduce((acc, nodeName) => { + return ( + (acc[`n8n-nodes-base.${nodeName}`] = { + sourcePath: '', + type: { + description: { + displayName: nodeName, + name: nodeName, + group: [], + description: '', + version: 1, + defaults: {}, + inputs: [], + outputs: [], + properties: [], + }, + trigger: options?.addTrigger ? async () => undefined : undefined, + }, + }), + acc + ); + }, {}); +} + const createWorkflow = async (nodes: INode[], workflowOwner?: User): Promise => { const workflowDetails = { id: randomInt(1, 10).toString(), diff --git a/packages/cli/test/integration/activation-errors.service.test.ts b/packages/cli/test/integration/activation-errors.service.test.ts index 7635660db3aca..5d56f0dc90d6a 100644 --- a/packages/cli/test/integration/activation-errors.service.test.ts +++ b/packages/cli/test/integration/activation-errors.service.test.ts @@ -1,8 +1,13 @@ import { ActivationErrorsService } from '@/ActivationErrors.service'; import { CacheService } from '@/services/cache/cache.service'; +import { GlobalConfig } from '@n8n/config'; +import { mockInstance } from '@test/mocking'; describe('ActivationErrorsService', () => { - const cacheService = new CacheService(); + const globalConfig = mockInstance(GlobalConfig, { + cache: { backend: 'memory', memory: { maxSize: 3 * 1024 * 1024, ttl: 3600 * 1000 } }, + }); + const cacheService = new CacheService(globalConfig); const activationErrorsService = new ActivationErrorsService(cacheService); const firstWorkflowId = 'GSG0etbfTA2CNPDX'; diff --git a/packages/cli/test/integration/commands/worker.cmd.test.ts b/packages/cli/test/integration/commands/worker.cmd.test.ts index 0ffad7bc056ec..54c15d381de95 100644 --- a/packages/cli/test/integration/commands/worker.cmd.test.ts +++ b/packages/cli/test/integration/commands/worker.cmd.test.ts @@ -15,7 +15,7 @@ import { type JobQueue, Queue } from '@/Queue'; import { setupTestCommand } from '@test-integration/utils/testCommand'; import { mockInstance } from '../../shared/mocking'; -import { AuditEventRelay } from '@/eventbus/audit-event-relay.service'; +import { LogStreamingEventRelay } from '@/events/log-streaming-event-relay'; config.set('executions.mode', 'queue'); config.set('binaryDataManager.availableModes', 'filesystem'); @@ -26,7 +26,7 @@ const externalHooks = mockInstance(ExternalHooks); const externalSecretsManager = mockInstance(ExternalSecretsManager); const license = mockInstance(License); const messageEventBus = mockInstance(MessageEventBus); -const auditEventRelay = mockInstance(AuditEventRelay); +const logStreamingEventRelay = mockInstance(LogStreamingEventRelay); const orchestrationHandlerWorkerService = mockInstance(OrchestrationHandlerWorkerService); const queue = mockInstance(Queue); const orchestrationWorkerService = mockInstance(OrchestrationWorkerService); @@ -45,7 +45,7 @@ test('worker initializes all its components', async () => { expect(externalHooks.init).toHaveBeenCalledTimes(1); expect(externalSecretsManager.init).toHaveBeenCalledTimes(1); expect(messageEventBus.initialize).toHaveBeenCalledTimes(1); - expect(auditEventRelay.init).toHaveBeenCalledTimes(1); + expect(logStreamingEventRelay.init).toHaveBeenCalledTimes(1); expect(queue.init).toHaveBeenCalledTimes(1); expect(queue.process).toHaveBeenCalledTimes(1); expect(orchestrationWorkerService.init).toHaveBeenCalledTimes(1); diff --git a/packages/cli/test/integration/community-packages.api.test.ts b/packages/cli/test/integration/community-packages.api.test.ts index 661dbcd0fe4e9..46c7efad03434 100644 --- a/packages/cli/test/integration/community-packages.api.test.ts +++ b/packages/cli/test/integration/community-packages.api.test.ts @@ -179,7 +179,7 @@ describe('POST /community-packages', () => { communityPackagesService.hasPackageLoaded.mockReturnValue(false); communityPackagesService.checkNpmPackageStatus.mockResolvedValue({ status: 'OK' }); communityPackagesService.parseNpmPackageName.mockReturnValue(parsedNpmPackageName); - communityPackagesService.installNpmModule.mockResolvedValue(mockPackage()); + communityPackagesService.installPackage.mockResolvedValue(mockPackage()); await authAgent.post('/community-packages').send({ name: mockPackageName() }).expect(200); @@ -219,7 +219,7 @@ describe('DELETE /community-packages', () => { await authAgent.delete('/community-packages').query({ name: mockPackageName() }).expect(200); - expect(communityPackagesService.removeNpmModule).toHaveBeenCalledTimes(1); + expect(communityPackagesService.removePackage).toHaveBeenCalledTimes(1); }); }); @@ -242,6 +242,6 @@ describe('PATCH /community-packages', () => { await authAgent.patch('/community-packages').send({ name: mockPackageName() }); - expect(communityPackagesService.updateNpmModule).toHaveBeenCalledTimes(1); + expect(communityPackagesService.updatePackage).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/cli/test/integration/controllers/oauth/oauth2.api.test.ts b/packages/cli/test/integration/controllers/oauth/oauth2.api.test.ts new file mode 100644 index 0000000000000..970727c2c8c64 --- /dev/null +++ b/packages/cli/test/integration/controllers/oauth/oauth2.api.test.ts @@ -0,0 +1,107 @@ +import { Container } from 'typedi'; +import { response as Response } from 'express'; +import nock from 'nock'; +import { parse as parseQs } from 'querystring'; + +import type { CredentialsEntity } from '@db/entities/CredentialsEntity'; +import type { User } from '@db/entities/User'; +import { CredentialsHelper } from '@/CredentialsHelper'; +import { OAuth2CredentialController } from '@/controllers/oauth/oAuth2Credential.controller'; + +import { createOwner } from '@test-integration/db/users'; +import { saveCredential } from '@test-integration/db/credentials'; +import * as testDb from '@test-integration/testDb'; +import { setupTestServer } from '@test-integration/utils'; +import type { SuperAgentTest } from '@test-integration/types'; + +describe('OAuth2 API', () => { + const testServer = setupTestServer({ endpointGroups: ['oauth2'] }); + + let owner: User; + let ownerAgent: SuperAgentTest; + let credential: CredentialsEntity; + const credentialData = { + clientId: 'client_id', + clientSecret: 'client_secret', + authUrl: 'https://test.domain/oauth2/auth', + accessTokenUrl: 'https://test.domain/oauth2/token', + authQueryParameters: 'access_type=offline', + }; + + CredentialsHelper.prototype.applyDefaultsAndOverwrites = (_, decryptedDataOriginal) => + decryptedDataOriginal; + + beforeAll(async () => { + owner = await createOwner(); + ownerAgent = testServer.authAgentFor(owner); + }); + + beforeEach(async () => { + await testDb.truncate(['SharedCredentials', 'Credentials']); + credential = await saveCredential( + { + name: 'Test', + type: 'testOAuth2Api', + data: credentialData, + }, + { + user: owner, + role: 'credential:owner', + }, + ); + }); + + it('should return a valid auth URL when the auth flow is initiated', async () => { + const controller = Container.get(OAuth2CredentialController); + const csrfSpy = jest.spyOn(controller, 'createCsrfState').mockClear(); + + const response = await ownerAgent + .get('/oauth2-credential/auth') + .query({ id: credential.id }) + .expect(200); + const authUrl = new URL(response.body.data); + expect(authUrl.hostname).toBe('test.domain'); + expect(authUrl.pathname).toBe('/oauth2/auth'); + + expect(csrfSpy).toHaveBeenCalled(); + const [_, state] = csrfSpy.mock.results[0].value; + expect(parseQs(authUrl.search.slice(1))).toEqual({ + access_type: 'offline', + client_id: 'client_id', + redirect_uri: 'http://localhost:5678/rest/oauth2-credential/callback', + response_type: 'code', + state, + scope: 'openid', + }); + }); + + it('should handle a valid callback without auth', async () => { + const controller = Container.get(OAuth2CredentialController); + const csrfSpy = jest.spyOn(controller, 'createCsrfState').mockClear(); + const renderSpy = (Response.render = jest.fn(function () { + this.end(); + })); + + await ownerAgent.get('/oauth2-credential/auth').query({ id: credential.id }).expect(200); + + const [_, state] = csrfSpy.mock.results[0].value; + + nock('https://test.domain').post('/oauth2/token').reply(200, { access_token: 'updated_token' }); + + await testServer.authlessAgent + .get('/oauth2-credential/callback') + .query({ code: 'auth_code', state }) + .expect(200); + + expect(renderSpy).toHaveBeenCalledWith('oauth-callback'); + + const updatedCredential = await Container.get(CredentialsHelper).getCredentials( + credential, + credential.type, + ); + expect(updatedCredential.getData()).toEqual({ + ...credentialData, + oauthTokenData: { access_token: 'updated_token' }, + }); + }); +}); diff --git a/packages/cli/test/integration/database/repositories/execution.repository.test.ts b/packages/cli/test/integration/database/repositories/execution.repository.test.ts index cfb897d627a56..e777f624299aa 100644 --- a/packages/cli/test/integration/database/repositories/execution.repository.test.ts +++ b/packages/cli/test/integration/database/repositories/execution.repository.test.ts @@ -52,5 +52,34 @@ describe('ExecutionRepository', () => { }); expect(executionData?.data).toEqual('[{"resultData":"1"},{}]'); }); + + it('should not create execution if execution data insert fails', async () => { + const executionRepo = Container.get(ExecutionRepository); + const executionDataRepo = Container.get(ExecutionDataRepository); + + const workflow = await createWorkflow({ settings: { executionOrder: 'v1' } }); + jest + .spyOn(executionDataRepo, 'createExecutionDataForExecution') + .mockRejectedValueOnce(new Error()); + + await expect( + async () => + await executionRepo.createNewExecution({ + workflowId: workflow.id, + data: { + //@ts-expect-error This is not needed for tests + resultData: {}, + }, + workflowData: workflow, + mode: 'manual', + startedAt: new Date(), + status: 'new', + finished: false, + }), + ).rejects.toThrow(); + + const executionEntities = await executionRepo.find(); + expect(executionEntities).toBeEmptyArray(); + }); }); }); diff --git a/packages/cli/test/integration/eventbus.ee.test.ts b/packages/cli/test/integration/eventbus.ee.test.ts index a79c1388b39ee..f21822683b854 100644 --- a/packages/cli/test/integration/eventbus.ee.test.ts +++ b/packages/cli/test/integration/eventbus.ee.test.ts @@ -200,7 +200,7 @@ test('should anonymize audit message to syslog ', async () => { 'message', async function handler005(msg: { command: string; data: any }) { if (msg.command === 'appendMessageToLog') { - const sent = await eventBus.getEventsAll(); + await eventBus.getEventsAll(); await confirmIdInAll(testAuditMessage.id); expect(mockedSyslogClientLog).toHaveBeenCalled(); eventBus.logWriter.worker?.removeListener('message', handler005); @@ -217,7 +217,7 @@ test('should anonymize audit message to syslog ', async () => { 'message', async function handler006(msg: { command: string; data: any }) { if (msg.command === 'appendMessageToLog') { - const sent = await eventBus.getEventsAll(); + await eventBus.getEventsAll(); await confirmIdInAll(testAuditMessage.id); expect(mockedSyslogClientLog).toHaveBeenCalled(); syslogDestination.disable(); diff --git a/packages/cli/test/integration/executions.controller.test.ts b/packages/cli/test/integration/executions.controller.test.ts index ef04dde99bd81..1e6d65a4f8368 100644 --- a/packages/cli/test/integration/executions.controller.test.ts +++ b/packages/cli/test/integration/executions.controller.test.ts @@ -12,7 +12,10 @@ import { WaitTracker } from '@/WaitTracker'; import { createTeamProject, linkUserToProject } from './shared/db/projects'; mockInstance(WaitTracker); -mockInstance(ConcurrencyControlService, { isEnabled: false }); +mockInstance(ConcurrencyControlService, { + // @ts-expect-error Private property + isEnabled: false, +}); const testServer = setupTestServer({ endpointGroups: ['executions'] }); diff --git a/packages/cli/test/integration/project.service.integration.test.ts b/packages/cli/test/integration/project.service.integration.test.ts index 77d388c1617d6..7fe55ade06747 100644 --- a/packages/cli/test/integration/project.service.integration.test.ts +++ b/packages/cli/test/integration/project.service.integration.test.ts @@ -31,7 +31,6 @@ describe('ProjectService', () => { describe('when user has roles in projects where workflow is accessible', () => { it('should return roles and project IDs', async () => { const user = await createUser(); - const secondUser = await createUser(); // @TODO: Needed only to satisfy index in legacy column const firstProject = await createTeamProject('Project 1'); const secondProject = await createTeamProject('Project 2'); @@ -42,17 +41,15 @@ describe('ProjectService', () => { const workflow = await createWorkflow(); await sharedWorkflowRepository.insert({ - userId: user.id, // @TODO: Legacy column projectId: firstProject.id, workflowId: workflow.id, role: 'workflow:owner', }); await sharedWorkflowRepository.insert({ - userId: secondUser.id, // @TODO: Legacy column projectId: secondProject.id, workflowId: workflow.id, - role: 'workflow:user', + role: 'workflow:owner', }); const projectIds = await projectService.findProjectsWorkflowIsIn(workflow.id); @@ -63,9 +60,6 @@ describe('ProjectService', () => { describe('when user has no roles in projects where workflow is accessible', () => { it('should return project IDs but no roles', async () => { - const user = await createUser(); - const secondUser = await createUser(); // @TODO: Needed only to satisfy index in legacy column - const firstProject = await createTeamProject('Project 1'); const secondProject = await createTeamProject('Project 2'); @@ -74,17 +68,15 @@ describe('ProjectService', () => { const workflow = await createWorkflow(); await sharedWorkflowRepository.insert({ - userId: user.id, // @TODO: Legacy column projectId: firstProject.id, workflowId: workflow.id, role: 'workflow:owner', }); await sharedWorkflowRepository.insert({ - userId: secondUser.id, // @TODO: Legacy column projectId: secondProject.id, workflowId: workflow.id, - role: 'workflow:user', + role: 'workflow:owner', }); const projectIds = await projectService.findProjectsWorkflowIsIn(workflow.id); diff --git a/packages/cli/test/integration/prometheus-metrics.test.ts b/packages/cli/test/integration/prometheus-metrics.test.ts index 68c8756a86ec0..1eccb9b7d0ce2 100644 --- a/packages/cli/test/integration/prometheus-metrics.test.ts +++ b/packages/cli/test/integration/prometheus-metrics.test.ts @@ -2,17 +2,30 @@ import { Container } from 'typedi'; import { parse as semverParse } from 'semver'; import request, { type Response } from 'supertest'; -import config from '@/config'; import { N8N_VERSION } from '@/constants'; import { PrometheusMetricsService } from '@/metrics/prometheus-metrics.service'; import { setupTestServer } from './shared/utils'; +import { GlobalConfig } from '@n8n/config'; jest.unmock('@/eventbus/MessageEventBus/MessageEventBus'); const toLines = (response: Response) => response.text.trim().split('\n'); -config.set('endpoints.metrics.enable', true); -config.set('endpoints.metrics.prefix', 'n8n_test_'); +const globalConfig = Container.get(GlobalConfig); +globalConfig.endpoints.metrics = { + enable: true, + prefix: 'n8n_test_', + includeDefaultMetrics: true, + includeApiEndpoints: true, + includeCacheMetrics: true, + includeMessageEventBusMetrics: true, + includeCredentialTypeLabel: false, + includeNodeTypeLabel: false, + includeWorkflowIdLabel: false, + includeApiPathLabel: true, + includeApiMethodLabel: true, + includeApiStatusCodeLabel: true, +}; const server = setupTestServer({ endpointGroups: ['metrics'] }); const agent = request.agent(server.app); diff --git a/packages/cli/test/integration/pruning.service.test.ts b/packages/cli/test/integration/pruning.service.test.ts index a600b4aabdcab..37d218c09d438 100644 --- a/packages/cli/test/integration/pruning.service.test.ts +++ b/packages/cli/test/integration/pruning.service.test.ts @@ -1,5 +1,5 @@ import config from '@/config'; -import { BinaryDataService } from 'n8n-core'; +import { BinaryDataService, InstanceSettings } from 'n8n-core'; import type { ExecutionStatus } from 'n8n-workflow'; import Container from 'typedi'; @@ -15,10 +15,11 @@ import { mockInstance } from '../shared/mocking'; import { createWorkflow } from './shared/db/workflows'; import { createExecution, createSuccessfulExecution } from './shared/db/executions'; import { mock } from 'jest-mock-extended'; -import type { OrchestrationService } from '@/services/orchestration.service'; describe('softDeleteOnPruningCycle()', () => { let pruningService: PruningService; + const instanceSettings = new InstanceSettings(); + instanceSettings.markAsLeader(); const now = new Date(); const yesterday = new Date(Date.now() - TIME.DAY); @@ -29,9 +30,10 @@ describe('softDeleteOnPruningCycle()', () => { pruningService = new PruningService( mockInstance(Logger), + instanceSettings, Container.get(ExecutionRepository), mockInstance(BinaryDataService), - mock(), + mock(), ); workflow = await createWorkflow(); @@ -98,7 +100,6 @@ describe('softDeleteOnPruningCycle()', () => { }); test.each<[ExecutionStatus, Partial]>([ - ['warning', { startedAt: now, stoppedAt: now }], ['unknown', { startedAt: now, stoppedAt: now }], ['canceled', { startedAt: now, stoppedAt: now }], ['crashed', { startedAt: now, stoppedAt: now }], @@ -191,7 +192,6 @@ describe('softDeleteOnPruningCycle()', () => { }); test.each<[ExecutionStatus, Partial]>([ - ['warning', { startedAt: yesterday, stoppedAt: yesterday }], ['unknown', { startedAt: yesterday, stoppedAt: yesterday }], ['canceled', { startedAt: yesterday, stoppedAt: yesterday }], ['crashed', { startedAt: yesterday, stoppedAt: yesterday }], diff --git a/packages/cli/test/integration/publicApi/credentials.test.ts b/packages/cli/test/integration/publicApi/credentials.test.ts index dfa6c44dd4037..b5ed2bfb31d82 100644 --- a/packages/cli/test/integration/publicApi/credentials.test.ts +++ b/packages/cli/test/integration/publicApi/credentials.test.ts @@ -9,9 +9,10 @@ import { randomApiKey, randomName } from '../shared/random'; import * as utils from '../shared/utils/'; import type { CredentialPayload, SaveCredentialFunction } from '../shared/types'; import * as testDb from '../shared/testDb'; -import { affixRoleToSaveCredential } from '../shared/db/credentials'; +import { affixRoleToSaveCredential, createCredentials } from '../shared/db/credentials'; import { addApiKey, createUser, createUserShell } from '../shared/db/users'; import type { SuperAgentTest } from '../shared/types'; +import { createTeamProject } from '@test-integration/db/projects'; let owner: User; let member: User; @@ -256,6 +257,53 @@ describe('GET /credentials/schema/:credentialType', () => { }); }); +describe('PUT /credentials/:id/transfer', () => { + test('should transfer credential to project', async () => { + /** + * Arrange + */ + const [firstProject, secondProject] = await Promise.all([ + createTeamProject('first-project', owner), + createTeamProject('second-project', owner), + ]); + + const credentials = await createCredentials( + { name: 'Test', type: 'test', data: '' }, + firstProject, + ); + + /** + * Act + */ + const response = await authOwnerAgent.put(`/credentials/${credentials.id}/transfer`).send({ + destinationProjectId: secondProject.id, + }); + + /** + * Assert + */ + expect(response.statusCode).toBe(204); + }); + + test('if no destination project, should reject', async () => { + /** + * Arrange + */ + const project = await createTeamProject('first-project', member); + const credentials = await createCredentials({ name: 'Test', type: 'test', data: '' }, project); + + /** + * Act + */ + const response = await authOwnerAgent.put(`/credentials/${credentials.id}/transfer`).send({}); + + /** + * Assert + */ + expect(response.statusCode).toBe(400); + }); +}); + const credentialPayload = (): CredentialPayload => ({ name: randomName(), type: 'githubApi', diff --git a/packages/cli/test/integration/publicApi/executions.test.ts b/packages/cli/test/integration/publicApi/executions.test.ts index 9961dac545036..8dac97fc22381 100644 --- a/packages/cli/test/integration/publicApi/executions.test.ts +++ b/packages/cli/test/integration/publicApi/executions.test.ts @@ -20,6 +20,8 @@ import { import type { SuperAgentTest } from '../shared/types'; import { mockInstance } from '@test/mocking'; import { Telemetry } from '@/telemetry'; +import { createTeamProject } from '@test-integration/db/projects'; +import type { ExecutionEntity } from '@/databases/entities/ExecutionEntity'; let owner: User; let user1: User; @@ -447,6 +449,42 @@ describe('GET /executions', () => { } }); + test('should return executions filtered by project ID', async () => { + /** + * Arrange + */ + const [firstProject, secondProject] = await Promise.all([ + createTeamProject(), + createTeamProject(), + ]); + const [firstWorkflow, secondWorkflow] = await Promise.all([ + createWorkflow({}, firstProject), + createWorkflow({}, secondProject), + ]); + const [firstExecution, secondExecution, _] = await Promise.all([ + createExecution({}, firstWorkflow), + createExecution({}, firstWorkflow), + createExecution({}, secondWorkflow), + ]); + + /** + * Act + */ + const response = await authOwnerAgent.get('/executions').query({ + projectId: firstProject.id, + }); + + /** + * Assert + */ + expect(response.statusCode).toBe(200); + expect(response.body.data.length).toBe(2); + expect(response.body.nextCursor).toBeNull(); + expect(response.body.data.map((execution: ExecutionEntity) => execution.id)).toEqual( + expect.arrayContaining([firstExecution.id, secondExecution.id]), + ); + }); + test('owner should retrieve all executions regardless of ownership', async () => { const [firstWorkflowForUser1, secondWorkflowForUser1] = await createManyWorkflows(2, {}, user1); await createManyExecutions(2, firstWorkflowForUser1, createSuccessfulExecution); diff --git a/packages/cli/test/integration/publicApi/projects.test.ts b/packages/cli/test/integration/publicApi/projects.test.ts new file mode 100644 index 0000000000000..0554fd6f4116f --- /dev/null +++ b/packages/cli/test/integration/publicApi/projects.test.ts @@ -0,0 +1,401 @@ +import { setupTestServer } from '@test-integration/utils'; +import { createMember, createOwner } from '@test-integration/db/users'; +import * as testDb from '../shared/testDb'; +import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error'; +import { createTeamProject, getProjectByNameOrFail } from '@test-integration/db/projects'; +import { mockInstance } from '@test/mocking'; +import { Telemetry } from '@/telemetry'; + +describe('Projects in Public API', () => { + const testServer = setupTestServer({ endpointGroups: ['publicApi'] }); + mockInstance(Telemetry); + + beforeAll(async () => { + await testDb.init(); + }); + + beforeEach(async () => { + await testDb.truncate(['Project', 'User']); + }); + + describe('GET /projects', () => { + it('if licensed, should return all projects with pagination', async () => { + /** + * Arrange + */ + testServer.license.setQuota('quota:maxTeamProjects', -1); + testServer.license.enable('feat:projectRole:admin'); + const owner = await createOwner({ withApiKey: true }); + const projects = await Promise.all([ + createTeamProject(), + createTeamProject(), + createTeamProject(), + ]); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).get('/projects'); + + /** + * Assert + */ + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('data'); + expect(response.body).toHaveProperty('nextCursor'); + expect(Array.isArray(response.body.data)).toBe(true); + expect(response.body.data.length).toBe(projects.length + 1); // +1 for the owner's personal project + + projects.forEach(({ id, name }) => { + expect(response.body.data).toContainEqual(expect.objectContaining({ id, name })); + }); + }); + + it('if not authenticated, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: false }); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).get('/projects'); + + /** + * Assert + */ + expect(response.status).toBe(401); + expect(response.body).toHaveProperty('message', "'X-N8N-API-KEY' header required"); + }); + + it('if not licensed, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: true }); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).get('/projects'); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty( + 'message', + new FeatureNotLicensedError('feat:projectRole:admin').message, + ); + }); + + it('if missing scope, should reject', async () => { + /** + * Arrange + */ + testServer.license.setQuota('quota:maxTeamProjects', -1); + testServer.license.enable('feat:projectRole:admin'); + const owner = await createMember({ withApiKey: true }); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).get('/projects'); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty('message', 'Forbidden'); + }); + }); + + describe('POST /projects', () => { + it('if licensed, should create a new project', async () => { + /** + * Arrange + */ + testServer.license.setQuota('quota:maxTeamProjects', -1); + testServer.license.enable('feat:projectRole:admin'); + const owner = await createOwner({ withApiKey: true }); + const projectPayload = { name: 'some-project' }; + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(owner) + .post('/projects') + .send(projectPayload); + + /** + * Assert + */ + expect(response.status).toBe(201); + expect(response.body).toEqual({ + name: 'some-project', + type: 'team', + id: expect.any(String), + createdAt: expect.any(String), + updatedAt: expect.any(String), + role: 'project:admin', + scopes: expect.any(Array), + }); + await expect(getProjectByNameOrFail(projectPayload.name)).resolves.not.toThrow(); + }); + + it('if not authenticated, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: false }); + const projectPayload = { name: 'some-project' }; + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(owner) + .post('/projects') + .send(projectPayload); + + /** + * Assert + */ + expect(response.status).toBe(401); + expect(response.body).toHaveProperty('message', "'X-N8N-API-KEY' header required"); + }); + + it('if not licensed, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: true }); + const projectPayload = { name: 'some-project' }; + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(owner) + .post('/projects') + .send(projectPayload); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty( + 'message', + new FeatureNotLicensedError('feat:projectRole:admin').message, + ); + }); + + it('if missing scope, should reject', async () => { + /** + * Arrange + */ + testServer.license.setQuota('quota:maxTeamProjects', -1); + testServer.license.enable('feat:projectRole:admin'); + const member = await createMember({ withApiKey: true }); + const projectPayload = { name: 'some-project' }; + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(member) + .post('/projects') + .send(projectPayload); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty('message', 'Forbidden'); + }); + }); + + describe('DELETE /projects/:id', () => { + it('if licensed, should delete a project', async () => { + /** + * Arrange + */ + testServer.license.setQuota('quota:maxTeamProjects', -1); + testServer.license.enable('feat:projectRole:admin'); + const owner = await createOwner({ withApiKey: true }); + const project = await createTeamProject(); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).delete(`/projects/${project.id}`); + + /** + * Assert + */ + expect(response.status).toBe(204); + await expect(getProjectByNameOrFail(project.id)).rejects.toThrow(); + }); + + it('if not authenticated, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: false }); + const project = await createTeamProject(); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).delete(`/projects/${project.id}`); + + /** + * Assert + */ + expect(response.status).toBe(401); + expect(response.body).toHaveProperty('message', "'X-N8N-API-KEY' header required"); + }); + + it('if not licensed, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: true }); + const project = await createTeamProject(); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).delete(`/projects/${project.id}`); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty( + 'message', + new FeatureNotLicensedError('feat:projectRole:admin').message, + ); + }); + + it('if missing scope, should reject', async () => { + /** + * Arrange + */ + testServer.license.setQuota('quota:maxTeamProjects', -1); + testServer.license.enable('feat:projectRole:admin'); + const member = await createMember({ withApiKey: true }); + const project = await createTeamProject(); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(member).delete(`/projects/${project.id}`); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty('message', 'Forbidden'); + }); + }); + + describe('PUT /projects/:id', () => { + it('if licensed, should update a project', async () => { + /** + * Arrange + */ + testServer.license.setQuota('quota:maxTeamProjects', -1); + testServer.license.enable('feat:projectRole:admin'); + const owner = await createOwner({ withApiKey: true }); + const project = await createTeamProject('old-name'); + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(owner) + .put(`/projects/${project.id}`) + .send({ name: 'new-name' }); + + /** + * Assert + */ + expect(response.status).toBe(204); + await expect(getProjectByNameOrFail('new-name')).resolves.not.toThrow(); + }); + + it('if not authenticated, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: false }); + const project = await createTeamProject(); + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(owner) + .put(`/projects/${project.id}`) + .send({ name: 'new-name' }); + + /** + * Assert + */ + expect(response.status).toBe(401); + expect(response.body).toHaveProperty('message', "'X-N8N-API-KEY' header required"); + }); + + it('if not licensed, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: true }); + const project = await createTeamProject(); + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(owner) + .put(`/projects/${project.id}`) + .send({ name: 'new-name' }); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty( + 'message', + new FeatureNotLicensedError('feat:projectRole:admin').message, + ); + }); + + it('if missing scope, should reject', async () => { + /** + * Arrange + */ + testServer.license.setQuota('quota:maxTeamProjects', -1); + testServer.license.enable('feat:projectRole:admin'); + const member = await createMember({ withApiKey: true }); + const project = await createTeamProject(); + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(member) + .put(`/projects/${project.id}`) + .send({ name: 'new-name' }); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty('message', 'Forbidden'); + }); + }); +}); diff --git a/packages/cli/test/integration/publicApi/users.ee.test.ts b/packages/cli/test/integration/publicApi/users.ee.test.ts index 6e57a629d7dc7..be23d8f45a519 100644 --- a/packages/cli/test/integration/publicApi/users.ee.test.ts +++ b/packages/cli/test/integration/publicApi/users.ee.test.ts @@ -7,8 +7,10 @@ import { mockInstance } from '../../shared/mocking'; import { randomApiKey } from '../shared/random'; import * as utils from '../shared/utils/'; import * as testDb from '../shared/testDb'; -import { createUser, createUserShell } from '../shared/db/users'; +import { createOwner, createUser, createUserShell } from '../shared/db/users'; import type { SuperAgentTest } from '../shared/types'; +import { createTeamProject, linkUserToProject } from '@test-integration/db/projects'; +import type { User } from '@/databases/entities/User'; mockInstance(License, { getUsersLimit: jest.fn().mockReturnValue(-1), @@ -84,6 +86,46 @@ describe('With license unlimited quota:users', () => { expect(updatedAt).toBeDefined(); } }); + + it('should return users filtered by project ID', async () => { + /** + * Arrange + */ + const [owner, firstMember, secondMember, thirdMember] = await Promise.all([ + createOwner({ withApiKey: true }), + createUser({ role: 'global:member' }), + createUser({ role: 'global:member' }), + createUser({ role: 'global:member' }), + ]); + + const [firstProject, secondProject] = await Promise.all([ + createTeamProject(), + createTeamProject(), + ]); + + await Promise.all([ + linkUserToProject(firstMember, firstProject, 'project:admin'), + linkUserToProject(secondMember, firstProject, 'project:viewer'), + linkUserToProject(thirdMember, secondProject, 'project:admin'), + ]); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).get('/users').query({ + projectId: firstProject.id, + }); + + /** + * Assert + */ + expect(response.status).toBe(200); + expect(response.body.data.length).toBe(2); + expect(response.body.nextCursor).toBeNull(); + expect(response.body.data.map((user: User) => user.id)).toEqual( + expect.arrayContaining([firstMember.id, secondMember.id]), + ); + }); }); describe('GET /users/:id', () => { diff --git a/packages/cli/test/integration/publicApi/users.test.ts b/packages/cli/test/integration/publicApi/users.test.ts new file mode 100644 index 0000000000000..6021ae01a35f5 --- /dev/null +++ b/packages/cli/test/integration/publicApi/users.test.ts @@ -0,0 +1,252 @@ +import { setupTestServer } from '@test-integration/utils'; +import * as testDb from '../shared/testDb'; +import { createMember, createOwner, getUserById } from '@test-integration/db/users'; +import { mockInstance } from '@test/mocking'; +import { Telemetry } from '@/telemetry'; +import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error'; + +describe('Users in Public API', () => { + const testServer = setupTestServer({ endpointGroups: ['publicApi'] }); + mockInstance(Telemetry); + + beforeAll(async () => { + await testDb.init(); + }); + + beforeEach(async () => { + await testDb.truncate(['User']); + }); + + describe('POST /users', () => { + it('if not authenticated, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: false }); + const payload = { email: 'test@test.com', role: 'global:admin' }; + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).post('/users').send(payload); + + /** + * Assert + */ + expect(response.status).toBe(401); + }); + + it('if missing scope, should reject', async () => { + /** + * Arrange + */ + testServer.license.enable('feat:advancedPermissions'); + const member = await createMember({ withApiKey: true }); + const payload = [{ email: 'test@test.com', role: 'global:admin' }]; + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(member).post('/users').send(payload); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty('message', 'Forbidden'); + }); + + it('should create a user', async () => { + /** + * Arrange + */ + testServer.license.enable('feat:advancedPermissions'); + const owner = await createOwner({ withApiKey: true }); + const payload = [{ email: 'test@test.com', role: 'global:admin' }]; + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).post('/users').send(payload); + + /** + * Assert + */ + expect(response.status).toBe(201); + + expect(response.body).toHaveLength(1); + + const [result] = response.body; + const { user: returnedUser, error } = result; + const payloadUser = payload[0]; + + expect(returnedUser).toHaveProperty('email', payload[0].email); + expect(typeof returnedUser.inviteAcceptUrl).toBe('string'); + expect(typeof returnedUser.emailSent).toBe('boolean'); + expect(error).toBe(''); + + const storedUser = await getUserById(returnedUser.id); + expect(returnedUser.id).toBe(storedUser.id); + expect(returnedUser.email).toBe(storedUser.email); + expect(returnedUser.email).toBe(payloadUser.email); + expect(storedUser.role).toBe(payloadUser.role); + }); + }); + + describe('DELETE /users/:id', () => { + it('if not authenticated, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: false }); + const member = await createMember(); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).delete(`/users/${member.id}`); + + /** + * Assert + */ + expect(response.status).toBe(401); + }); + + it('if missing scope, should reject', async () => { + /** + * Arrange + */ + testServer.license.enable('feat:advancedPermissions'); + const firstMember = await createMember({ withApiKey: true }); + const secondMember = await createMember(); + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(firstMember) + .delete(`/users/${secondMember.id}`); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty('message', 'Forbidden'); + }); + + it('should delete a user', async () => { + /** + * Arrange + */ + testServer.license.enable('feat:advancedPermissions'); + const owner = await createOwner({ withApiKey: true }); + const member = await createMember(); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).delete(`/users/${member.id}`); + + /** + * Assert + */ + expect(response.status).toBe(204); + await expect(getUserById(member.id)).rejects.toThrow(); + }); + }); + + describe('PATCH /users/:id/role', () => { + it('if not authenticated, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: false }); + const member = await createMember(); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).patch(`/users/${member.id}/role`); + + /** + * Assert + */ + expect(response.status).toBe(401); + }); + + it('if not licensed, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: true }); + const member = await createMember(); + const payload = { newRoleName: 'global:admin' }; + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(owner) + .patch(`/users/${member.id}/role`) + .send(payload); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty( + 'message', + new FeatureNotLicensedError('feat:advancedPermissions').message, + ); + }); + + it('if missing scope, should reject', async () => { + /** + * Arrange + */ + testServer.license.enable('feat:advancedPermissions'); + const firstMember = await createMember({ withApiKey: true }); + const secondMember = await createMember(); + const payload = { newRoleName: 'global:admin' }; + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(firstMember) + .patch(`/users/${secondMember.id}/role`) + .send(payload); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty('message', 'Forbidden'); + }); + + it("should change a user's role", async () => { + /** + * Arrange + */ + testServer.license.enable('feat:advancedPermissions'); + const owner = await createOwner({ withApiKey: true }); + const member = await createMember(); + const payload = { newRoleName: 'global:admin' }; + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(owner) + .patch(`/users/${member.id}/role`) + .send(payload); + + /** + * Assert + */ + expect(response.status).toBe(204); + const storedUser = await getUserById(member.id); + expect(storedUser.role).toBe(payload.newRoleName); + }); + }); +}); diff --git a/packages/cli/test/integration/publicApi/variables.test.ts b/packages/cli/test/integration/publicApi/variables.test.ts new file mode 100644 index 0000000000000..b97c2467ebf63 --- /dev/null +++ b/packages/cli/test/integration/publicApi/variables.test.ts @@ -0,0 +1,167 @@ +import { setupTestServer } from '@test-integration/utils'; +import { createOwner } from '@test-integration/db/users'; +import { createVariable, getVariableOrFail } from '@test-integration/db/variables'; +import * as testDb from '../shared/testDb'; +import { FeatureNotLicensedError } from '@/errors/feature-not-licensed.error'; + +describe('Variables in Public API', () => { + const testServer = setupTestServer({ endpointGroups: ['publicApi'] }); + + beforeAll(async () => { + await testDb.init(); + }); + + beforeEach(async () => { + await testDb.truncate(['Variables', 'User']); + }); + + describe('GET /variables', () => { + it('if licensed, should return all variables with pagination', async () => { + /** + * Arrange + */ + testServer.license.enable('feat:variables'); + const owner = await createOwner({ withApiKey: true }); + const variables = await Promise.all([createVariable(), createVariable(), createVariable()]); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).get('/variables'); + + /** + * Assert + */ + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('data'); + expect(response.body).toHaveProperty('nextCursor'); + expect(Array.isArray(response.body.data)).toBe(true); + expect(response.body.data.length).toBe(variables.length); + + variables.forEach(({ id, key, value }) => { + expect(response.body.data).toContainEqual(expect.objectContaining({ id, key, value })); + }); + }); + + it('if not licensed, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: true }); + + /** + * Act + */ + const response = await testServer.publicApiAgentFor(owner).get('/variables'); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty( + 'message', + new FeatureNotLicensedError('feat:variables').message, + ); + }); + }); + + describe('POST /variables', () => { + it('if licensed, should create a new variable', async () => { + /** + * Arrange + */ + testServer.license.enable('feat:variables'); + const owner = await createOwner({ withApiKey: true }); + const variablePayload = { key: 'key', value: 'value' }; + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(owner) + .post('/variables') + .send(variablePayload); + + /** + * Assert + */ + expect(response.status).toBe(201); + await expect(getVariableOrFail(response.body.id)).resolves.toEqual( + expect.objectContaining(variablePayload), + ); + }); + + it('if not licensed, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: true }); + const variablePayload = { key: 'key', value: 'value' }; + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(owner) + .post('/variables') + .send(variablePayload); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty( + 'message', + new FeatureNotLicensedError('feat:variables').message, + ); + }); + }); + + describe('DELETE /variables/:id', () => { + it('if licensed, should delete a variable', async () => { + /** + * Arrange + */ + testServer.license.enable('feat:variables'); + const owner = await createOwner({ withApiKey: true }); + const variable = await createVariable(); + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(owner) + .delete(`/variables/${variable.id}`); + + /** + * Assert + */ + expect(response.status).toBe(204); + await expect(getVariableOrFail(variable.id)).rejects.toThrow(); + }); + + it('if not licensed, should reject', async () => { + /** + * Arrange + */ + const owner = await createOwner({ withApiKey: true }); + const variable = await createVariable(); + + /** + * Act + */ + const response = await testServer + .publicApiAgentFor(owner) + .delete(`/variables/${variable.id}`); + + /** + * Assert + */ + expect(response.status).toBe(403); + expect(response.body).toHaveProperty( + 'message', + new FeatureNotLicensedError('feat:variables').message, + ); + }); + }); +}); diff --git a/packages/cli/test/integration/publicApi/workflows.test.ts b/packages/cli/test/integration/publicApi/workflows.test.ts index 10737a30f0b12..1e292af64d92f 100644 --- a/packages/cli/test/integration/publicApi/workflows.test.ts +++ b/packages/cli/test/integration/publicApi/workflows.test.ts @@ -21,6 +21,8 @@ import { createTag } from '../shared/db/tags'; import { mockInstance } from '../../shared/mocking'; import type { SuperAgentTest } from '../shared/types'; import { Telemetry } from '@/telemetry'; +import { ProjectService } from '@/services/project.service'; +import { createTeamProject } from '@test-integration/db/projects'; mockInstance(Telemetry); @@ -265,6 +267,25 @@ describe('GET /workflows', () => { } }); + test('should return all user-accessible workflows filtered by `projectId`', async () => { + license.setQuota('quota:maxTeamProjects', 2); + const otherProject = await Container.get(ProjectService).createTeamProject( + 'Other project', + member, + ); + + await Promise.all([ + createWorkflow({}, member), + createWorkflow({ name: 'Other workflow' }, otherProject), + ]); + + const response = await authMemberAgent.get(`/workflows?projectId=${otherProject.id}`); + + expect(response.statusCode).toBe(200); + expect(response.body.data.length).toBe(1); + expect(response.body.data[0].name).toBe('Other workflow'); + }); + test('should return all owned workflows filtered by name', async () => { const workflowName = 'Workflow 1'; @@ -1465,3 +1486,44 @@ describe('PUT /workflows/:id/tags', () => { } }); }); + +describe('PUT /workflows/:id/transfer', () => { + test('should transfer workflow to project', async () => { + /** + * Arrange + */ + const firstProject = await createTeamProject('first-project', member); + const secondProject = await createTeamProject('second-project', member); + const workflow = await createWorkflow({}, firstProject); + + /** + * Act + */ + const response = await authMemberAgent.put(`/workflows/${workflow.id}/transfer`).send({ + destinationProjectId: secondProject.id, + }); + + /** + * Assert + */ + expect(response.statusCode).toBe(204); + }); + + test('if no destination project, should reject', async () => { + /** + * Arrange + */ + const firstProject = await createTeamProject('first-project', member); + const workflow = await createWorkflow({}, firstProject); + + /** + * Act + */ + const response = await authMemberAgent.put(`/workflows/${workflow.id}/transfer`).send({}); + + /** + * Assert + */ + expect(response.statusCode).toBe(400); + }); +}); diff --git a/packages/cli/test/integration/security-audit/InstanceRiskReporter.test.ts b/packages/cli/test/integration/security-audit/InstanceRiskReporter.test.ts index 5d359e0dba52b..5f59206426a05 100644 --- a/packages/cli/test/integration/security-audit/InstanceRiskReporter.test.ts +++ b/packages/cli/test/integration/security-audit/InstanceRiskReporter.test.ts @@ -14,6 +14,7 @@ import config from '@/config'; import { generateNanoId } from '@db/utils/generators'; import { WorkflowRepository } from '@db/repositories/workflow.repository'; import Container from 'typedi'; +import { NodeConnectionType } from 'n8n-workflow'; let securityAuditService: SecurityAuditService; @@ -156,7 +157,7 @@ test('should not report webhooks validated by direct children', async () => { [ { node: 'My Node', - type: 'main', + type: NodeConnectionType.Main, index: 0, }, ], diff --git a/packages/cli/test/integration/shared/constants.ts b/packages/cli/test/integration/shared/constants.ts index caa3667c23012..5fffacbd11788 100644 --- a/packages/cli/test/integration/shared/constants.ts +++ b/packages/cli/test/integration/shared/constants.ts @@ -1,8 +1,7 @@ -import config from '@/config'; import { GlobalConfig } from '@n8n/config'; import Container from 'typedi'; -export const REST_PATH_SEGMENT = config.getEnv('endpoints.rest'); +export const REST_PATH_SEGMENT = Container.get(GlobalConfig).endpoints.rest; export const PUBLIC_API_REST_PATH_SEGMENT = Container.get(GlobalConfig).publicApi.path; diff --git a/packages/cli/test/integration/shared/db/credentials.ts b/packages/cli/test/integration/shared/db/credentials.ts index 046d27db261a0..588fee6b51196 100644 --- a/packages/cli/test/integration/shared/db/credentials.ts +++ b/packages/cli/test/integration/shared/db/credentials.ts @@ -38,11 +38,24 @@ export async function createManyCredentials( ); } -export async function createCredentials(attributes: Partial = emptyAttributes) { +export async function createCredentials( + attributes: Partial = emptyAttributes, + project?: Project, +) { const credentialsRepository = Container.get(CredentialsRepository); - const entity = credentialsRepository.create(attributes); + const credentials = await credentialsRepository.save(credentialsRepository.create(attributes)); + + if (project) { + await Container.get(SharedCredentialsRepository).save( + Container.get(SharedCredentialsRepository).create({ + project, + credentials, + role: 'credential:owner', + }), + ); + } - return await credentialsRepository.save(entity); + return credentials; } /** diff --git a/packages/cli/test/integration/shared/db/projects.ts b/packages/cli/test/integration/shared/db/projects.ts index 60548575b362b..3de7de5bb95f9 100644 --- a/packages/cli/test/integration/shared/db/projects.ts +++ b/packages/cli/test/integration/shared/db/projects.ts @@ -34,6 +34,10 @@ export const linkUserToProject = async (user: User, project: Project, role: Proj ); }; +export async function getProjectByNameOrFail(name: string) { + return await Container.get(ProjectRepository).findOneOrFail({ where: { name } }); +} + export const getPersonalProject = async (user: User): Promise => { return await Container.get(ProjectRepository).findOneOrFail({ where: { diff --git a/packages/cli/test/integration/shared/db/users.ts b/packages/cli/test/integration/shared/db/users.ts index aa7a3baaa726f..98626bc549d9e 100644 --- a/packages/cli/test/integration/shared/db/users.ts +++ b/packages/cli/test/integration/shared/db/users.ts @@ -78,11 +78,19 @@ export async function createUserWithMfaEnabled( }; } -export async function createOwner() { +export async function createOwner({ withApiKey } = { withApiKey: false }) { + if (withApiKey) { + return await addApiKey(await createUser({ role: 'global:owner' })); + } + return await createUser({ role: 'global:owner' }); } -export async function createMember() { +export async function createMember({ withApiKey } = { withApiKey: false }) { + if (withApiKey) { + return await addApiKey(await createUser({ role: 'global:member' })); + } + return await createUser({ role: 'global:member' }); } diff --git a/packages/cli/test/integration/shared/db/variables.ts b/packages/cli/test/integration/shared/db/variables.ts new file mode 100644 index 0000000000000..68495da3b180f --- /dev/null +++ b/packages/cli/test/integration/shared/db/variables.ts @@ -0,0 +1,12 @@ +import { VariablesRepository } from '@/databases/repositories/variables.repository'; +import { generateNanoId } from '@/databases/utils/generators'; +import { randomString } from 'n8n-workflow'; +import Container from 'typedi'; + +export async function createVariable(key = randomString(5), value = randomString(5)) { + return await Container.get(VariablesRepository).save({ id: generateNanoId(), key, value }); +} + +export async function getVariableOrFail(id: string) { + return await Container.get(VariablesRepository).findOneOrFail({ where: { id } }); +} diff --git a/packages/cli/test/integration/shared/db/workflows.ts b/packages/cli/test/integration/shared/db/workflows.ts index f81ac044c3c1d..dc5490f9f3c61 100644 --- a/packages/cli/test/integration/shared/db/workflows.ts +++ b/packages/cli/test/integration/shared/db/workflows.ts @@ -9,6 +9,7 @@ import { WorkflowRepository } from '@db/repositories/workflow.repository'; import type { SharedWorkflow, WorkflowSharingRole } from '@db/entities/SharedWorkflow'; import { ProjectRepository } from '@/databases/repositories/project.repository'; import { Project } from '@/databases/entities/Project'; +import { NodeConnectionType } from 'n8n-workflow'; export async function createManyWorkflows( amount: number, @@ -157,7 +158,7 @@ export async function createWorkflowWithTrigger( position: [780, 300], }, ], - connections: { Cron: { main: [[{ node: 'Set', type: 'main', index: 0 }]] } }, + connections: { Cron: { main: [[{ node: 'Set', type: NodeConnectionType.Main, index: 0 }]] } }, ...attributes, }, user, diff --git a/packages/cli/test/integration/shared/types.ts b/packages/cli/test/integration/shared/types.ts index 0352386590a3d..cb794d0f95b92 100644 --- a/packages/cli/test/integration/shared/types.ts +++ b/packages/cli/test/integration/shared/types.ts @@ -13,6 +13,7 @@ type EndpointGroup = | 'me' | 'users' | 'auth' + | 'oauth2' | 'owner' | 'passwordReset' | 'credentials' diff --git a/packages/cli/test/integration/shared/utils/index.ts b/packages/cli/test/integration/shared/utils/index.ts index 96efb06039681..7abdb1a8e834d 100644 --- a/packages/cli/test/integration/shared/utils/index.ts +++ b/packages/cli/test/integration/shared/utils/index.ts @@ -96,9 +96,10 @@ export async function initBinaryDataService(mode: 'default' | 'filesystem' = 'de * Extract the value (token) of the auth cookie in a response. */ export function getAuthToken(response: request.Response, authCookieName = AUTH_COOKIE_NAME) { - const cookies: string[] = response.headers['set-cookie']; + const cookiesHeader = response.headers['set-cookie']; + if (!cookiesHeader) return undefined; - if (!cookies) return undefined; + const cookies = Array.isArray(cookiesHeader) ? cookiesHeader : [cookiesHeader]; const authCookie = cookies.find((c) => c.startsWith(`${authCookieName}=`)); diff --git a/packages/cli/test/integration/shared/utils/testCommand.ts b/packages/cli/test/integration/shared/utils/testCommand.ts index 7a8477c4dd1b8..2d25d837cc928 100644 --- a/packages/cli/test/integration/shared/utils/testCommand.ts +++ b/packages/cli/test/integration/shared/utils/testCommand.ts @@ -4,7 +4,7 @@ import { mock } from 'jest-mock-extended'; import type { BaseCommand } from '@/commands/BaseCommand'; import * as testDb from '../testDb'; -import { TelemetryEventRelay } from '@/telemetry/telemetry-event-relay.service'; +import { TelemetryEventRelay } from '@/events/telemetry-event-relay'; import { mockInstance } from '@test/mocking'; export const setupTestCommand = (Command: Class) => { diff --git a/packages/cli/test/integration/shared/utils/testServer.ts b/packages/cli/test/integration/shared/utils/testServer.ts index 3fc4cb5642b49..7776b7e669415 100644 --- a/packages/cli/test/integration/shared/utils/testServer.ts +++ b/packages/cli/test/integration/shared/utils/testServer.ts @@ -159,6 +159,10 @@ export const setupTestServer = ({ await import('@/controllers/auth.controller'); break; + case 'oauth2': + await import('@/controllers/oauth/oAuth2Credential.controller'); + break; + case 'mfa': await import('@/controllers/mfa.controller'); break; diff --git a/packages/cli/test/unit/webhooks.test.ts b/packages/cli/test/integration/webhooks.test.ts similarity index 91% rename from packages/cli/test/unit/webhooks.test.ts rename to packages/cli/test/integration/webhooks.test.ts index 5fe8f937de660..9bd1977ed53d2 100644 --- a/packages/cli/test/unit/webhooks.test.ts +++ b/packages/cli/test/integration/webhooks.test.ts @@ -2,7 +2,6 @@ import type SuperAgentTest from 'supertest/lib/agent'; import { agent as testAgent } from 'supertest'; import { mock } from 'jest-mock-extended'; -import config from '@/config'; import { AbstractServer } from '@/AbstractServer'; import { ActiveWebhooks } from '@/ActiveWebhooks'; import { ExternalHooks } from '@/ExternalHooks'; @@ -12,7 +11,9 @@ import { WaitingWebhooks } from '@/WaitingWebhooks'; import { WaitingForms } from '@/WaitingForms'; import type { IResponseCallbackData } from '@/Interfaces'; -import { mockInstance } from '../shared/mocking'; +import { mockInstance } from '@test/mocking'; +import { GlobalConfig } from '@n8n/config'; +import Container from 'typedi'; let agent: SuperAgentTest; @@ -46,7 +47,7 @@ describe('WebhookServer', () => { for (const [key, manager] of tests) { describe(`for ${key}`, () => { it('should handle preflight requests', async () => { - const pathPrefix = config.getEnv(`endpoints.${key}`); + const pathPrefix = Container.get(GlobalConfig).endpoints[key]; manager.getWebhookMethods.mockResolvedValueOnce(['GET']); const response = await agent @@ -60,7 +61,7 @@ describe('WebhookServer', () => { }); it('should handle regular requests', async () => { - const pathPrefix = config.getEnv(`endpoints.${key}`); + const pathPrefix = Container.get(GlobalConfig).endpoints[key]; manager.getWebhookMethods.mockResolvedValueOnce(['GET']); manager.executeWebhook.mockResolvedValueOnce( mockResponse({ test: true }, { key: 'value ' }), diff --git a/packages/cli/test/integration/workflows/workflow.service.ee.test.ts b/packages/cli/test/integration/workflows/workflow.service.ee.test.ts index 55287c5f2210c..6e3b00bd58fcc 100644 --- a/packages/cli/test/integration/workflows/workflow.service.ee.test.ts +++ b/packages/cli/test/integration/workflows/workflow.service.ee.test.ts @@ -30,6 +30,8 @@ describe('EnterpriseWorkflowService', () => { Container.get(CredentialsRepository), mock(), mock(), + mock(), + mock(), ); }); diff --git a/packages/cli/test/integration/workflows/workflows.controller-with-active-workflow-manager.ee.test.ts b/packages/cli/test/integration/workflows/workflows.controller-with-active-workflow-manager.ee.test.ts index 607639b091a12..d32722c23dd86 100644 --- a/packages/cli/test/integration/workflows/workflows.controller-with-active-workflow-manager.ee.test.ts +++ b/packages/cli/test/integration/workflows/workflows.controller-with-active-workflow-manager.ee.test.ts @@ -11,7 +11,6 @@ import { Telemetry } from '@/telemetry'; mockInstance(Telemetry); let member: User; -let anotherMember: User; const testServer = utils.setupTestServer({ endpointGroups: ['workflows'], @@ -20,7 +19,6 @@ const testServer = utils.setupTestServer({ beforeAll(async () => { member = await createUser({ role: 'global:member' }); - anotherMember = await createUser({ role: 'global:member' }); await utils.initNodeTypes(); }); diff --git a/packages/cli/test/shared/flushPromises.ts b/packages/cli/test/shared/flushPromises.ts new file mode 100644 index 0000000000000..405b9e98dca26 --- /dev/null +++ b/packages/cli/test/shared/flushPromises.ts @@ -0,0 +1,6 @@ +/** + * Ensure all pending promises settle. The promise's `resolve` is placed in + * the macrotask queue and so called at the next iteration of the event loop + * after all promises in the microtask queue have settled first. + */ +export const flushPromises = async () => await new Promise(setImmediate); diff --git a/packages/cli/test/unit/shared/mockObjects.ts b/packages/cli/test/shared/mockObjects.ts similarity index 94% rename from packages/cli/test/unit/shared/mockObjects.ts rename to packages/cli/test/shared/mockObjects.ts index e7a165977301f..a8795e8e1013f 100644 --- a/packages/cli/test/unit/shared/mockObjects.ts +++ b/packages/cli/test/shared/mockObjects.ts @@ -8,7 +8,7 @@ import { randomEmail, randomName, uniqueId, -} from '../../integration/shared/random'; +} from '../integration/shared/random'; export const mockCredential = (): CredentialsEntity => Object.assign(new CredentialsEntity(), randomCredentialPayload()); diff --git a/packages/cli/test/unit/shared/testData.ts b/packages/cli/test/shared/testData.ts similarity index 100% rename from packages/cli/test/unit/shared/testData.ts rename to packages/cli/test/shared/testData.ts diff --git a/packages/cli/test/unit/Helpers.ts b/packages/cli/test/unit/Helpers.ts deleted file mode 100644 index 50b9f43489076..0000000000000 --- a/packages/cli/test/unit/Helpers.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { INodeTypeData } from 'n8n-workflow'; - -/** - * Ensure all pending promises settle. The promise's `resolve` is placed in - * the macrotask queue and so called at the next iteration of the event loop - * after all promises in the microtask queue have settled first. - */ -export const flushPromises = async () => await new Promise(setImmediate); - -export function mockNodeTypesData( - nodeNames: string[], - options?: { - addTrigger?: boolean; - }, -) { - return nodeNames.reduce((acc, nodeName) => { - return ( - (acc[`n8n-nodes-base.${nodeName}`] = { - sourcePath: '', - type: { - description: { - displayName: nodeName, - name: nodeName, - group: [], - description: '', - version: 1, - defaults: {}, - inputs: [], - outputs: [], - properties: [], - }, - trigger: options?.addTrigger ? async () => undefined : undefined, - }, - }), - acc - ); - }, {}); -} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 008f1cb70d742..0efc9328610d8 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -2,7 +2,6 @@ "extends": ["../../tsconfig.json", "../../tsconfig.backend.json"], "compilerOptions": { "rootDir": ".", - "preserveSymlinks": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "baseUrl": "src", diff --git a/packages/core/package.json b/packages/core/package.json index 0eb22acf6d5eb..dfcc23db99d92 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "n8n-core", - "version": "1.52.0", + "version": "1.53.0", "description": "Core functionality of n8n", "main": "dist/index", "types": "dist/index.d.ts", @@ -28,23 +28,23 @@ "devDependencies": { "@types/aws4": "^1.5.1", "@types/concat-stream": "^2.0.0", - "@types/express": "^4.17.21", - "@types/lodash": "^4.14.195", + "@types/express": "catalog:", + "@types/lodash": "catalog:", "@types/mime-types": "^2.1.0", - "@types/uuid": "^8.3.2", - "@types/xml2js": "^0.4.14" + "@types/uuid": "catalog:", + "@types/xml2js": "catalog:" }, "dependencies": { "@n8n/client-oauth2": "workspace:*", "aws4": "1.11.0", - "axios": "1.6.7", + "axios": "catalog:", "concat-stream": "2.0.0", "cron": "3.1.7", - "fast-glob": "3.2.12", + "fast-glob": "catalog:", "file-type": "16.5.4", - "form-data": "4.0.0", - "lodash": "4.17.21", - "luxon": "^3.4.4", + "form-data": "catalog:", + "lodash": "catalog:", + "luxon": "catalog:", "mime-types": "2.1.35", "n8n-workflow": "workspace:*", "oauth-1.0a": "2.2.6", @@ -52,8 +52,8 @@ "pretty-bytes": "5.6.0", "qs": "6.11.0", "ssh2": "1.15.0", - "typedi": "0.10.0", - "uuid": "8.3.2", - "xml2js": "0.6.2" + "typedi": "catalog:", + "uuid": "catalog:", + "xml2js": "catalog:" } } diff --git a/packages/core/src/InstanceSettings.ts b/packages/core/src/InstanceSettings.ts index e8ab9aa553223..f75e1df712108 100644 --- a/packages/core/src/InstanceSettings.ts +++ b/packages/core/src/InstanceSettings.ts @@ -14,6 +14,8 @@ interface WritableSettings { type Settings = ReadOnlySettings & WritableSettings; +type InstanceRole = 'unset' | 'leader' | 'follower'; + const inTest = process.env.NODE_ENV === 'test'; @Service() @@ -38,6 +40,25 @@ export class InstanceSettings { readonly instanceId = this.generateInstanceId(); + /** Always `leader` in single-main setup. `leader` or `follower` in multi-main setup. */ + private instanceRole: InstanceRole = 'unset'; + + get isLeader() { + return this.instanceRole === 'leader'; + } + + markAsLeader() { + this.instanceRole = 'leader'; + } + + get isFollower() { + return this.instanceRole === 'follower'; + } + + markAsFollower() { + this.instanceRole = 'follower'; + } + get encryptionKey() { return this.settings.encryptionKey; } diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index f9e27cc15c0ac..cf677ba5bd036 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -4212,8 +4212,9 @@ export function getExecuteWebhookFunctions( mode: WorkflowExecuteMode, webhookData: IWebhookData, closeFunctions: CloseFunction[], + runExecutionData: IRunExecutionData | null, ): IWebhookFunctions { - return ((workflow: Workflow, node: INode) => { + return ((workflow: Workflow, node: INode, runExecutionData: IRunExecutionData | null) => { return { ...getCommonWorkflowFunctions(workflow, node, additionalData), getBodyData(): IDataObject { @@ -4274,10 +4275,21 @@ export function getExecuteWebhookFunctions( fallbackValue?: any, options?: IGetNodeParameterOptions, ): NodeParameterValueType | object => { - const runExecutionData: IRunExecutionData | null = null; const itemIndex = 0; const runIndex = 0; - const connectionInputData: INodeExecutionData[] = []; + + let connectionInputData: INodeExecutionData[] = []; + let executionData: IExecuteData | undefined; + + if (runExecutionData?.executionData !== undefined) { + executionData = runExecutionData.executionData.nodeExecutionStack[0]; + + if (executionData !== undefined) { + connectionInputData = executionData.data.main[0]!; + } + } + + const additionalKeys = getAdditionalKeys(additionalData, mode, runExecutionData); return getNodeParameter( workflow, @@ -4288,8 +4300,8 @@ export function getExecuteWebhookFunctions( parameterName, itemIndex, mode, - getAdditionalKeys(additionalData, mode, null), - undefined, + additionalKeys, + executionData, fallbackValue, options, ); @@ -4336,5 +4348,5 @@ export function getExecuteWebhookFunctions( }, nodeHelpers: getNodeHelperFunctions(additionalData, workflow.id), }; - })(workflow, node); + })(workflow, node, runExecutionData); } diff --git a/packages/design-system/package.json b/packages/design-system/package.json index 88b2e967d09ce..386a7e1e862c8 100644 --- a/packages/design-system/package.json +++ b/packages/design-system/package.json @@ -1,6 +1,6 @@ { "name": "n8n-design-system", - "version": "1.42.0", + "version": "1.43.0", "main": "src/main.ts", "import": "src/main.ts", "scripts": { @@ -27,13 +27,18 @@ "@types/markdown-it-link-attributes": "^3.0.1", "@types/sanitize-html": "^2.11.0", "@vitejs/plugin-vue": "^5.0.4", + "@vitest/coverage-v8": "catalog:frontend", "@vue/test-utils": "^2.4.3", "autoprefixer": "^10.4.19", "postcss": "^8.4.38", "sass": "^1.64.1", "tailwindcss": "^3.4.3", "unplugin-icons": "^0.19.0", - "unplugin-vue-components": "^0.27.2" + "unplugin-vue-components": "^0.27.2", + "vite": "catalog:frontend", + "vitest": "catalog:frontend", + "vitest-mock-extended": "catalog:frontend", + "vue-tsc": "catalog:frontend" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^1.2.36", @@ -45,7 +50,7 @@ "markdown-it-link-attributes": "^4.0.1", "markdown-it-task-lists": "^2.1.1", "sanitize-html": "2.12.1", - "vue": "^3.4.21", + "vue": "catalog:frontend", "vue-boring-avatars": "^1.3.0", "vue-router": "^4.2.2", "xss": "^1.0.14" diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index a4425d2504d9a..caf156fa83c9e 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -1,6 +1,6 @@ { "name": "n8n-editor-ui", - "version": "1.52.0", + "version": "1.53.0", "description": "Workflow Editor UI for n8n", "main": "index.js", "scripts": { @@ -43,7 +43,7 @@ "@vue-flow/node-resizer": "^1.4.0", "@vueuse/components": "^10.11.0", "@vueuse/core": "^10.11.0", - "axios": "1.6.7", + "axios": "catalog:", "chart.js": "^4.4.0", "codemirror-lang-html-n8n": "^1.0.0", "dateformat": "^3.0.3", @@ -55,7 +55,7 @@ "humanize-duration": "^3.27.2", "jsonpath": "^1.1.1", "lodash-es": "^4.17.21", - "luxon": "^3.3.0", + "luxon": "catalog:", "n8n-design-system": "workspace:*", "n8n-workflow": "workspace:*", "pinia": "^2.1.6", @@ -63,9 +63,9 @@ "qrcode.vue": "^3.3.4", "stream-browserify": "^3.0.0", "timeago.js": "^4.0.2", - "uuid": "^8.3.2", + "uuid": "catalog:", "v3-infinite-loading": "^1.2.2", - "vue": "^3.4.21", + "vue": "catalog:frontend", "vue-agile": "^2.0.0", "vue-chartjs": "^5.2.0", "vue-i18n": "^9.2.2", @@ -86,11 +86,16 @@ "@types/jsonpath": "^0.2.0", "@types/lodash-es": "^4.17.6", "@types/luxon": "^3.2.0", - "@types/uuid": "^8.3.2", + "@types/uuid": "catalog:", + "@vitest/coverage-v8": "catalog:frontend", "cross-env": "^7.0.3", "miragejs": "^0.1.48", "unplugin-icons": "^0.19.0", - "unplugin-vue-components": "^0.27.2" + "unplugin-vue-components": "^0.27.2", + "vite": "catalog:frontend", + "vitest": "catalog:frontend", + "vitest-mock-extended": "catalog:frontend", + "vue-tsc": "catalog:frontend" }, "peerDependencies": { "@fortawesome/fontawesome-svg-core": "*", diff --git a/packages/editor-ui/src/assets/images/gcp-secrets-manager.svg b/packages/editor-ui/src/assets/images/gcp-secrets-manager.svg new file mode 100644 index 0000000000000..a6e07c76819ad --- /dev/null +++ b/packages/editor-ui/src/assets/images/gcp-secrets-manager.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/editor-ui/src/components/CredentialCard.test.ts b/packages/editor-ui/src/components/CredentialCard.test.ts index 06760c75da7c5..9ca589923d59e 100644 --- a/packages/editor-ui/src/components/CredentialCard.test.ts +++ b/packages/editor-ui/src/components/CredentialCard.test.ts @@ -6,7 +6,7 @@ import { createComponentRenderer } from '@/__tests__/render'; import CredentialCard from '@/components/CredentialCard.vue'; import type { ICredentialsResponse } from '@/Interface'; import type { ProjectSharingData } from '@/types/projects.types'; -import { useSettingsStore } from '@/stores/settings.store'; +import { useProjectsStore } from '@/stores/projects.store'; const renderComponent = createComponentRenderer(CredentialCard); @@ -22,12 +22,12 @@ const createCredential = (overrides = {}): ICredentialsResponse => ({ }); describe('CredentialCard', () => { - let settingsStore: ReturnType; + let projectsStore: ReturnType; beforeEach(() => { const pinia = createTestingPinia(); setActivePinia(pinia); - settingsStore = useSettingsStore(); + projectsStore = useProjectsStore(); }); it('should render name and home project name', () => { @@ -63,7 +63,7 @@ describe('CredentialCard', () => { }); it('should show Move action only if there is resource permission and not on community plan', async () => { - vi.spyOn(settingsStore, 'isCommunityPlan', 'get').mockReturnValue(false); + vi.spyOn(projectsStore, 'isTeamProjectFeatureEnabled', 'get').mockReturnValue(true); const data = createCredential({ scopes: ['credential:move'], diff --git a/packages/editor-ui/src/components/CredentialCard.vue b/packages/editor-ui/src/components/CredentialCard.vue index f36926a19b09d..d9ac12ffd9a54 100644 --- a/packages/editor-ui/src/components/CredentialCard.vue +++ b/packages/editor-ui/src/components/CredentialCard.vue @@ -14,7 +14,6 @@ import { useProjectsStore } from '@/stores/projects.store'; import ProjectCardBadge from '@/components/Projects/ProjectCardBadge.vue'; import { useI18n } from '@/composables/useI18n'; import { ResourceType } from '@/utils/projects.utils'; -import { useSettingsStore } from '@/stores/settings.store'; const CREDENTIAL_LIST_ITEM_ACTIONS = { OPEN: 'open', @@ -46,7 +45,6 @@ const message = useMessage(); const uiStore = useUIStore(); const credentialsStore = useCredentialsStore(); const projectsStore = useProjectsStore(); -const settingsStore = useSettingsStore(); const resourceTypeLabel = computed(() => locale.baseText('generic.credential').toLowerCase()); const credentialType = computed(() => credentialsStore.getCredentialTypeByName(props.data.type)); @@ -66,7 +64,7 @@ const actions = computed(() => { }); } - if (credentialPermissions.value.move && !settingsStore.isCommunityPlan) { + if (credentialPermissions.value.move && projectsStore.isTeamProjectFeatureEnabled) { items.push({ label: locale.baseText('credentials.item.move'), value: CREDENTIAL_LIST_ITEM_ACTIONS.MOVE, diff --git a/packages/editor-ui/src/components/Error/NodeErrorView.vue b/packages/editor-ui/src/components/Error/NodeErrorView.vue index 5244371d0fc47..a0d6396df1416 100644 --- a/packages/editor-ui/src/components/Error/NodeErrorView.vue +++ b/packages/editor-ui/src/components/Error/NodeErrorView.vue @@ -21,6 +21,7 @@ import type { BaseTextKey } from '@/plugins/i18n'; type Props = { error: NodeError | NodeApiError | NodeOperationError; + compact?: boolean; }; const props = defineProps(); @@ -377,7 +378,7 @@ function copySuccess() { > -
+

{{ i18n.baseText('nodeErrorView.details.title') }} diff --git a/packages/editor-ui/src/components/ExternalSecretsProviderImage.ee.vue b/packages/editor-ui/src/components/ExternalSecretsProviderImage.ee.vue index dc40fed88c53c..8d8eec910917d 100644 --- a/packages/editor-ui/src/components/ExternalSecretsProviderImage.ee.vue +++ b/packages/editor-ui/src/components/ExternalSecretsProviderImage.ee.vue @@ -7,6 +7,7 @@ import doppler from '../assets/images/doppler.webp'; import vault from '../assets/images/hashicorp.webp'; import awsSecretsManager from '../assets/images/aws-secrets-manager.svg'; import azureKeyVault from '../assets/images/azure-key-vault.svg'; +import gcpSecretsManager from '../assets/images/gcp-secrets-manager.svg'; const props = defineProps<{ provider: ExternalSecretsProvider; @@ -20,6 +21,7 @@ const image = computed( vault, awsSecretsManager, azureKeyVault, + gcpSecretsManager, })[props.provider.name], ); diff --git a/packages/editor-ui/src/components/OutputPanel.vue b/packages/editor-ui/src/components/OutputPanel.vue index 6d76c34295978..af1a7801f6652 100644 --- a/packages/editor-ui/src/components/OutputPanel.vue +++ b/packages/editor-ui/src/components/OutputPanel.vue @@ -28,6 +28,7 @@

+ diff --git a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsSidebar.vue b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsSidebar.vue index df30fd0eb0438..0186ef4830a1a 100644 --- a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsSidebar.vue +++ b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsSidebar.vue @@ -186,6 +186,9 @@ export default defineComponent({ this.$emit('refresh'); }, onFilterChanged(filter: ExecutionFilterType) { + this.autoScrollDeps.activeExecutionSet = false; + this.autoScrollDeps.scroll = true; + this.mountedItems = []; this.$emit('filterUpdated', filter); }, reloadExecutions(): void { diff --git a/packages/editor-ui/src/composables/__tests__/useExecutionHelpers.test.ts b/packages/editor-ui/src/composables/__tests__/useExecutionHelpers.test.ts index 29bd64ea4243e..465abeb173fbf 100644 --- a/packages/editor-ui/src/composables/__tests__/useExecutionHelpers.test.ts +++ b/packages/editor-ui/src/composables/__tests__/useExecutionHelpers.test.ts @@ -47,4 +47,25 @@ describe('useExecutionHelpers()', () => { ); }); }); + + describe('isExecutionRetriable', () => { + const { isExecutionRetriable } = useExecutionHelpers(); + + it.each(['crashed', 'error'])('returns true when execution status is %s', (status) => { + expect(isExecutionRetriable({ status } as ExecutionSummary)).toEqual(true); + }); + + it.each(['canceled', 'new', 'running', 'success', 'unknown', 'waiting'])( + 'returns false when execution status is %s', + (status) => { + expect(isExecutionRetriable({ status } as ExecutionSummary)).toEqual(false); + }, + ); + + it('should return false if retrySuccessId is set', () => { + expect( + isExecutionRetriable({ status: 'crashed', retrySuccessId: '123' } as ExecutionSummary), + ).toEqual(false); + }); + }); }); diff --git a/packages/editor-ui/src/composables/__tests__/useNodeHelpers.test.ts b/packages/editor-ui/src/composables/__tests__/useNodeHelpers.test.ts index 1969b3d5b8192..732bf51f5e677 100644 --- a/packages/editor-ui/src/composables/__tests__/useNodeHelpers.test.ts +++ b/packages/editor-ui/src/composables/__tests__/useNodeHelpers.test.ts @@ -3,6 +3,7 @@ import { createTestingPinia } from '@pinia/testing'; import { useNodeHelpers } from '@/composables/useNodeHelpers'; import { createTestNode } from '@/__tests__/mocks'; import { useWorkflowsStore } from '@/stores/workflows.store'; +import { CUSTOM_API_CALL_KEY } from '@/constants'; vi.mock('@/stores/workflows.store', () => ({ useWorkflowsStore: vi.fn(), @@ -17,6 +18,34 @@ describe('useNodeHelpers()', () => { vi.clearAllMocks(); }); + describe('isCustomApiCallSelected', () => { + test('should return `true` when resource includes `CUSTOM_API_CALL_KEY`', () => { + const nodeValues = { + parameters: { resource: CUSTOM_API_CALL_KEY }, + }; + expect(useNodeHelpers().isCustomApiCallSelected(nodeValues)).toBe(true); + }); + + test('should return `true` when operation includes `CUSTOM_API_CALL_KEY`', () => { + const nodeValues = { + parameters: { + operation: CUSTOM_API_CALL_KEY, + }, + }; + expect(useNodeHelpers().isCustomApiCallSelected(nodeValues)).toBe(true); + }); + + test('should return `false` when neither resource nor operation includes `CUSTOM_API_CALL_KEY`', () => { + const nodeValues = { + parameters: { + resource: 'users', + operation: 'get', + }, + }; + expect(useNodeHelpers().isCustomApiCallSelected(nodeValues)).toBe(false); + }); + }); + describe('getNodeInputData()', () => { it('should return an empty array when node is null', () => { const { getNodeInputData } = useNodeHelpers(); diff --git a/packages/editor-ui/src/composables/useExecutionHelpers.ts b/packages/editor-ui/src/composables/useExecutionHelpers.ts index a00f5b639a1fa..9740f8dc06639 100644 --- a/packages/editor-ui/src/composables/useExecutionHelpers.ts +++ b/packages/editor-ui/src/composables/useExecutionHelpers.ts @@ -62,11 +62,7 @@ export function useExecutionHelpers() { } function isExecutionRetriable(execution: ExecutionSummary): boolean { - return ( - ['crashed', 'error'].includes(execution.status) && - !execution.retryOf && - !execution.retrySuccessId - ); + return ['crashed', 'error'].includes(execution.status) && !execution.retrySuccessId; } return { diff --git a/packages/editor-ui/src/composables/useNodeHelpers.ts b/packages/editor-ui/src/composables/useNodeHelpers.ts index 00f18041b0225..a8c763b11f5a2 100644 --- a/packages/editor-ui/src/composables/useNodeHelpers.ts +++ b/packages/editor-ui/src/composables/useNodeHelpers.ts @@ -8,6 +8,7 @@ import { FORM_TRIGGER_NODE_TYPE, NODE_OUTPUT_DEFAULT_KEY, PLACEHOLDER_FILLED_AT_EXECUTION_TIME, + SPLIT_IN_BATCHES_NODE_TYPE, WEBHOOK_NODE_TYPE, } from '@/constants'; @@ -97,11 +98,13 @@ export function useNodeHelpers() { if (!isObject(parameters)) return false; - if ('resource' in parameters && 'operation' in parameters) { + if ('resource' in parameters || 'operation' in parameters) { const { resource, operation } = parameters; - if (!isString(resource) || !isString(operation)) return false; - return resource.includes(CUSTOM_API_CALL_KEY) || operation.includes(CUSTOM_API_CALL_KEY); + return ( + (isString(resource) && resource.includes(CUSTOM_API_CALL_KEY)) || + (isString(operation) && operation.includes(CUSTOM_API_CALL_KEY)) + ); } return false; @@ -569,6 +572,16 @@ export function useNodeHelpers() { paneType: NodePanelType = 'output', connectionType: ConnectionTypes = NodeConnectionType.Main, ): INodeExecutionData[] { + //TODO: check if this needs to be fixed in different place + if ( + node?.type === SPLIT_IN_BATCHES_NODE_TYPE && + paneType === 'input' && + runIndex !== 0 && + outputIndex !== 0 + ) { + runIndex = runIndex - 1; + } + if (node === null) { return []; } diff --git a/packages/editor-ui/src/constants.ts b/packages/editor-ui/src/constants.ts index 8b30c972c8af0..acf94fcc3c0dd 100644 --- a/packages/editor-ui/src/constants.ts +++ b/packages/editor-ui/src/constants.ts @@ -92,7 +92,6 @@ export const NPM_KEYWORD_SEARCH_URL = 'https://www.npmjs.com/search?q=keywords%3An8n-community-node-package'; export const N8N_QUEUE_MODE_DOCS_URL = `https://${DOCS_DOMAIN}/hosting/scaling/queue-mode/`; export const COMMUNITY_NODES_INSTALLATION_DOCS_URL = `https://${DOCS_DOMAIN}/integrations/community-nodes/installation/gui-install/`; -export const COMMUNITY_NODES_MANUAL_INSTALLATION_DOCS_URL = `https://${DOCS_DOMAIN}/integrations/community-nodes/installation/manual-install/`; export const COMMUNITY_NODES_NPM_INSTALLATION_URL = 'https://docs.npmjs.com/downloading-and-installing-node-js-and-npm'; export const COMMUNITY_NODES_RISKS_DOCS_URL = `https://${DOCS_DOMAIN}/integrations/community-nodes/risks/`; diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json index e5ee0088590c8..2acf1cfd3554b 100644 --- a/packages/editor-ui/src/plugins/i18n/locales/en.json +++ b/packages/editor-ui/src/plugins/i18n/locales/en.json @@ -1562,7 +1562,6 @@ "settings.communityNodes.empty.description": "Install over {count} node packages contributed by our community.", "settings.communityNodes.empty.description.no-packages": "Install node packages contributed by our community.", "settings.communityNodes.empty.installPackageLabel": "Install a community node", - "settings.communityNodes.queueMode.warning": "You need to install community nodes manually because your instance is running in queue mode. More info", "settings.communityNodes.npmUnavailable.warning": "To use this feature, please install npm and restart n8n.", "settings.communityNodes.notAvailableOnDesktop": "Feature unavailable on desktop. Please self-host to use community nodes.", "settings.communityNodes.packageNodes.label": "{count} node | {count} nodes", diff --git a/packages/editor-ui/src/stores/projects.store.ts b/packages/editor-ui/src/stores/projects.store.ts index f5fbaf6200762..6bc61d246c7c9 100644 --- a/packages/editor-ui/src/stores/projects.store.ts +++ b/packages/editor-ui/src/stores/projects.store.ts @@ -49,19 +49,19 @@ export const useProjectsStore = defineStore('projects', () => { ); const teamProjects = computed(() => projects.value.filter((p) => p.type === ProjectTypes.Team)); const teamProjectsLimit = computed(() => settingsStore.settings.enterprise.projects.team.limit); - const teamProjectsAvailable = computed( + const isTeamProjectFeatureEnabled = computed( () => settingsStore.settings.enterprise.projects.team.limit !== 0, ); const hasUnlimitedProjects = computed( () => settingsStore.settings.enterprise.projects.team.limit === -1, ); - const teamProjectLimitExceeded = computed( + const isTeamProjectLimitExceeded = computed( () => projectsCount.value.team >= teamProjectsLimit.value, ); const canCreateProjects = computed( () => hasUnlimitedProjects.value || - (teamProjectsAvailable.value && !teamProjectLimitExceeded.value), + (isTeamProjectFeatureEnabled.value && !isTeamProjectLimitExceeded.value), ); const hasPermissionToCreateProjects = computed(() => hasPermission(['rbac'], { rbac: { scope: 'project:create' } }), @@ -199,7 +199,7 @@ export const useProjectsStore = defineStore('projects', () => { hasUnlimitedProjects, canCreateProjects, hasPermissionToCreateProjects, - teamProjectsAvailable, + isTeamProjectFeatureEnabled, projectNavActiveId, setCurrentProject, getAllProjects, diff --git a/packages/editor-ui/src/views/SettingsCommunityNodesView.vue b/packages/editor-ui/src/views/SettingsCommunityNodesView.vue index fcfb3742383c6..194a289eb1b9b 100644 --- a/packages/editor-ui/src/views/SettingsCommunityNodesView.vue +++ b/packages/editor-ui/src/views/SettingsCommunityNodesView.vue @@ -3,25 +3,13 @@
{{ $locale.baseText('settings.communityNodes') }}
-
- -
-
+
{ v-if="workflow" :executions="executions" :execution="execution" - :filters="filters" :workflow="workflow" :loading="loading" :loading-more="loadingMore" diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index 2a8333cd91c86..d7b16155a1aa5 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -1,6 +1,6 @@ { "name": "n8n-node-dev", - "version": "1.52.0", + "version": "1.53.0", "description": "CLI to simplify n8n credentials/node development", "main": "dist/src/index", "types": "dist/src/index.d.ts", @@ -40,12 +40,12 @@ "dependencies": { "@oclif/core": "4.0.7", "change-case": "^4.1.1", - "fast-glob": "^3.2.5", + "fast-glob": "catalog:", "inquirer": "^7.0.1", "n8n-core": "workspace:*", "n8n-workflow": "workspace:*", "replace-in-file": "^6.0.0", "tmp-promise": "^3.0.3", - "typedi": "^0.10.0" + "typedi": "catalog:" } } diff --git a/packages/nodes-base/credentials/CalendlyOAuth2Api.credentials.ts b/packages/nodes-base/credentials/CalendlyOAuth2Api.credentials.ts new file mode 100644 index 0000000000000..200d238c6d73f --- /dev/null +++ b/packages/nodes-base/credentials/CalendlyOAuth2Api.credentials.ts @@ -0,0 +1,52 @@ +import type { ICredentialType, INodeProperties, Icon } from 'n8n-workflow'; + +export class CalendlyOAuth2Api implements ICredentialType { + name = 'calendlyOAuth2Api'; + + extends = ['oAuth2Api']; + + displayName = 'Calendly OAuth2 API'; + + documentationUrl = 'calendly'; + + icon: Icon = 'file:icons/Calendly.svg'; + + properties: INodeProperties[] = [ + { + displayName: 'Grant Type', + name: 'grantType', + type: 'hidden', + default: 'authorizationCode', + }, + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden', + default: 'https://auth.calendly.com/oauth/authorize', + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden', + default: 'https://auth.calendly.com/oauth/token', + }, + { + displayName: 'Authentication', + name: 'authentication', + type: 'hidden', + default: 'header', + }, + { + displayName: 'Auth URI Query Parameters', + name: 'authQueryParameters', + type: 'hidden', + default: '', + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden', + default: '', + }, + ]; +} diff --git a/packages/nodes-base/credentials/icons/Calendly.svg b/packages/nodes-base/credentials/icons/Calendly.svg new file mode 100644 index 0000000000000..195a7461e33b6 --- /dev/null +++ b/packages/nodes-base/credentials/icons/Calendly.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/nodes-base/nodes/Aws/AwsLambda.node.ts b/packages/nodes-base/nodes/Aws/AwsLambda.node.ts index e1fd51f13086c..ed69ab35bd1b8 100644 --- a/packages/nodes-base/nodes/Aws/AwsLambda.node.ts +++ b/packages/nodes-base/nodes/Aws/AwsLambda.node.ts @@ -195,13 +195,21 @@ export class AwsLambda implements INodeType { throw new NodeApiError(this.getNode(), responseData as JsonObject); } else { - returnData.push({ - result: responseData, - } as IDataObject); + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ + result: responseData, + }), + { itemData: { item: i } }, + ); + returnData.push(...executionData); } } catch (error) { if (this.continueOnFail(error)) { - returnData.push({ error: (error as JsonObject).message }); + const executionData = this.helpers.constructExecutionMetaData( + this.helpers.returnJsonArray({ error: (error as JsonObject).message }), + { itemData: { item: i } }, + ); + returnData.push(...executionData); continue; } throw error; diff --git a/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts b/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts index d2e7b127237d2..40a10cab92cfa 100644 --- a/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts +++ b/packages/nodes-base/nodes/Calendly/CalendlyTrigger.node.ts @@ -26,6 +26,20 @@ export class CalendlyTrigger implements INodeType { { name: 'calendlyApi', required: true, + displayOptions: { + show: { + authentication: ['apiKey'], + }, + }, + }, + { + name: 'calendlyOAuth2Api', + required: true, + displayOptions: { + show: { + authentication: ['oAuth2'], + }, + }, }, ], webhooks: [ @@ -37,6 +51,23 @@ export class CalendlyTrigger implements INodeType { }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + // eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased + name: 'OAuth2 (recommended)', + value: 'oAuth2', + }, + { + name: 'API Key or Personal Access Token', + value: 'apiKey', + }, + ], + default: 'apiKey', + }, { displayName: 'Scope', name: 'scope', @@ -86,9 +117,8 @@ export class CalendlyTrigger implements INodeType { const webhookUrl = this.getNodeWebhookUrl('default'); const webhookData = this.getWorkflowStaticData('node'); const events = this.getNodeParameter('events') as string; - const { apiKey } = (await this.getCredentials('calendlyApi')) as { apiKey: string }; - const authenticationType = getAuthenticationType(apiKey); + const authenticationType = await getAuthenticationType.call(this); // remove condition once API Keys are deprecated if (authenticationType === 'apiKey') { @@ -149,9 +179,8 @@ export class CalendlyTrigger implements INodeType { const webhookData = this.getWorkflowStaticData('node'); const webhookUrl = this.getNodeWebhookUrl('default'); const events = this.getNodeParameter('events') as string; - const { apiKey } = (await this.getCredentials('calendlyApi')) as { apiKey: string }; - const authenticationType = getAuthenticationType(apiKey); + const authenticationType = await getAuthenticationType.call(this); // remove condition once API Keys are deprecated if (authenticationType === 'apiKey') { @@ -201,8 +230,7 @@ export class CalendlyTrigger implements INodeType { }, async delete(this: IHookFunctions): Promise { const webhookData = this.getWorkflowStaticData('node'); - const { apiKey } = (await this.getCredentials('calendlyApi')) as { apiKey: string }; - const authenticationType = getAuthenticationType(apiKey); + const authenticationType = await getAuthenticationType.call(this); // remove condition once API Keys are deprecated if (authenticationType === 'apiKey') { diff --git a/packages/nodes-base/nodes/Calendly/GenericFunctions.ts b/packages/nodes-base/nodes/Calendly/GenericFunctions.ts index 4befbb5ceed21..f39aa877618b2 100644 --- a/packages/nodes-base/nodes/Calendly/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Calendly/GenericFunctions.ts @@ -1,6 +1,4 @@ import type { - ICredentialDataDecryptedObject, - ICredentialTestFunctions, IDataObject, IExecuteFunctions, ILoadOptionsFunctions, @@ -10,12 +8,24 @@ import type { IRequestOptions, } from 'n8n-workflow'; -export function getAuthenticationType(data: string): 'accessToken' | 'apiKey' { +function getAuthenticationTypeFromApiKey(data: string): 'accessToken' | 'apiKey' { // The access token is a JWT, so it will always include dots to separate // header, payoload and signature. return data.includes('.') ? 'accessToken' : 'apiKey'; } +export async function getAuthenticationType( + this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, +): Promise<'accessToken' | 'apiKey'> { + const authentication = this.getNodeParameter('authentication', 0) as string; + if (authentication === 'apiKey') { + const { apiKey } = (await this.getCredentials('calendlyApi')) as { apiKey: string }; + return getAuthenticationTypeFromApiKey(apiKey); + } else { + return 'accessToken'; + } +} + export async function calendlyApiRequest( this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, @@ -26,9 +36,7 @@ export async function calendlyApiRequest( uri?: string, option: IDataObject = {}, ): Promise { - const { apiKey } = (await this.getCredentials('calendlyApi')) as { apiKey: string }; - - const authenticationType = getAuthenticationType(apiKey); + const authenticationType = await getAuthenticationType.call(this); const headers: IDataObject = { 'Content-Type': 'application/json', @@ -57,37 +65,10 @@ export async function calendlyApiRequest( delete options.qs; } options = Object.assign({}, options, option); - return await this.helpers.requestWithAuthentication.call(this, 'calendlyApi', options); -} - -export async function validateCredentials( - this: ICredentialTestFunctions, - decryptedCredentials: ICredentialDataDecryptedObject, -): Promise { - const credentials = decryptedCredentials; - const { apiKey } = credentials as { - apiKey: string; - }; - - const authenticationType = getAuthenticationType(apiKey); - - const options: IRequestOptions = { - method: 'GET', - uri: '', - json: true, - }; - - if (authenticationType === 'accessToken') { - Object.assign(options, { - headers: { Authorization: `Bearer ${apiKey}` }, - uri: 'https://api.calendly.com/users/me', - }); - } else { - Object.assign(options, { - headers: { 'X-TOKEN': apiKey }, - uri: 'https://calendly.com/api/v1/users/me', - }); - } - return await this.helpers.request(options); + const credentialsType = + (this.getNodeParameter('authentication', 0) as string) === 'apiKey' + ? 'calendlyApi' + : 'calendlyOAuth2Api'; + return await this.helpers.requestWithAuthentication.call(this, credentialsType, options); } diff --git a/packages/nodes-base/nodes/Form/common.descriptions.ts b/packages/nodes-base/nodes/Form/common.descriptions.ts index 8ab9cc0927a53..c3505e9509f86 100644 --- a/packages/nodes-base/nodes/Form/common.descriptions.ts +++ b/packages/nodes-base/nodes/Form/common.descriptions.ts @@ -54,7 +54,7 @@ export const formFields: INodeProperties = { type: 'string', default: '', placeholder: 'e.g. What is your name?', - description: 'Label appears above the input field', + description: 'Label that appears above the input field', required: true, }, { @@ -102,6 +102,7 @@ export const formFields: INodeProperties = { { displayName: 'Placeholder', name: 'placeholder', + description: 'Sample text to display inside the field', type: 'string', default: '', displayOptions: { @@ -169,11 +170,11 @@ export const formFields: INodeProperties = { }, }, { - displayName: 'Accept File Types', + displayName: 'Accepted File Types', name: 'acceptFileTypes', type: 'string', default: '', - description: 'List of file types that can be uploaded, separated by commas', + description: 'Comma-separated list of allowed file extensions', hint: 'Leave empty to allow all file types', placeholder: 'e.g. .jpg, .png', displayOptions: { @@ -188,7 +189,7 @@ export const formFields: INodeProperties = { type: 'string', default: '', description: - 'Returns a string representation of this field formatted according to the specified format string. For a table of tokens and their interpretations, see here.', + 'How to format the date in the output data. For a table of tokens and their interpretations, see here.', placeholder: 'e.g. dd/mm/yyyy', hint: 'Leave empty to use the default format', displayOptions: { diff --git a/packages/nodes-base/nodes/Form/utils.ts b/packages/nodes-base/nodes/Form/utils.ts index 1928c935bdea8..2e79c5699ce5f 100644 --- a/packages/nodes-base/nodes/Form/utils.ts +++ b/packages/nodes-base/nodes/Form/utils.ts @@ -140,8 +140,11 @@ const checkResponseModeConfiguration = (context: IWebhookFunctions) => { } }; -export async function formWebhook(context: IWebhookFunctions) { - const nodeVersion = context.getNode().typeVersion; +export async function formWebhook( + context: IWebhookFunctions, + authProperty = FORM_TRIGGER_AUTHENTICATION_PROPERTY, +) { + const node = context.getNode(); const options = context.getNodeParameter('options', {}) as { ignoreBots?: boolean; respondWithOptions?: { @@ -159,9 +162,10 @@ export async function formWebhook(context: IWebhookFunctions) { const req = context.getRequestObject(); try { - if (options.ignoreBots && isbot(req.headers['user-agent'])) + if (options.ignoreBots && isbot(req.headers['user-agent'])) { throw new WebhookAuthorizationError(403); - await validateWebhookAuthentication(context, FORM_TRIGGER_AUTHENTICATION_PROPERTY); + } + await validateWebhookAuthentication(context, authProperty); } catch (error) { if (error instanceof WebhookAuthorizationError) { res.writeHead(error.responseCode, { 'WWW-Authenticate': 'Basic realm="Webhook"' }); @@ -310,7 +314,7 @@ export async function formWebhook(context: IWebhookFunctions) { let { useWorkflowTimezone } = options; - if (useWorkflowTimezone === undefined && nodeVersion > 2) { + if (useWorkflowTimezone === undefined && node.typeVersion > 2) { useWorkflowTimezone = true; } diff --git a/packages/nodes-base/nodes/Postgres/Postgres.node.ts b/packages/nodes-base/nodes/Postgres/Postgres.node.ts index 05e09dff4dfcb..0e1760faeac94 100644 --- a/packages/nodes-base/nodes/Postgres/Postgres.node.ts +++ b/packages/nodes-base/nodes/Postgres/Postgres.node.ts @@ -11,7 +11,7 @@ export class Postgres extends VersionedNodeType { name: 'postgres', icon: 'file:postgres.svg', group: ['input'], - defaultVersion: 2.4, + defaultVersion: 2.5, description: 'Get, add and update data in Postgres', parameterPane: 'wide', }; @@ -23,6 +23,7 @@ export class Postgres extends VersionedNodeType { 2.2: new PostgresV2(baseDescription), 2.3: new PostgresV2(baseDescription), 2.4: new PostgresV2(baseDescription), + 2.5: new PostgresV2(baseDescription), }; super(nodeVersions, baseDescription); diff --git a/packages/nodes-base/nodes/Postgres/v2/actions/database/executeQuery.operation.ts b/packages/nodes-base/nodes/Postgres/v2/actions/database/executeQuery.operation.ts index 2b8449e983ccb..4e6100e5755a2 100644 --- a/packages/nodes-base/nodes/Postgres/v2/actions/database/executeQuery.operation.ts +++ b/packages/nodes-base/nodes/Postgres/v2/actions/database/executeQuery.operation.ts @@ -3,6 +3,7 @@ import type { IExecuteFunctions, INodeExecutionData, INodeProperties, + NodeParameterValueType, } from 'n8n-workflow'; import { NodeOperationError } from 'n8n-workflow'; @@ -78,22 +79,45 @@ export async function execute( const rawReplacements = (node.parameters.options as IDataObject)?.queryReplacement as string; - if (rawReplacements) { - const rawValues = rawReplacements - .replace(/^=+/, '') + const stringToArray = (str: NodeParameterValueType | undefined) => { + if (!str) return []; + return String(str) .split(',') .filter((entry) => entry) .map((entry) => entry.trim()); + }; - for (const rawValue of rawValues) { - const resolvables = getResolvables(rawValue); + if (rawReplacements) { + const nodeVersion = nodeOptions.nodeVersion as number; + if (nodeVersion >= 2.5) { + const rawValues = rawReplacements.replace(/^=+/, ''); + const resolvables = getResolvables(rawValues); if (resolvables.length) { for (const resolvable of resolvables) { - values.push(this.evaluateExpression(`${resolvable}`, i) as IDataObject); + const evaluatedValues = stringToArray(this.evaluateExpression(`${resolvable}`, i)); + if (evaluatedValues.length) values.push(...evaluatedValues); } } else { - values.push(rawValue); + values.push(...stringToArray(rawValues)); + } + } else { + const rawValues = rawReplacements + .replace(/^=+/, '') + .split(',') + .filter((entry) => entry) + .map((entry) => entry.trim()); + + for (const rawValue of rawValues) { + const resolvables = getResolvables(rawValue); + + if (resolvables.length) { + for (const resolvable of resolvables) { + values.push(this.evaluateExpression(`${resolvable}`, i) as IDataObject); + } + } else { + values.push(rawValue); + } } } } diff --git a/packages/nodes-base/nodes/Postgres/v2/actions/versionDescription.ts b/packages/nodes-base/nodes/Postgres/v2/actions/versionDescription.ts index 1687accf1cde4..de6047003de9e 100644 --- a/packages/nodes-base/nodes/Postgres/v2/actions/versionDescription.ts +++ b/packages/nodes-base/nodes/Postgres/v2/actions/versionDescription.ts @@ -8,7 +8,7 @@ export const versionDescription: INodeTypeDescription = { name: 'postgres', icon: 'file:postgres.svg', group: ['input'], - version: [2, 2.1, 2.2, 2.3, 2.4], + version: [2, 2.1, 2.2, 2.3, 2.4, 2.5], subtitle: '={{ $parameter["operation"] }}', description: 'Get, add and update data in Postgres', defaults: { diff --git a/packages/nodes-base/nodes/Wait/Wait.node.ts b/packages/nodes-base/nodes/Wait/Wait.node.ts index 181a208f923c3..98badea8331d8 100644 --- a/packages/nodes-base/nodes/Wait/Wait.node.ts +++ b/packages/nodes-base/nodes/Wait/Wait.node.ts @@ -237,6 +237,14 @@ export class Wait extends Webhook { inputs: ['main'], outputs: ['main'], credentials: credentialsProperty(this.authPropertyName), + hints: [ + { + message: + "When testing your workflow using the Editor UI, you can't see the rest of the execution following the Wait node. To inspect the execution results, enable Save Manual Executions in your Workflow settings so you can review the execution results there.", + location: 'outputPane', + whenToDisplay: 'beforeExecution', + }, + ], webhooks: [ { ...defaultWebhookDescription, @@ -294,6 +302,29 @@ export class Wait extends Webhook { default: 'timeInterval', description: 'Determines the waiting mode to use before the workflow continues', }, + { + displayName: 'Authentication', + name: 'incomingAuthentication', + type: 'options', + options: [ + { + name: 'Basic Auth', + value: 'basicAuth', + }, + { + name: 'None', + value: 'none', + }, + ], + default: 'none', + description: + 'If and how incoming resume-webhook-requests to $execution.resumeFormUrl should be authenticated for additional security', + displayOptions: { + show: { + resume: ['form'], + }, + }, + }, { ...authenticationProperty(this.authPropertyName), description: @@ -427,7 +458,7 @@ export class Wait extends Webhook { async webhook(context: IWebhookFunctions) { const resume = context.getNodeParameter('resume', 0) as string; - if (resume === 'form') return await formWebhook(context); + if (resume === 'form') return await formWebhook(context, this.authPropertyName); return await super.webhook(context); } diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index ba352f63fc494..e9da85e9faf47 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-base", - "version": "1.52.0", + "version": "1.53.0", "description": "Base nodes of n8n", "main": "index.js", "scripts": { @@ -52,6 +52,7 @@ "dist/credentials/BubbleApi.credentials.js", "dist/credentials/CalApi.credentials.js", "dist/credentials/CalendlyApi.credentials.js", + "dist/credentials/CalendlyOAuth2Api.credentials.js", "dist/credentials/CarbonBlackApi.credentials.js", "dist/credentials/ChargebeeApi.credentials.js", "dist/credentials/CircleCiApi.credentials.js", @@ -802,15 +803,15 @@ "devDependencies": { "@types/amqplib": "^0.10.1", "@types/aws4": "^1.5.1", - "@types/basic-auth": "^1.1.3", + "@types/basic-auth": "catalog:", "@types/cheerio": "^0.22.15", "@types/eventsource": "^1.1.2", - "@types/express": "^4.17.21", + "@types/express": "catalog:", "@types/html-to-text": "^9.0.1", "@types/gm": "^1.25.0", "@types/js-nacl": "^1.3.0", "@types/jsonwebtoken": "^9.0.6", - "@types/lodash": "^4.14.195", + "@types/lodash": "catalog:", "@types/lossless-json": "^1.0.0", "@types/mailparser": "^3.4.4", "@types/mime-types": "^2.1.0", @@ -821,19 +822,19 @@ "@types/showdown": "^1.9.4", "@types/snowflake-sdk": "^1.6.20", "@types/ssh2-sftp-client": "^5.1.0", - "@types/uuid": "^8.3.2", - "@types/xml2js": "^0.4.14", + "@types/uuid": "catalog:", + "@types/xml2js": "catalog:", "eslint-plugin-n8n-nodes-base": "^1.16.0", "n8n-core": "workspace:*" }, "dependencies": { "@kafkajs/confluent-schema-registry": "1.0.6", "@n8n/imap": "workspace:*", - "@n8n/vm2": "3.9.20", + "@n8n/vm2": "3.9.24", "amqplib": "0.10.3", "alasql": "^4.4.0", "aws4": "1.11.0", - "basic-auth": "2.0.1", + "basic-auth": "catalog:", "change-case": "4.1.2", "cheerio": "1.0.0-rc.6", "chokidar": "3.5.2", @@ -842,7 +843,7 @@ "currency-codes": "2.1.0", "eventsource": "2.0.2", "html-to-text": "9.0.5", - "fast-glob": "3.2.12", + "fast-glob": "catalog:", "fflate": "0.7.4", "get-system-fonts": "2.0.2", "gm": "1.25.0", @@ -854,9 +855,9 @@ "jsonwebtoken": "9.0.2", "kafkajs": "1.16.0", "ldapts": "4.2.6", - "lodash": "4.17.21", + "lodash": "catalog:", "lossless-json": "1.0.5", - "luxon": "3.3.0", + "luxon": "catalog:", "mailparser": "3.6.7", "minifaker": "1.34.1", "moment-timezone": "0.5.37", @@ -886,9 +887,9 @@ "ssh2-sftp-client": "7.2.3", "tmp-promise": "3.0.3", "ts-ics": "^1.2.2", - "typedi": "0.10.0", - "uuid": "8.3.2", + "typedi": "catalog:", + "uuid": "catalog:", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz", - "xml2js": "0.6.2" + "xml2js": "catalog:" } } diff --git a/packages/workflow/package.json b/packages/workflow/package.json index 96a4f31fd16fa..77348f2a945e1 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -1,6 +1,6 @@ { "name": "n8n-workflow", - "version": "1.51.0", + "version": "1.52.0", "description": "Workflow base code of n8n", "main": "dist/index.js", "module": "src/index.ts", @@ -31,31 +31,31 @@ "devDependencies": { "@langchain/core": "^0.2.18", "@types/deep-equal": "^1.0.1", - "@types/express": "^4.17.21", + "@types/express": "catalog:", "@types/jmespath": "^0.15.0", - "@types/lodash": "^4.14.195", + "@types/lodash": "catalog:", "@types/luxon": "^3.2.0", "@types/md5": "^2.3.5", - "@types/xml2js": "^0.4.14" + "@types/xml2js": "catalog:" }, "dependencies": { - "@n8n/tournament": "1.0.2", + "@n8n/tournament": "1.0.3", "@n8n_io/riot-tmpl": "4.0.0", "ast-types": "0.15.2", - "axios": "1.6.7", + "axios": "catalog:", "callsites": "3.1.0", "deep-equal": "2.2.0", "esprima-next": "5.8.4", - "form-data": "4.0.0", + "form-data": "catalog:", "jmespath": "0.16.0", "js-base64": "3.7.2", "jssha": "3.3.1", - "lodash": "4.17.21", - "luxon": "3.3.0", + "lodash": "catalog:", + "luxon": "catalog:", "md5": "2.3.0", "recast": "0.21.5", "title-case": "3.0.3", "transliteration": "2.3.5", - "xml2js": "0.6.2" + "xml2js": "catalog:" } } diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index ea31b06fef34b..39b4d06b47d98 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -466,6 +466,7 @@ export interface IGetExecuteWebhookFunctions { mode: WorkflowExecuteMode, webhookData: IWebhookData, closeFunctions: CloseFunction[], + runExecutionData: IRunExecutionData | null, ): IWebhookFunctions; } diff --git a/packages/workflow/src/Workflow.ts b/packages/workflow/src/Workflow.ts index 3c74480b3864f..ef0539db28a97 100644 --- a/packages/workflow/src/Workflow.ts +++ b/packages/workflow/src/Workflow.ts @@ -1237,6 +1237,7 @@ export class Workflow { additionalData: IWorkflowExecuteAdditionalData, nodeExecuteFunctions: INodeExecuteFunctions, mode: WorkflowExecuteMode, + runExecutionData: IRunExecutionData | null, ): Promise { const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); if (nodeType === undefined) { @@ -1258,6 +1259,7 @@ export class Workflow { mode, webhookData, closeFunctions, + runExecutionData, ); return nodeType instanceof Node ? await nodeType.webhook(context) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e97519b806455..8287287e22db9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,70 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +catalogs: + default: + '@types/basic-auth': + specifier: ^1.1.3 + version: 1.1.3 + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 + '@types/lodash': + specifier: ^4.14.195 + version: 4.14.195 + '@types/uuid': + specifier: ^8.3.2 + version: 8.3.4 + '@types/xml2js': + specifier: ^0.4.14 + version: 0.4.14 + basic-auth: + specifier: 2.0.1 + version: 2.0.1 + fast-glob: + specifier: 3.2.12 + version: 3.2.12 + form-data: + specifier: 4.0.0 + version: 4.0.0 + lodash: + specifier: 4.17.21 + version: 4.17.21 + luxon: + specifier: 3.4.4 + version: 3.4.4 + nanoid: + specifier: 3.3.6 + version: 3.3.6 + typedi: + specifier: 0.10.0 + version: 0.10.0 + uuid: + specifier: 8.3.2 + version: 8.3.2 + xml2js: + specifier: 0.6.2 + version: 0.6.2 + frontend: + '@vitest/coverage-v8': + specifier: ^1.6.0 + version: 1.6.0 + vite: + specifier: ^5.2.12 + version: 5.2.12 + vitest: + specifier: ^1.6.0 + version: 1.6.0 + vitest-mock-extended: + specifier: ^1.3.1 + version: 1.3.1 + vue: + specifier: ^3.4.21 + version: 3.4.21 + vue-tsc: + specifier: ^2.0.19 + version: 2.0.19 + overrides: '@types/node': ^18.16.16 axios: 1.6.7 @@ -54,9 +118,6 @@ importers: '@types/supertest': specifier: ^6.0.2 version: 6.0.2 - '@vitest/coverage-v8': - specifier: ^1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) jest: specifier: ^29.6.2 version: 29.6.2(@types/node@18.16.16) @@ -105,18 +166,6 @@ importers: typescript: specifier: ^5.5.2 version: 5.5.2 - vite: - specifier: ^5.2.12 - version: 5.2.12(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1) - vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1) - vitest-mock-extended: - specifier: ^1.3.1 - version: 1.3.1(typescript@5.5.2)(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) - vue-tsc: - specifier: ^2.0.19 - version: 2.0.19(typescript@5.5.2) cypress: dependencies: @@ -136,17 +185,17 @@ importers: specifier: ^1.12.0 version: 1.12.0(cypress@13.11.0) lodash: - specifier: 4.17.21 + specifier: 'catalog:' version: 4.17.21 nanoid: - specifier: 3.3.6 + specifier: 'catalog:' version: 3.3.6 start-server-and-test: specifier: ^2.0.3 version: 2.0.3 devDependencies: '@types/lodash': - specifier: ^4.14.195 + specifier: 'catalog:' version: 4.14.195 eslint-plugin-cypress: specifier: ^3.3.0 @@ -167,10 +216,10 @@ importers: specifier: ^4.0.1 version: 4.0.1 uuid: - specifier: ^8.3.2 + specifier: 'catalog:' version: 8.3.2 vue: - specifier: ^3.4.21 + specifier: catalog:frontend version: 3.4.21(typescript@5.5.2) vue-markdown-render: specifier: ^2.1.1 @@ -185,12 +234,24 @@ importers: '@types/markdown-it': specifier: ^12.2.3 version: 12.2.3 + '@vitest/coverage-v8': + specifier: catalog:frontend + version: 1.6.0(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) unplugin-icons: specifier: ^0.19.0 version: 0.19.0(@vue/compiler-sfc@3.4.21)(vue-template-compiler@2.7.14) + vite: + specifier: catalog:frontend + version: 5.2.12(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1) vite-plugin-dts: specifier: ^3.9.1 version: 3.9.1(@types/node@18.16.16)(rollup@4.18.0)(typescript@5.5.2)(vite@5.2.12(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) + vitest: + specifier: catalog:frontend + version: 1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1) + vue-tsc: + specifier: catalog:frontend + version: 2.0.19(typescript@5.5.2) packages/@n8n/client-oauth2: dependencies: @@ -220,7 +281,7 @@ importers: specifier: 0.2.2 version: 0.2.2 typedi: - specifier: 0.10.0 + specifier: 'catalog:' version: 0.10.0(patch_hash=sk6omkefrosihg7lmqbzh7vfxe) packages/@n8n/imap: @@ -288,10 +349,10 @@ importers: version: 0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)) '@langchain/community': specifier: 0.2.20 - version: 0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1) + version: 0.2.20(bl2awxa6z6ereahlcpz2dlbf6q) '@langchain/core': specifier: 0.2.18 - version: 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + version: 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) '@langchain/google-genai': specifier: 0.0.23 version: 0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8) @@ -309,19 +370,19 @@ importers: version: 0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)) '@langchain/openai': specifier: 0.2.5 - version: 0.2.5(encoding@0.1.13)(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1)) + version: 0.2.5(encoding@0.1.13)(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq)) '@langchain/pinecone': specifier: 0.0.8 - version: 0.0.8(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + version: 0.0.8(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) '@langchain/qdrant': specifier: ^0.0.5 - version: 0.0.5(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13))(typescript@5.5.2) + version: 0.0.5(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13))(typescript@5.5.2) '@langchain/redis': specifier: 0.0.5 - version: 0.0.5(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + version: 0.0.5(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) '@langchain/textsplitters': specifier: 0.0.3 - version: 0.0.3(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + version: 0.0.3(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) '@mozilla/readability': specifier: ^0.5.0 version: 0.5.0 @@ -329,8 +390,8 @@ importers: specifier: 0.3.20-10 version: 0.3.20-10(@sentry/node@7.87.0)(ioredis@5.3.2)(mssql@10.0.2)(mysql2@3.10.0)(pg@8.11.3)(redis@4.6.12)(sqlite3@5.1.7) '@n8n/vm2': - specifier: 3.9.20 - version: 3.9.20 + specifier: 3.9.24 + version: 3.9.24 '@pinecone-database/pinecone': specifier: 3.0.0 version: 3.0.0 @@ -347,7 +408,7 @@ importers: specifier: 0.28.4 version: 0.28.4(typescript@5.5.2) basic-auth: - specifier: 2.0.1 + specifier: 'catalog:' version: 2.0.1 cheerio: specifier: 1.0.0-rc.12 @@ -362,7 +423,7 @@ importers: specifier: 3.0.2 version: 3.0.2(ts-toolbelt@9.6.0) form-data: - specifier: 4.0.0 + specifier: 'catalog:' version: 4.0.0 generate-schema: specifier: 2.6.0 @@ -378,9 +439,9 @@ importers: version: 2.1.0 langchain: specifier: 0.2.11 - version: 0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1) + version: 0.2.11(3eonfu4fvxvjb5zdrprnb3pokq) lodash: - specifier: 4.17.21 + specifier: 'catalog:' version: 4.17.21 mammoth: specifier: 1.7.2 @@ -420,7 +481,7 @@ importers: version: 3.23.0(zod@3.23.8) devDependencies: '@types/basic-auth': - specifier: ^1.1.3 + specifier: 'catalog:' version: 1.1.3 '@types/cheerio': specifier: ^0.22.15 @@ -459,7 +520,7 @@ importers: version: 8.1.4(@types/react@18.0.27)(encoding@0.1.13)(prettier@3.2.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@storybook/addon-interactions': specifier: ^8.1.4 - version: 8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) + version: 8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) '@storybook/addon-links': specifier: ^8.1.4 version: 8.1.4(react@18.2.0) @@ -471,7 +532,7 @@ importers: version: 8.1.4(@types/react@18.0.27)(encoding@0.1.13)(prettier@3.2.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@storybook/test': specifier: ^8.1.4 - version: 8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) + version: 8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) '@storybook/vue3': specifier: ^8.1.4 version: 8.1.4(encoding@0.1.13)(prettier@3.2.5)(vue@3.4.21(typescript@5.5.2)) @@ -547,6 +608,9 @@ importers: '@azure/keyvault-secrets': specifier: ^4.8.0 version: 4.8.0 + '@google-cloud/secret-manager': + specifier: ^5.6.0 + version: 5.6.0(encoding@0.1.13) '@n8n/client-oauth2': specifier: workspace:* version: link:../@n8n/client-oauth2 @@ -644,7 +708,7 @@ importers: specifier: 7.2.0 version: 7.2.0(express@4.19.2) fast-glob: - specifier: 3.2.12 + specifier: 'catalog:' version: 3.2.12 flat: specifier: 5.0.2 @@ -683,11 +747,11 @@ importers: specifier: 4.2.6 version: 4.2.6 lodash: - specifier: 4.17.21 + specifier: 'catalog:' version: 4.17.21 luxon: - specifier: 3.3.0 - version: 3.3.0 + specifier: 'catalog:' + version: 3.4.4 mysql2: specifier: 3.10.0 version: 3.10.0 @@ -704,7 +768,7 @@ importers: specifier: workspace:* version: link:../workflow nanoid: - specifier: 3.3.6 + specifier: 'catalog:' version: 3.3.6 nodemailer: specifier: 6.9.9 @@ -785,10 +849,10 @@ importers: specifier: 1.1.1 version: 1.1.1 typedi: - specifier: 0.10.0 + specifier: 'catalog:' version: 0.10.0(patch_hash=sk6omkefrosihg7lmqbzh7vfxe) uuid: - specifier: 8.3.2 + specifier: 'catalog:' version: 8.3.2 validator: specifier: 13.7.0 @@ -800,7 +864,7 @@ importers: specifier: '>=8.17.1' version: 8.17.1 xml2js: - specifier: 0.6.2 + specifier: 'catalog:' version: 0.6.2 xmllint-wasm: specifier: 3.0.1 @@ -831,7 +895,7 @@ importers: specifier: ^1.4.2 version: 1.4.3 '@types/express': - specifier: ^4.17.21 + specifier: 'catalog:' version: 4.17.21 '@types/flat': specifier: ^5.0.5 @@ -846,7 +910,7 @@ importers: specifier: ^9.0.6 version: 9.0.6 '@types/lodash': - specifier: ^4.14.195 + specifier: 'catalog:' version: 4.14.195 '@types/psl': specifier: ^1.1.0 @@ -870,7 +934,7 @@ importers: specifier: ^1.1.2 version: 1.1.2 '@types/uuid': - specifier: ^8.3.2 + specifier: 'catalog:' version: 8.3.4 '@types/validator': specifier: ^13.7.0 @@ -879,7 +943,7 @@ importers: specifier: ^8.5.4 version: 8.5.4(patch_hash=nbzuqaoyqbrfwipijj5qriqqju) '@types/xml2js': - specifier: ^0.4.14 + specifier: 'catalog:' version: 0.4.14 '@types/yamljs': specifier: ^0.2.31 @@ -918,19 +982,19 @@ importers: specifier: 3.1.7 version: 3.1.7 fast-glob: - specifier: 3.2.12 + specifier: 'catalog:' version: 3.2.12 file-type: specifier: 16.5.4 version: 16.5.4 form-data: - specifier: 4.0.0 + specifier: 'catalog:' version: 4.0.0 lodash: - specifier: 4.17.21 + specifier: 'catalog:' version: 4.17.21 luxon: - specifier: ^3.4.4 + specifier: 'catalog:' version: 3.4.4 mime-types: specifier: 2.1.35 @@ -954,13 +1018,13 @@ importers: specifier: 1.15.0 version: 1.15.0 typedi: - specifier: 0.10.0 + specifier: 'catalog:' version: 0.10.0(patch_hash=sk6omkefrosihg7lmqbzh7vfxe) uuid: - specifier: 8.3.2 + specifier: 'catalog:' version: 8.3.2 xml2js: - specifier: 0.6.2 + specifier: 'catalog:' version: 0.6.2 devDependencies: '@types/aws4': @@ -970,19 +1034,19 @@ importers: specifier: ^2.0.0 version: 2.0.0 '@types/express': - specifier: ^4.17.21 + specifier: 'catalog:' version: 4.17.21 '@types/lodash': - specifier: ^4.14.195 + specifier: 'catalog:' version: 4.14.195 '@types/mime-types': specifier: ^2.1.0 version: 2.1.1 '@types/uuid': - specifier: ^8.3.2 + specifier: 'catalog:' version: 8.3.4 '@types/xml2js': - specifier: ^0.4.14 + specifier: 'catalog:' version: 0.4.14 packages/design-system: @@ -1015,7 +1079,7 @@ importers: specifier: 2.12.1 version: 2.12.1 vue: - specifier: ^3.4.21 + specifier: catalog:frontend version: 3.4.21(typescript@5.5.2) vue-boring-avatars: specifier: ^1.3.0 @@ -1054,6 +1118,9 @@ importers: '@vitejs/plugin-vue': specifier: ^5.0.4 version: 5.0.4(vite@5.2.12(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1))(vue@3.4.21(typescript@5.5.2)) + '@vitest/coverage-v8': + specifier: catalog:frontend + version: 1.6.0(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) '@vue/test-utils': specifier: ^2.4.3 version: 2.4.3(@vue/server-renderer@3.4.21(vue@3.4.21(typescript@5.5.2)))(vue@3.4.21(typescript@5.5.2)) @@ -1075,6 +1142,18 @@ importers: unplugin-vue-components: specifier: ^0.27.2 version: 0.27.3(@babel/parser@7.24.6)(rollup@4.18.0)(vue@3.4.21(typescript@5.5.2)) + vite: + specifier: catalog:frontend + version: 5.2.12(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1) + vitest: + specifier: catalog:frontend + version: 1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1) + vitest-mock-extended: + specifier: catalog:frontend + version: 1.3.1(typescript@5.5.2)(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) + vue-tsc: + specifier: catalog:frontend + version: 2.0.19(typescript@5.5.2) packages/editor-ui: dependencies: @@ -1208,8 +1287,8 @@ importers: specifier: ^4.17.21 version: 4.17.21 luxon: - specifier: ^3.3.0 - version: 3.3.0 + specifier: 'catalog:' + version: 3.4.4 n8n-design-system: specifier: workspace:* version: link:../design-system @@ -1232,13 +1311,13 @@ importers: specifier: ^4.0.2 version: 4.0.2 uuid: - specifier: ^8.3.2 + specifier: 'catalog:' version: 8.3.2 v3-infinite-loading: specifier: ^1.2.2 version: 1.2.2 vue: - specifier: ^3.4.21 + specifier: catalog:frontend version: 3.4.21(typescript@5.5.2) vue-agile: specifier: ^2.0.0 @@ -1296,8 +1375,11 @@ importers: specifier: ^3.2.0 version: 3.2.0 '@types/uuid': - specifier: ^8.3.2 + specifier: 'catalog:' version: 8.3.4 + '@vitest/coverage-v8': + specifier: catalog:frontend + version: 1.6.0(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) cross-env: specifier: ^7.0.3 version: 7.0.3 @@ -1310,6 +1392,18 @@ importers: unplugin-vue-components: specifier: ^0.27.2 version: 0.27.3(@babel/parser@7.24.6)(rollup@4.18.0)(vue@3.4.21(typescript@5.5.2)) + vite: + specifier: catalog:frontend + version: 5.2.12(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1) + vitest: + specifier: catalog:frontend + version: 1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1) + vitest-mock-extended: + specifier: catalog:frontend + version: 1.3.1(typescript@5.5.2)(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) + vue-tsc: + specifier: catalog:frontend + version: 2.0.19(typescript@5.5.2) packages/node-dev: dependencies: @@ -1320,7 +1414,7 @@ importers: specifier: ^4.1.1 version: 4.1.2 fast-glob: - specifier: ^3.2.5 + specifier: 'catalog:' version: 3.2.12 inquirer: specifier: ^7.0.1 @@ -1338,7 +1432,7 @@ importers: specifier: ^3.0.3 version: 3.0.3 typedi: - specifier: ^0.10.0 + specifier: 'catalog:' version: 0.10.0(patch_hash=sk6omkefrosihg7lmqbzh7vfxe) devDependencies: '@types/inquirer': @@ -1354,8 +1448,8 @@ importers: specifier: workspace:* version: link:../@n8n/imap '@n8n/vm2': - specifier: 3.9.20 - version: 3.9.20 + specifier: 3.9.24 + version: 3.9.24 alasql: specifier: ^4.4.0 version: 4.4.0(encoding@0.1.13) @@ -1366,7 +1460,7 @@ importers: specifier: 1.11.0 version: 1.11.0 basic-auth: - specifier: 2.0.1 + specifier: 'catalog:' version: 2.0.1 change-case: specifier: 4.1.2 @@ -1390,7 +1484,7 @@ importers: specifier: 2.0.2 version: 2.0.2 fast-glob: - specifier: 3.2.12 + specifier: 'catalog:' version: 3.2.12 fflate: specifier: 0.7.4 @@ -1429,14 +1523,14 @@ importers: specifier: 4.2.6 version: 4.2.6 lodash: - specifier: 4.17.21 + specifier: 'catalog:' version: 4.17.21 lossless-json: specifier: 1.0.5 version: 1.0.5 luxon: - specifier: 3.3.0 - version: 3.3.0 + specifier: 'catalog:' + version: 3.4.4 mailparser: specifier: 3.6.7 version: 3.6.7 @@ -1525,16 +1619,16 @@ importers: specifier: ^1.2.2 version: 1.2.2(date-fns@2.30.0)(lodash@4.17.21)(zod@3.23.8) typedi: - specifier: 0.10.0 + specifier: 'catalog:' version: 0.10.0(patch_hash=sk6omkefrosihg7lmqbzh7vfxe) uuid: - specifier: 8.3.2 + specifier: 'catalog:' version: 8.3.2 xlsx: specifier: https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz version: https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz xml2js: - specifier: 0.6.2 + specifier: 'catalog:' version: 0.6.2 devDependencies: '@types/amqplib': @@ -1544,7 +1638,7 @@ importers: specifier: ^1.5.1 version: 1.11.2 '@types/basic-auth': - specifier: ^1.1.3 + specifier: 'catalog:' version: 1.1.3 '@types/cheerio': specifier: ^0.22.15 @@ -1553,7 +1647,7 @@ importers: specifier: ^1.1.2 version: 1.1.9 '@types/express': - specifier: ^4.17.21 + specifier: 'catalog:' version: 4.17.21 '@types/gm': specifier: ^1.25.0 @@ -1568,7 +1662,7 @@ importers: specifier: ^9.0.6 version: 9.0.6 '@types/lodash': - specifier: ^4.14.195 + specifier: 'catalog:' version: 4.14.195 '@types/lossless-json': specifier: ^1.0.0 @@ -1601,10 +1695,10 @@ importers: specifier: ^5.1.0 version: 5.3.2 '@types/uuid': - specifier: ^8.3.2 + specifier: 'catalog:' version: 8.3.4 '@types/xml2js': - specifier: ^0.4.14 + specifier: 'catalog:' version: 0.4.14 eslint-plugin-n8n-nodes-base: specifier: ^1.16.0 @@ -1616,8 +1710,8 @@ importers: packages/workflow: dependencies: '@n8n/tournament': - specifier: 1.0.2 - version: 1.0.2 + specifier: 1.0.3 + version: 1.0.3 '@n8n_io/riot-tmpl': specifier: 4.0.0 version: 4.0.0 @@ -1637,7 +1731,7 @@ importers: specifier: 5.8.4 version: 5.8.4 form-data: - specifier: 4.0.0 + specifier: 'catalog:' version: 4.0.0 jmespath: specifier: 0.16.0 @@ -1649,11 +1743,11 @@ importers: specifier: 3.3.1 version: 3.3.1 lodash: - specifier: 4.17.21 + specifier: 'catalog:' version: 4.17.21 luxon: - specifier: 3.3.0 - version: 3.3.0 + specifier: 'catalog:' + version: 3.4.4 md5: specifier: 2.3.0 version: 2.3.0 @@ -1667,23 +1761,23 @@ importers: specifier: 2.3.5 version: 2.3.5 xml2js: - specifier: 0.6.2 + specifier: 'catalog:' version: 0.6.2 devDependencies: '@langchain/core': specifier: ^0.2.18 - version: 0.2.18(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0) + version: 0.2.18(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0) '@types/deep-equal': specifier: ^1.0.1 version: 1.0.1 '@types/express': - specifier: ^4.17.21 + specifier: 'catalog:' version: 4.17.21 '@types/jmespath': specifier: ^0.15.0 version: 0.15.0 '@types/lodash': - specifier: ^4.14.195 + specifier: 'catalog:' version: 4.14.195 '@types/luxon': specifier: ^3.2.0 @@ -1692,7 +1786,7 @@ importers: specifier: ^2.3.5 version: 2.3.5 '@types/xml2js': - specifier: ^0.4.14 + specifier: 'catalog:' version: 0.4.14 packages: @@ -3269,6 +3363,10 @@ packages: resolution: {integrity: sha512-uWJJf6S2PJL7oZ4ezv16aZl9+IJqPo5GzUv1pZ3/qRiMj13p0ylEgX1+LxBpX71eEPKTwMHoJV2IBBe3EAq7Xw==} engines: {node: '>=14.0.0'} + '@google-cloud/secret-manager@5.6.0': + resolution: {integrity: sha512-0daW/OXQEVc6VQKPyJTQNyD+563I/TYQ7GCQJx4dq3lB666R9FUPvqHx9b/o/qQtZ5pfuoCbGZl3krpxgTSW8Q==} + engines: {node: '>=14.0.0'} + '@google-cloud/storage@6.11.0': resolution: {integrity: sha512-p5VX5K2zLTrMXlKdS1CiQNkKpygyn7CBFm5ZvfhVj6+7QUsjWvYx9YDMkYXdarZ6JDt4cxiu451y9QUIH82ZTw==} engines: {node: '>=12'} @@ -3285,11 +3383,6 @@ packages: resolution: {integrity: sha512-vYVqYzHicDqyKB+NQhAc54I1QWCBLCrYG6unqOIcBTHx+7x8C9lcoLj3KVJXs2VB4lUbpWY+Kk9NipcbXYWmvg==} engines: {node: '>=12.10.0'} - '@grpc/proto-loader@0.7.10': - resolution: {integrity: sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==} - engines: {node: '>=6'} - hasBin: true - '@grpc/proto-loader@0.7.13': resolution: {integrity: sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==} engines: {node: '>=6'} @@ -3470,15 +3563,9 @@ packages: '@jridgewell/source-map@0.3.2': resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==} - '@jridgewell/sourcemap-codec@1.4.14': - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - '@jridgewell/trace-mapping@0.3.18': - resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} - '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -4056,8 +4143,8 @@ packages: resolution: {integrity: sha512-rbnMnSdEwq2yuYMgzOQ4jTXm+oH7yjN/0ISfB/7O6pUcEPsZt9UW60BYfQ1WWHkKa/evI8vgER2zV5/RC1BupQ==} engines: {node: '>=18.10'} - '@n8n/tournament@1.0.2': - resolution: {integrity: sha512-fTpi7F8ra5flGSVfRzohPyG7czAAKCZPlLjdKdwbLJivLoI/Ekhgodov1jfVSCVFVbwQ06gRQRxLEDzl2jl8ig==} + '@n8n/tournament@1.0.3': + resolution: {integrity: sha512-GnmDD5wKAxKfxnSzhENHPn5n91/1c3/psnuT7D+jHHVQdMe8qaCcSq15rcGRfDfTf2v+BZBT0yeyK8Cfexr9yw==} engines: {node: '>=18.10', pnpm: '>=8.6'} '@n8n/typeorm@0.3.20-10': @@ -4124,9 +4211,9 @@ packages: typeorm-aurora-data-api-driver: optional: true - '@n8n/vm2@3.9.20': - resolution: {integrity: sha512-qk2oJYkuFRVSTxoro4obX/sv/wT1pViZjHh/isjOvFB93D52QIg3TCjMPsHOfHTmkxCKJffjLrUvjIwvWzSMCQ==} - engines: {node: '>=18.10', pnpm: '>=8.6.12'} + '@n8n/vm2@3.9.24': + resolution: {integrity: sha512-O4z67yVgUs2FHkcw3vbGnxdC1EglpzOj966kPkK4gtW+ZmTTFRfEB+2Ehq6PMthgg/Ou5JCLSR3wvQIZFFt4Pg==} + engines: {node: '>=18.10', pnpm: '>=9.6'} hasBin: true '@n8n_io/license-sdk@2.13.0': @@ -5912,10 +5999,6 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.2.0: - resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} - engines: {node: '>=0.4.0'} - acorn-walk@8.3.2: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} @@ -6148,9 +6231,6 @@ packages: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} - assert@2.0.0: - resolution: {integrity: sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==} - assert@2.1.0: resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} @@ -7474,9 +7554,6 @@ packages: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} - es6-object-assign@1.1.0: - resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==} - es6-promise@3.3.1: resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} @@ -8828,10 +8905,6 @@ packages: isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} - istanbul-lib-coverage@3.2.0: - resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} - engines: {node: '>=8'} - istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -8840,10 +8913,6 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} - istanbul-lib-report@3.0.0: - resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} - engines: {node: '>=8'} - istanbul-lib-report@3.0.1: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} @@ -8856,10 +8925,6 @@ packages: resolution: {integrity: sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==} engines: {node: '>=10'} - istanbul-reports@3.1.5: - resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==} - engines: {node: '>=8'} - istanbul-reports@3.1.6: resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} engines: {node: '>=8'} @@ -9696,10 +9761,6 @@ packages: lunr@2.3.9: resolution: {integrity: sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==} - luxon@3.3.0: - resolution: {integrity: sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==} - engines: {node: '>=12'} - luxon@3.4.4: resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} engines: {node: '>=12'} @@ -10628,9 +10689,6 @@ packages: resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==} engines: {node: '>=12'} - pathe@1.1.1: - resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} - pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -13247,7 +13305,7 @@ snapshots: '@acuminous/bitsyntax@0.1.2': dependencies: buffer-more-ints: 1.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) safe-buffer: 5.1.2 transitivePeerDependencies: - supports-color @@ -14360,10 +14418,10 @@ snapshots: '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.0) '@babel/helpers': 7.24.0 - '@babel/parser': 7.24.0 + '@babel/parser': 7.24.6 '@babel/template': 7.24.0 '@babel/traverse': 7.24.0 - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 convert-source-map: 2.0.0 debug: 4.3.5(supports-color@8.1.1) gensync: 1.0.0-beta.2 @@ -14394,14 +14452,14 @@ snapshots: '@babel/generator@7.22.9': dependencies: - '@babel/types': 7.24.0 - '@jridgewell/gen-mapping': 0.3.2 - '@jridgewell/trace-mapping': 0.3.18 + '@babel/types': 7.24.6 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 '@babel/generator@7.23.6': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 @@ -14415,7 +14473,7 @@ snapshots: '@babel/helper-annotate-as-pure@7.22.5': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@babel/helper-annotate-as-pure@7.24.6': dependencies: @@ -14510,7 +14568,7 @@ snapshots: '@babel/helper-function-name@7.23.0': dependencies: '@babel/template': 7.24.0 - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@babel/helper-function-name@7.24.6': dependencies: @@ -14519,7 +14577,7 @@ snapshots: '@babel/helper-hoist-variables@7.22.5': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@babel/helper-hoist-variables@7.24.6': dependencies: @@ -14527,7 +14585,7 @@ snapshots: '@babel/helper-member-expression-to-functions@7.23.0': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@babel/helper-member-expression-to-functions@7.24.6': dependencies: @@ -14535,7 +14593,7 @@ snapshots: '@babel/helper-module-imports@7.22.15': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@babel/helper-module-imports@7.24.6': dependencies: @@ -14570,7 +14628,7 @@ snapshots: '@babel/helper-optimise-call-expression@7.22.5': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@babel/helper-optimise-call-expression@7.24.6': dependencies: @@ -14605,7 +14663,7 @@ snapshots: '@babel/helper-simple-access@7.22.5': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@babel/helper-simple-access@7.24.6': dependencies: @@ -14613,7 +14671,7 @@ snapshots: '@babel/helper-skip-transparent-expression-wrappers@7.22.5': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@babel/helper-skip-transparent-expression-wrappers@7.24.6': dependencies: @@ -14621,7 +14679,7 @@ snapshots: '@babel/helper-split-export-declaration@7.22.6': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@babel/helper-split-export-declaration@7.24.6': dependencies: @@ -14649,7 +14707,7 @@ snapshots: dependencies: '@babel/template': 7.24.0 '@babel/traverse': 7.24.0 - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 transitivePeerDependencies: - supports-color @@ -15324,7 +15382,7 @@ snapshots: dependencies: '@babel/core': 7.24.6 '@babel/helper-plugin-utils': 7.24.6 - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 esutils: 2.0.3 '@babel/preset-typescript@7.23.3(@babel/core@7.24.6)': @@ -15362,8 +15420,8 @@ snapshots: '@babel/template@7.24.0': dependencies: '@babel/code-frame': 7.24.6 - '@babel/parser': 7.24.0 - '@babel/types': 7.24.0 + '@babel/parser': 7.24.6 + '@babel/types': 7.24.6 '@babel/template@7.24.6': dependencies: @@ -15379,8 +15437,8 @@ snapshots: '@babel/helper-function-name': 7.23.0 '@babel/helper-hoist-variables': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.24.0 - '@babel/types': 7.24.0 + '@babel/parser': 7.24.6 + '@babel/types': 7.24.6 debug: 4.3.5(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: @@ -15743,8 +15801,8 @@ snapshots: url-join: 4.0.1 zod: 3.23.8 optionalDependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) - langchain: 0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1) + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) + langchain: 0.2.11(3eonfu4fvxvjb5zdrprnb3pokq) transitivePeerDependencies: - encoding @@ -15777,6 +15835,13 @@ snapshots: - encoding - supports-color + '@google-cloud/secret-manager@5.6.0(encoding@0.1.13)': + dependencies: + google-gax: 4.3.4(encoding@0.1.13) + transitivePeerDependencies: + - encoding + - supports-color + '@google-cloud/storage@6.11.0(encoding@0.1.13)': dependencies: '@google-cloud/paginator': 3.0.7 @@ -15809,13 +15874,6 @@ snapshots: '@grpc/proto-loader': 0.7.13 '@js-sdsl/ordered-map': 4.4.2 - '@grpc/proto-loader@0.7.10': - dependencies: - lodash.camelcase: 4.3.0 - long: 5.2.3 - protobufjs: 7.3.0 - yargs: 17.7.2 - '@grpc/proto-loader@0.7.13': dependencies: lodash.camelcase: 4.3.0 @@ -16008,18 +16066,18 @@ snapshots: '@jest/test-result': 29.6.2 '@jest/transform': 29.6.2 '@jest/types': 29.6.1 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 '@types/node': 18.16.16 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 glob: 7.2.3 graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.0 + istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 5.2.1 - istanbul-lib-report: 3.0.0 + istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.5 + istanbul-reports: 3.1.6 jest-message-util: 29.6.2 jest-util: 29.6.2 jest-worker: 29.6.2 @@ -16044,7 +16102,7 @@ snapshots: '@jest/source-map@29.6.0': dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 callsites: 3.1.0 graceful-fs: 4.2.11 @@ -16066,7 +16124,7 @@ snapshots: dependencies: '@babel/core': 7.24.0 '@jest/types': 29.6.1 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -16095,7 +16153,7 @@ snapshots: dependencies: '@jridgewell/set-array': 1.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/gen-mapping@0.3.5': dependencies: @@ -16115,15 +16173,8 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 optional: true - '@jridgewell/sourcemap-codec@1.4.14': {} - '@jridgewell/sourcemap-codec@1.4.15': {} - '@jridgewell/trace-mapping@0.3.18': - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 - '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.0 @@ -16169,7 +16220,7 @@ snapshots: '@kwsites/file-exists@1.1.1': dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -16178,7 +16229,7 @@ snapshots: '@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))': dependencies: '@anthropic-ai/sdk': 0.22.0(encoding@0.1.13) - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) fast-xml-parser: 4.3.5 zod: 3.23.8 zod-to-json-schema: 3.23.0(zod@3.23.8) @@ -16190,23 +16241,23 @@ snapshots: '@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))': dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) cohere-ai: 7.10.1(encoding@0.1.13) transitivePeerDependencies: - encoding - langchain - openai - ? '@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1)' - : dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) - '@langchain/openai': 0.2.5(encoding@0.1.13)(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1)) + '@langchain/community@0.2.20(bl2awxa6z6ereahlcpz2dlbf6q)': + dependencies: + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) + '@langchain/openai': 0.2.5(encoding@0.1.13)(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq)) binary-extensions: 2.2.0 expr-eval: 2.0.2 flat: 5.0.2 js-yaml: 4.1.0 - langchain: 0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1) - langsmith: 0.1.34(@langchain/core@0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)))(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + langchain: 0.2.11(3eonfu4fvxvjb5zdrprnb3pokq) + langsmith: 0.1.34(@langchain/core@0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)))(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) uuid: 10.0.0 zod: 3.23.8 zod-to-json-schema: 3.23.0(zod@3.23.8) @@ -16267,13 +16318,13 @@ snapshots: - pyodide - supports-color - ? '@langchain/core@0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13))' - : dependencies: + '@langchain/core@0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13))': + dependencies: ansi-styles: 5.2.0 camelcase: 6.3.0 decamelize: 1.2.0 js-tiktoken: 1.0.12 - langsmith: 0.1.39(@langchain/core@0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)))(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + langsmith: 0.1.39(@langchain/core@0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)))(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) ml-distance: 4.0.1 mustache: 4.2.0 p-queue: 6.6.2 @@ -16285,13 +16336,13 @@ snapshots: - langchain - openai - '@langchain/core@0.2.18(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0)': + '@langchain/core@0.2.18(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0)': dependencies: ansi-styles: 5.2.0 camelcase: 6.3.0 decamelize: 1.2.0 js-tiktoken: 1.0.12 - langsmith: 0.1.39(@langchain/core@0.2.18(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0))(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0) + langsmith: 0.1.39(@langchain/core@0.2.18(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0))(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0) ml-distance: 4.0.1 mustache: 4.2.0 p-queue: 6.6.2 @@ -16303,9 +16354,9 @@ snapshots: - langchain - openai - ? '@langchain/google-common@0.0.22(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13))(zod@3.23.8)' - : dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + '@langchain/google-common@0.0.22(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13))(zod@3.23.8)': + dependencies: + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) uuid: 10.0.0 zod-to-json-schema: 3.23.0(zod@3.23.8) transitivePeerDependencies: @@ -16313,10 +16364,10 @@ snapshots: - openai - zod - ? '@langchain/google-gauth@0.0.21(encoding@0.1.13)(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13))(zod@3.23.8)' - : dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) - '@langchain/google-common': 0.0.22(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13))(zod@3.23.8) + '@langchain/google-gauth@0.0.21(encoding@0.1.13)(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13))(zod@3.23.8)': + dependencies: + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) + '@langchain/google-common': 0.0.22(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13))(zod@3.23.8) google-auth-library: 8.9.0(encoding@0.1.13) transitivePeerDependencies: - encoding @@ -16328,7 +16379,7 @@ snapshots: '@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8)': dependencies: '@google/generative-ai': 0.7.1 - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) zod-to-json-schema: 3.23.0(zod@3.23.8) transitivePeerDependencies: - langchain @@ -16337,8 +16388,8 @@ snapshots: '@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8)': dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) - '@langchain/google-gauth': 0.0.21(encoding@0.1.13)(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13))(zod@3.23.8) + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) + '@langchain/google-gauth': 0.0.21(encoding@0.1.13)(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13))(zod@3.23.8) transitivePeerDependencies: - encoding - langchain @@ -16348,8 +16399,8 @@ snapshots: '@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))': dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) - '@langchain/openai': 0.2.5(encoding@0.1.13)(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1)) + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) + '@langchain/openai': 0.2.5(encoding@0.1.13)(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq)) groq-sdk: 0.3.2(encoding@0.1.13) zod: 3.23.8 zod-to-json-schema: 3.23.0(zod@3.23.8) @@ -16361,7 +16412,7 @@ snapshots: '@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))': dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) '@mistralai/mistralai': 0.4.0(encoding@0.1.13) uuid: 10.0.0 zod: 3.23.8 @@ -16373,16 +16424,16 @@ snapshots: '@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))': dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) ollama: 0.5.6 uuid: 10.0.0 transitivePeerDependencies: - langchain - openai - ? '@langchain/openai@0.2.5(encoding@0.1.13)(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))' - : dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + '@langchain/openai@0.2.5(encoding@0.1.13)(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))': + dependencies: + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) js-tiktoken: 1.0.12 openai: 4.53.0(encoding@0.1.13) zod: 3.23.8 @@ -16392,9 +16443,9 @@ snapshots: - langchain - supports-color - '@langchain/openai@0.2.5(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))': + '@langchain/openai@0.2.5(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))': dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0) + '@langchain/core': 0.2.18(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0) js-tiktoken: 1.0.12 openai: 4.53.0(encoding@0.1.13) zod: 3.23.8 @@ -16405,9 +16456,9 @@ snapshots: - supports-color optional: true - ? '@langchain/pinecone@0.0.8(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13))' - : dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + '@langchain/pinecone@0.0.8(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13))': + dependencies: + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) '@pinecone-database/pinecone': 3.0.0 flat: 5.0.2 uuid: 10.0.0 @@ -16415,9 +16466,9 @@ snapshots: - langchain - openai - ? '@langchain/qdrant@0.0.5(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13))(typescript@5.5.2)' - : dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + '@langchain/qdrant@0.0.5(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13))(typescript@5.5.2)': + dependencies: + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) '@qdrant/js-client-rest': 1.9.0(typescript@5.5.2) uuid: 9.0.1 transitivePeerDependencies: @@ -16425,25 +16476,25 @@ snapshots: - openai - typescript - ? '@langchain/redis@0.0.5(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13))' - : dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + '@langchain/redis@0.0.5(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13))': + dependencies: + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) redis: 4.6.13 transitivePeerDependencies: - langchain - openai - ? '@langchain/textsplitters@0.0.3(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13))' - : dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + '@langchain/textsplitters@0.0.3(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13))': + dependencies: + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) js-tiktoken: 1.0.12 transitivePeerDependencies: - langchain - openai - '@langchain/textsplitters@0.0.3(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0)': + '@langchain/textsplitters@0.0.3(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0)': dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0) + '@langchain/core': 0.2.18(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0) js-tiktoken: 1.0.12 transitivePeerDependencies: - langchain @@ -16592,7 +16643,7 @@ snapshots: '@types/retry': 0.12.5 retry: 0.13.1 - '@n8n/tournament@1.0.2': + '@n8n/tournament@1.0.3': dependencies: '@n8n_io/riot-tmpl': 4.0.1 ast-types: 0.16.1 @@ -16659,10 +16710,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@n8n/vm2@3.9.20': + '@n8n/vm2@3.9.24': dependencies: - acorn: 8.11.2 - acorn-walk: 8.2.0 + acorn: 8.12.1 + acorn-walk: 8.3.2 '@n8n_io/license-sdk@2.13.0': dependencies: @@ -17712,11 +17763,11 @@ snapshots: dependencies: '@storybook/global': 5.0.0 - '@storybook/addon-interactions@8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1))': + '@storybook/addon-interactions@8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1))': dependencies: '@storybook/global': 5.0.0 '@storybook/instrumenter': 8.1.4 - '@storybook/test': 8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) + '@storybook/test': 8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) '@storybook/types': 8.1.4 polished: 4.2.2 ts-dedent: 2.2.0 @@ -18164,14 +18215,14 @@ snapshots: - prettier - supports-color - '@storybook/test@8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1))': + '@storybook/test@8.1.4(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1))': dependencies: '@storybook/client-logger': 8.1.4 '@storybook/core-events': 8.1.4 '@storybook/instrumenter': 8.1.4 '@storybook/preview-api': 8.1.4 '@testing-library/dom': 9.3.4 - '@testing-library/jest-dom': 6.4.2(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1)) + '@testing-library/jest-dom': 6.4.2(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1)) '@testing-library/user-event': 14.5.2(@testing-library/dom@9.3.4) '@vitest/expect': 1.3.1 '@vitest/spy': 1.3.1 @@ -18334,7 +18385,7 @@ snapshots: jest: 29.6.2(@types/node@18.16.16) vitest: 1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1) - '@testing-library/jest-dom@6.4.2(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(jsdom@23.0.1)(sass@1.64.1)(terser@5.16.1))': + '@testing-library/jest-dom@6.4.2(@jest/globals@29.6.2)(@types/jest@29.5.3)(jest@29.6.2(@types/node@18.16.16))(vitest@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1))': dependencies: '@adobe/css-tools': 4.3.2 '@babel/runtime': 7.23.6 @@ -18393,24 +18444,24 @@ snapshots: '@types/babel__core@7.20.0': dependencies: - '@babel/parser': 7.24.0 - '@babel/types': 7.24.0 + '@babel/parser': 7.24.6 + '@babel/types': 7.24.6 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.18.2 '@types/babel__generator@7.6.4': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@types/babel__template@7.4.1': dependencies: - '@babel/parser': 7.24.0 - '@babel/types': 7.24.0 + '@babel/parser': 7.24.6 + '@babel/types': 7.24.6 '@types/babel__traverse@7.18.2': dependencies: - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@types/basic-auth@1.1.3': dependencies: @@ -18986,12 +19037,12 @@ snapshots: dependencies: '@ampproject/remapping': 2.2.1 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.4 istanbul-reports: 3.1.6 - magic-string: 0.30.8 + magic-string: 0.30.10 magicast: 0.3.3 picocolors: 1.0.0 std-env: 3.6.0 @@ -19017,12 +19068,12 @@ snapshots: dependencies: '@vitest/utils': 1.6.0 p-limit: 5.0.0 - pathe: 1.1.1 + pathe: 1.1.2 '@vitest/snapshot@1.6.0': dependencies: - magic-string: 0.30.8 - pathe: 1.1.1 + magic-string: 0.30.10 + pathe: 1.1.2 pretty-format: 29.7.0 '@vitest/spy@1.3.1': @@ -19122,15 +19173,15 @@ snapshots: '@vue/compiler-sfc@3.4.21': dependencies: - '@babel/parser': 7.24.0 + '@babel/parser': 7.24.6 '@vue/compiler-core': 3.4.21 '@vue/compiler-dom': 3.4.21 '@vue/compiler-ssr': 3.4.21 '@vue/shared': 3.4.21 estree-walker: 2.0.2 - magic-string: 0.30.8 + magic-string: 0.30.10 postcss: 8.4.38 - source-map-js: 1.0.2 + source-map-js: 1.2.0 '@vue/compiler-ssr@3.4.21': dependencies: @@ -19182,7 +19233,7 @@ snapshots: '@vue/compiler-dom': 3.4.21 '@vue/shared': 3.4.21 computeds: 0.0.1 - minimatch: 9.0.4 + minimatch: 9.0.5 path-browserify: 1.0.1 vue-template-compiler: 2.7.14 optionalDependencies: @@ -19306,15 +19357,13 @@ snapshots: acorn-globals@7.0.1: dependencies: - acorn: 8.11.2 + acorn: 8.12.1 acorn-walk: 8.3.2 acorn-jsx@5.3.2(acorn@8.11.2): dependencies: acorn: 8.11.2 - acorn-walk@8.2.0: {} - acorn-walk@8.3.2: {} acorn@7.4.1: {} @@ -19329,7 +19378,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -19557,13 +19606,6 @@ snapshots: assert-plus@1.0.0: {} - assert@2.0.0: - dependencies: - es6-object-assign: 1.1.0 - is-nan: 1.3.2 - object-is: 1.1.5 - util: 0.12.5 - assert@2.1.0: dependencies: call-bind: 1.0.7 @@ -19675,7 +19717,7 @@ snapshots: babel-plugin-jest-hoist@29.5.0: dependencies: '@babel/template': 7.24.0 - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@types/babel__core': 7.20.0 '@types/babel__traverse': 7.18.2 @@ -21153,8 +21195,6 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 - es6-object-assign@1.1.0: {} - es6-promise@3.3.1: {} esbuild-plugin-alias@0.2.1: {} @@ -22066,7 +22106,7 @@ snapshots: https-proxy-agent: 5.0.1 mri: 1.2.0 node-fetch-native: 1.0.1 - pathe: 1.1.1 + pathe: 1.1.2 tar: 6.2.1 transitivePeerDependencies: - supports-color @@ -22143,7 +22183,7 @@ snapshots: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.2.12 + fast-glob: 3.3.2 ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 @@ -22196,7 +22236,7 @@ snapshots: google-gax@4.3.4(encoding@0.1.13): dependencies: '@grpc/grpc-js': 1.10.8 - '@grpc/proto-loader': 0.7.10 + '@grpc/proto-loader': 0.7.13 '@types/long': 4.0.2 abort-controller: 3.0.0 duplexify: 4.1.2 @@ -22414,7 +22454,7 @@ snapshots: http-proxy-agent@7.0.0: dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -22429,14 +22469,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -22464,7 +22504,7 @@ snapshots: ics@2.40.0: dependencies: - nanoid: 3.3.6 + nanoid: 3.3.7 yup: 0.32.11 ieee754@1.2.1: {} @@ -22788,26 +22828,18 @@ snapshots: isstream@0.1.2: {} - istanbul-lib-coverage@3.2.0: {} - istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.24.0 - '@babel/parser': 7.24.0 + '@babel/parser': 7.24.6 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.6.0 transitivePeerDependencies: - supports-color - istanbul-lib-report@3.0.0: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 3.1.0 - supports-color: 7.2.0 - istanbul-lib-report@3.0.1: dependencies: istanbul-lib-coverage: 3.2.2 @@ -22825,16 +22857,11 @@ snapshots: istanbul-lib-source-maps@5.0.4: dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color - istanbul-reports@3.1.5: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - istanbul-reports@3.1.6: dependencies: html-escaper: 2.0.2 @@ -22925,7 +22952,7 @@ snapshots: jest-validate: 29.6.2 micromatch: 4.0.5 parse-json: 5.2.0 - pretty-format: 29.6.2 + pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: @@ -23014,7 +23041,7 @@ snapshots: chalk: 4.1.2 jest-diff: 29.5.0 jest-get-type: 29.4.3 - pretty-format: 29.6.2 + pretty-format: 29.7.0 jest-matcher-utils@29.6.2: dependencies: @@ -23031,7 +23058,7 @@ snapshots: chalk: 4.1.2 graceful-fs: 4.2.11 micromatch: 4.0.5 - pretty-format: 29.6.2 + pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 @@ -23043,7 +23070,7 @@ snapshots: chalk: 4.1.2 graceful-fs: 4.2.11 micromatch: 4.0.5 - pretty-format: 29.6.2 + pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 @@ -23143,7 +23170,7 @@ snapshots: '@babel/generator': 7.22.9 '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.24.0) '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.24.0) - '@babel/types': 7.24.0 + '@babel/types': 7.24.6 '@jest/expect-utils': 29.6.2 '@jest/transform': 29.6.2 '@jest/types': 29.6.1 @@ -23157,7 +23184,7 @@ snapshots: jest-message-util: 29.6.2 jest-util: 29.6.2 natural-compare: 1.4.0 - pretty-format: 29.6.2 + pretty-format: 29.7.0 semver: 7.6.0 transitivePeerDependencies: - supports-color @@ -23187,7 +23214,7 @@ snapshots: chalk: 4.1.2 jest-get-type: 29.4.3 leven: 3.1.0 - pretty-format: 29.6.2 + pretty-format: 29.7.0 jest-watcher@29.6.2: dependencies: @@ -23278,7 +23305,7 @@ snapshots: jscodeshift@0.15.2(@babel/preset-env@7.24.6(@babel/core@7.24.6)): dependencies: '@babel/core': 7.24.6 - '@babel/parser': 7.24.0 + '@babel/parser': 7.24.6 '@babel/plugin-transform-class-properties': 7.23.3(@babel/core@7.24.6) '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.24.6) '@babel/plugin-transform-nullish-coalescing-operator': 7.23.4(@babel/core@7.24.6) @@ -23305,7 +23332,7 @@ snapshots: jsdom@20.0.2: dependencies: abab: 2.0.6 - acorn: 8.11.2 + acorn: 8.12.1 acorn-globals: 7.0.1 cssom: 0.5.0 cssstyle: 2.3.0 @@ -23489,17 +23516,17 @@ snapshots: kuler@2.0.0: {} - ? langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1) - : dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) - '@langchain/openai': 0.2.5(encoding@0.1.13)(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1)) - '@langchain/textsplitters': 0.0.3(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq): + dependencies: + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) + '@langchain/openai': 0.2.5(encoding@0.1.13)(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq)) + '@langchain/textsplitters': 0.0.3(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) binary-extensions: 2.2.0 js-tiktoken: 1.0.12 js-yaml: 4.1.0 jsonpointer: 5.0.1 langchainhub: 0.0.8 - langsmith: 0.1.34(@langchain/core@0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)))(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) + langsmith: 0.1.34(@langchain/core@0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)))(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) ml-distance: 4.0.1 openapi-types: 12.1.3 p-retry: 4.6.2 @@ -23512,7 +23539,7 @@ snapshots: '@aws-sdk/credential-provider-node': 3.535.0 '@langchain/anthropic': 0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)) '@langchain/cohere': 0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)) - '@langchain/community': 0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1) + '@langchain/community': 0.2.20(bl2awxa6z6ereahlcpz2dlbf6q) '@langchain/google-genai': 0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8) '@langchain/google-vertexai': 0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8) '@langchain/groq': 0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)) @@ -23540,17 +23567,17 @@ snapshots: - openai - supports-color - langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0): + langchain@0.2.11(axios@1.6.7)(openai@4.53.0): dependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0) - '@langchain/openai': 0.2.5(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0)) - '@langchain/textsplitters': 0.0.3(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0) + '@langchain/core': 0.2.18(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0) + '@langchain/openai': 0.2.5(langchain@0.2.11(axios@1.6.7)(openai@4.53.0)) + '@langchain/textsplitters': 0.0.3(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0) binary-extensions: 2.2.0 js-tiktoken: 1.0.12 js-yaml: 4.1.0 jsonpointer: 5.0.1 langchainhub: 0.0.8 - langsmith: 0.1.34(@langchain/core@0.2.18(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0))(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0) + langsmith: 0.1.34(@langchain/core@0.2.18(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0))(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0) ml-distance: 4.0.1 openapi-types: 12.1.3 p-retry: 4.6.2 @@ -23560,7 +23587,6 @@ snapshots: zod-to-json-schema: 3.23.0(zod@3.23.8) optionalDependencies: axios: 1.6.7(debug@3.2.7) - jsdom: 23.0.1 transitivePeerDependencies: - encoding - openai @@ -23569,8 +23595,8 @@ snapshots: langchainhub@0.0.8: {} - ? langsmith@0.1.34(@langchain/core@0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)))(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) - : dependencies: + langsmith@0.1.34(@langchain/core@0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)))(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)): + dependencies: '@types/uuid': 9.0.7 commander: 10.0.1 lodash.set: 4.3.2 @@ -23578,11 +23604,11 @@ snapshots: p-retry: 4.6.2 uuid: 9.0.1 optionalDependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) - langchain: 0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1) + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) + langchain: 0.2.11(3eonfu4fvxvjb5zdrprnb3pokq) openai: 4.53.0(encoding@0.1.13) - langsmith@0.1.34(@langchain/core@0.2.18(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0))(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0): + langsmith@0.1.34(@langchain/core@0.2.18(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0))(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0): dependencies: '@types/uuid': 9.0.7 commander: 10.0.1 @@ -23591,24 +23617,24 @@ snapshots: p-retry: 4.6.2 uuid: 9.0.1 optionalDependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0) - langchain: 0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0) + '@langchain/core': 0.2.18(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0) + langchain: 0.2.11(axios@1.6.7)(openai@4.53.0) openai: 4.53.0(encoding@0.1.13) optional: true - ? langsmith@0.1.39(@langchain/core@0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)))(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) - : dependencies: + langsmith@0.1.39(@langchain/core@0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)))(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)): + dependencies: '@types/uuid': 9.0.7 commander: 10.0.1 p-queue: 6.6.2 p-retry: 4.6.2 uuid: 9.0.1 optionalDependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1))(openai@4.53.0(encoding@0.1.13)) - langchain: 0.2.11(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@langchain/anthropic@0.2.9(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/cohere@0.0.10(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/community@0.2.20(@aws-sdk/client-bedrock-runtime@3.535.0)(@aws-sdk/client-s3@3.478.0)(@aws-sdk/credential-provider-node@3.535.0)(@getzep/zep-cloud@1.0.11)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@2.5.0(encoding@0.1.13))(@google-cloud/storage@6.11.0(encoding@0.1.13))(@huggingface/inference@2.7.0)(@langchain/anthropic@0.2.9)(@langchain/cohere@0.0.10)(@langchain/google-genai@0.0.23)(@langchain/google-vertexai@0.0.21)(@langchain/groq@0.0.15)(@langchain/mistralai@0.0.27)(@langchain/ollama@0.0.2)(@mozilla/readability@0.5.0)(@pinecone-database/pinecone@3.0.0)(@qdrant/js-client-rest@1.9.0(typescript@5.5.2))(@smithy/eventstream-codec@2.2.0)(@smithy/protocol-http@3.3.0)(@smithy/signature-v4@2.2.1)(@smithy/util-utf8@2.3.0)(@supabase/postgrest-js@1.15.2)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(cohere-ai@7.10.1(encoding@0.1.13))(crypto-js@4.2.0)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(google-auth-library@9.10.0(encoding@0.1.13))(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(jsonwebtoken@9.0.2)(lodash@4.17.21)(mammoth@1.7.2)(mysql2@3.10.0)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(ws@8.17.1))(@langchain/google-genai@0.0.23(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/google-vertexai@0.0.21(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13))(zod@3.23.8))(@langchain/groq@0.0.15(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/mistralai@0.0.27(encoding@0.1.13)(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@langchain/ollama@0.0.2(langchain@0.2.11)(openai@4.53.0(encoding@0.1.13)))(@pinecone-database/pinecone@3.0.0)(@supabase/supabase-js@2.43.4)(@xata.io/client@0.28.4(typescript@5.5.2))(axios@1.6.7)(cheerio@1.0.0-rc.12)(d3-dsv@2.0.0)(encoding@0.1.13)(epub2@3.0.2(ts-toolbelt@9.6.0))(fast-xml-parser@4.3.5)(handlebars@4.7.8)(html-to-text@9.0.5)(ignore@5.2.4)(ioredis@5.3.2)(jsdom@23.0.1)(mammoth@1.7.2)(openai@4.53.0(encoding@0.1.13))(pdf-parse@1.1.1)(redis@4.6.12)(ws@8.17.1) + '@langchain/core': 0.2.18(langchain@0.2.11(3eonfu4fvxvjb5zdrprnb3pokq))(openai@4.53.0(encoding@0.1.13)) + langchain: 0.2.11(3eonfu4fvxvjb5zdrprnb3pokq) openai: 4.53.0(encoding@0.1.13) - langsmith@0.1.39(@langchain/core@0.2.18(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0))(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0): + langsmith@0.1.39(@langchain/core@0.2.18(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0))(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0): dependencies: '@types/uuid': 9.0.7 commander: 10.0.1 @@ -23616,8 +23642,8 @@ snapshots: p-retry: 4.6.2 uuid: 9.0.1 optionalDependencies: - '@langchain/core': 0.2.18(langchain@0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0))(openai@4.53.0) - langchain: 0.2.11(axios@1.6.7)(jsdom@23.0.1)(openai@4.53.0) + '@langchain/core': 0.2.18(langchain@0.2.11(axios@1.6.7)(openai@4.53.0))(openai@4.53.0) + langchain: 0.2.11(axios@1.6.7)(openai@4.53.0) openai: 4.53.0(encoding@0.1.13) lazy-ass@1.6.0: {} @@ -23852,8 +23878,6 @@ snapshots: lunr@2.3.9: {} - luxon@3.3.0: {} - luxon@3.4.4: {} lz-string@1.5.0: {} @@ -24041,7 +24065,7 @@ snapshots: minifaker@1.34.1: dependencies: '@types/uuid': 8.3.4 - nanoid: 3.3.6 + nanoid: 3.3.7 uuid: 8.3.2 minimalistic-assert@1.0.1: {} @@ -24164,8 +24188,8 @@ snapshots: mlly@1.4.2: dependencies: - acorn: 8.11.2 - pathe: 1.1.1 + acorn: 8.12.1 + pathe: 1.1.2 pkg-types: 1.0.3 ufo: 1.3.2 @@ -24216,7 +24240,7 @@ snapshots: mqtt-packet@9.0.0: dependencies: bl: 6.0.12 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) process-nextick-args: 2.0.1 transitivePeerDependencies: - supports-color @@ -24510,7 +24534,7 @@ snapshots: number-allocator@1.0.14: dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) js-sdsl: 4.3.0 transitivePeerDependencies: - supports-color @@ -24861,8 +24885,6 @@ snapshots: path-type@5.0.0: {} - pathe@1.1.1: {} - pathe@1.1.2: {} pathval@1.1.1: {} @@ -25011,7 +25033,7 @@ snapshots: dependencies: jsonc-parser: 3.2.0 mlly: 1.4.2 - pathe: 1.1.1 + pathe: 1.1.2 pkg-types@1.1.3: dependencies: @@ -25064,9 +25086,9 @@ snapshots: postcss@8.4.31: dependencies: - nanoid: 3.3.6 + nanoid: 3.3.7 picocolors: 1.0.0 - source-map-js: 1.0.2 + source-map-js: 1.2.0 postcss@8.4.38: dependencies: @@ -25542,7 +25564,7 @@ snapshots: recast@0.22.0: dependencies: - assert: 2.0.0 + assert: 2.1.0 ast-types: 0.15.2 esprima: 4.0.1 source-map: 0.6.1 @@ -26105,7 +26127,7 @@ snapshots: simple-websocket@9.1.0: dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) queue-microtask: 1.2.3 randombytes: 2.1.0 readable-stream: 3.6.0 @@ -26510,7 +26532,7 @@ snapshots: dependencies: component-emitter: 1.3.0 cookiejar: 2.1.4 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) fast-safe-stringify: 2.1.1 form-data: 4.0.0 formidable: 3.5.1 @@ -27147,7 +27169,7 @@ snapshots: unplugin@1.0.1: dependencies: - acorn: 8.11.2 + acorn: 8.12.1 chokidar: 3.5.2 webpack-sources: 3.2.3 webpack-virtual-modules: 0.5.0 @@ -27161,7 +27183,7 @@ snapshots: unplugin@1.5.1: dependencies: - acorn: 8.11.2 + acorn: 8.12.1 chokidar: 3.5.2 webpack-sources: 3.2.3 webpack-virtual-modules: 0.6.1 @@ -27244,7 +27266,7 @@ snapshots: v8-to-istanbul@9.1.0: dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0 @@ -27266,8 +27288,8 @@ snapshots: vite-node@1.6.0(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1): dependencies: cac: 6.7.14 - debug: 4.3.4(supports-color@8.1.1) - pathe: 1.1.1 + debug: 4.3.5(supports-color@8.1.1) + pathe: 1.1.2 picocolors: 1.0.0 vite: 5.2.12(@types/node@18.16.16)(sass@1.64.1)(terser@5.16.1) transitivePeerDependencies: @@ -27323,11 +27345,11 @@ snapshots: '@vitest/utils': 1.6.0 acorn-walk: 8.3.2 chai: 4.3.10 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.5(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.0 - magic-string: 0.30.8 - pathe: 1.1.1 + magic-string: 0.30.10 + pathe: 1.1.2 picocolors: 1.0.0 std-env: 3.6.0 strip-literal: 2.0.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index add43b6c008b6..2ce2d3d7dfeec 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,3 +3,29 @@ packages: - packages/@n8n/* - packages/@n8n_io/* - cypress + +catalog: + '@types/basic-auth': ^1.1.3 + '@types/express': ^4.17.21 + '@types/lodash': ^4.14.195 + '@types/uuid': ^8.3.2 + '@types/xml2js': ^0.4.14 + axios: 1.6.7 + basic-auth: 2.0.1 + fast-glob: 3.2.12 + form-data: 4.0.0 + lodash: 4.17.21 + luxon: 3.4.4 + nanoid: 3.3.6 + typedi: 0.10.0 + uuid: 8.3.2 + xml2js: 0.6.2 + +catalogs: + frontend: + '@vitest/coverage-v8': ^1.6.0 + vite: ^5.2.12 + vitest: ^1.6.0 + vitest-mock-extended: ^1.3.1 + vue: ^3.4.21 + vue-tsc: ^2.0.19