mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 03:12:15 +00:00
refactor(editor): Extract API Requests into @n8n/rest-api-client package (no-changelog) (#15930)
This commit is contained in:
@@ -1177,11 +1177,6 @@ export interface CommunityNodesState {
|
||||
installedPackages: CommunityPackageMap;
|
||||
}
|
||||
|
||||
export interface IRestApiContext {
|
||||
baseUrl: string;
|
||||
pushRef: string;
|
||||
}
|
||||
|
||||
export interface IZoomConfig {
|
||||
scale: number;
|
||||
offset: XYPosition;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useAIAssistantHelpers } from '@/composables/useAIAssistantHelpers';
|
||||
import { AI_ASSISTANT_MAX_CONTENT_LENGTH } from '@/constants';
|
||||
import type { ICredentialsResponse, IRestApiContext } from '@/Interface';
|
||||
import type { ICredentialsResponse } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import type { AskAiRequest, ChatRequest, ReplaceCodeRequest } from '@/types/assistant.types';
|
||||
import { makeRestApiRequest, streamRequest } from '@/utils/apiUtils';
|
||||
import { makeRestApiRequest, streamRequest } from '@n8n/rest-api-client';
|
||||
import { getObjectSizeInKB } from '@/utils/objectUtils';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type {
|
||||
CreateApiKeyRequestDto,
|
||||
UpdateApiKeyRequestDto,
|
||||
ApiKey,
|
||||
ApiKeyWithRawValue,
|
||||
} from '@n8n/api-types';
|
||||
import type { ApiKeyScope } from '@n8n/permissions';
|
||||
|
||||
export async function getApiKeys(context: IRestApiContext): Promise<ApiKey[]> {
|
||||
return await makeRestApiRequest(context, 'GET', '/api-keys');
|
||||
}
|
||||
|
||||
export async function getApiKeyScopes(context: IRestApiContext): Promise<ApiKeyScope[]> {
|
||||
return await makeRestApiRequest(context, 'GET', '/api-keys/scopes');
|
||||
}
|
||||
|
||||
export async function createApiKey(
|
||||
context: IRestApiContext,
|
||||
payload: CreateApiKeyRequestDto,
|
||||
): Promise<ApiKeyWithRawValue> {
|
||||
return await makeRestApiRequest(context, 'POST', '/api-keys', payload);
|
||||
}
|
||||
|
||||
export async function deleteApiKey(
|
||||
context: IRestApiContext,
|
||||
id: string,
|
||||
): Promise<{ success: boolean }> {
|
||||
return await makeRestApiRequest(context, 'DELETE', `/api-keys/${id}`);
|
||||
}
|
||||
|
||||
export async function updateApiKey(
|
||||
context: IRestApiContext,
|
||||
id: string,
|
||||
payload: UpdateApiKeyRequestDto,
|
||||
): Promise<{ success: boolean }> {
|
||||
return await makeRestApiRequest(context, 'PATCH', `/api-keys/${id}`, payload);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Cloud, IRestApiContext, InstanceUsage } from '@/Interface';
|
||||
import { get, post } from '@/utils/apiUtils';
|
||||
import type { Cloud, InstanceUsage } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import { get, post } from '@n8n/rest-api-client';
|
||||
|
||||
export async function getCurrentPlan(context: IRestApiContext): Promise<Cloud.PlanData> {
|
||||
return await get(context.baseUrl, '/admin/cloud-plan');
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import type { PublicInstalledPackage } from 'n8n-workflow';
|
||||
import { get, post, makeRestApiRequest } from '@/utils/apiUtils';
|
||||
|
||||
export async function getInstalledCommunityNodes(
|
||||
context: IRestApiContext,
|
||||
): Promise<PublicInstalledPackage[]> {
|
||||
const response = await get(context.baseUrl, '/community-packages');
|
||||
return response.data || [];
|
||||
}
|
||||
|
||||
export async function installNewPackage(
|
||||
context: IRestApiContext,
|
||||
name: string,
|
||||
verify?: boolean,
|
||||
version?: string,
|
||||
): Promise<PublicInstalledPackage> {
|
||||
return await post(context.baseUrl, '/community-packages', { name, verify, version });
|
||||
}
|
||||
|
||||
export async function uninstallPackage(context: IRestApiContext, name: string): Promise<void> {
|
||||
return await makeRestApiRequest(context, 'DELETE', '/community-packages', { name });
|
||||
}
|
||||
|
||||
export async function updatePackage(
|
||||
context: IRestApiContext,
|
||||
name: string,
|
||||
): Promise<PublicInstalledPackage> {
|
||||
return await makeRestApiRequest(context, 'PATCH', '/community-packages', { name });
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ICredentialsResponse, IRestApiContext, IShareCredentialsPayload } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { ICredentialsResponse, IShareCredentialsPayload } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
|
||||
export async function setCredentialSharedWith(
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import type {
|
||||
ICredentialsDecryptedResponse,
|
||||
ICredentialsResponse,
|
||||
IRestApiContext,
|
||||
} from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { ICredentialsDecryptedResponse, ICredentialsResponse } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import type {
|
||||
ICredentialsDecrypted,
|
||||
ICredentialType,
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { get } from '@/utils/apiUtils';
|
||||
|
||||
export async function getBecomeCreatorCta(context: IRestApiContext): Promise<boolean> {
|
||||
const response = await get(context.baseUrl, '/cta/become-creator');
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { EnvironmentVariable, IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { EnvironmentVariable } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
|
||||
export async function getVariables(context: IRestApiContext): Promise<EnvironmentVariable[]> {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest, request } from '@/utils/apiUtils';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import { makeRestApiRequest, request } from '@n8n/rest-api-client';
|
||||
|
||||
export interface TestRunRecord {
|
||||
id: string;
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { IDataObject, MessageEventBusDestinationOptions } from 'n8n-workflow';
|
||||
|
||||
export type ApiMessageEventBusDestinationOptions = MessageEventBusDestinationOptions & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export function hasDestinationId(
|
||||
destination: MessageEventBusDestinationOptions,
|
||||
): destination is ApiMessageEventBusDestinationOptions {
|
||||
return destination.id !== undefined;
|
||||
}
|
||||
|
||||
export async function saveDestinationToDb(
|
||||
context: IRestApiContext,
|
||||
destination: ApiMessageEventBusDestinationOptions,
|
||||
subscribedEvents: string[] = [],
|
||||
) {
|
||||
const data: IDataObject = {
|
||||
...destination,
|
||||
subscribedEvents,
|
||||
};
|
||||
return await makeRestApiRequest(context, 'POST', '/eventbus/destination', data);
|
||||
}
|
||||
|
||||
export async function deleteDestinationFromDb(context: IRestApiContext, destinationId: string) {
|
||||
return await makeRestApiRequest(context, 'DELETE', `/eventbus/destination?id=${destinationId}`);
|
||||
}
|
||||
|
||||
export async function sendTestMessageToDestination(
|
||||
context: IRestApiContext,
|
||||
destination: ApiMessageEventBusDestinationOptions,
|
||||
): Promise<boolean> {
|
||||
const data: IDataObject = {
|
||||
...destination,
|
||||
};
|
||||
return await makeRestApiRequest(context, 'GET', '/eventbus/testmessage', data);
|
||||
}
|
||||
|
||||
export async function getEventNamesFromBackend(context: IRestApiContext): Promise<string[]> {
|
||||
return await makeRestApiRequest(context, 'GET', '/eventbus/eventnames');
|
||||
}
|
||||
|
||||
export async function getDestinationsFromBackend(
|
||||
context: IRestApiContext,
|
||||
): Promise<MessageEventBusDestinationOptions[]> {
|
||||
return await makeRestApiRequest(context, 'GET', '/eventbus/destination');
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
|
||||
export async function sessionStarted(context: IRestApiContext): Promise<void> {
|
||||
return await makeRestApiRequest(context, 'GET', '/events/session-started');
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { IRestApiContext, ExternalSecretsProvider } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { ExternalSecretsProvider } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
|
||||
export const getExternalSecrets = async (
|
||||
context: IRestApiContext,
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import type {
|
||||
CurrentUserResponse,
|
||||
IInviteResponse,
|
||||
IRestApiContext,
|
||||
InvitableRoleName,
|
||||
} from '@/Interface';
|
||||
import type { CurrentUserResponse, IInviteResponse, InvitableRoleName } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
|
||||
type AcceptInvitationParams = {
|
||||
inviterId: string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ILdapConfig, ILdapSyncData, IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { ILdapConfig, ILdapSyncData } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
|
||||
export async function getLdapConfig(context: IRestApiContext): Promise<ILdapConfig> {
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
|
||||
export async function canEnableMFA(context: IRestApiContext) {
|
||||
return await makeRestApiRequest(context, 'POST', '/mfa/can-enable');
|
||||
}
|
||||
|
||||
export async function getMfaQR(
|
||||
context: IRestApiContext,
|
||||
): Promise<{ qrCode: string; secret: string; recoveryCodes: string[] }> {
|
||||
return await makeRestApiRequest(context, 'GET', '/mfa/qr');
|
||||
}
|
||||
|
||||
export async function enableMfa(
|
||||
context: IRestApiContext,
|
||||
data: { mfaCode: string },
|
||||
): Promise<void> {
|
||||
return await makeRestApiRequest(context, 'POST', '/mfa/enable', data);
|
||||
}
|
||||
|
||||
export async function verifyMfaCode(
|
||||
context: IRestApiContext,
|
||||
data: { mfaCode: string },
|
||||
): Promise<void> {
|
||||
return await makeRestApiRequest(context, 'POST', '/mfa/verify', data);
|
||||
}
|
||||
|
||||
export type DisableMfaParams = {
|
||||
mfaCode?: string;
|
||||
mfaRecoveryCode?: string;
|
||||
};
|
||||
|
||||
export async function disableMfa(context: IRestApiContext, data: DisableMfaParams): Promise<void> {
|
||||
return await makeRestApiRequest(context, 'POST', '/mfa/disable', data);
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
import type {
|
||||
ActionResultRequestDto,
|
||||
CommunityNodeType,
|
||||
OptionsRequestDto,
|
||||
ResourceLocatorRequestDto,
|
||||
ResourceMapperFieldsRequestDto,
|
||||
} from '@n8n/api-types';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import type { INodeTranslationHeaders } from '@n8n/i18n';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
type INodeListSearchResult,
|
||||
type INodePropertyOptions,
|
||||
type INodeTypeDescription,
|
||||
type INodeTypeNameVersion,
|
||||
type NodeParameterValueType,
|
||||
type ResourceMapperFields,
|
||||
sleep,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
async function fetchNodeTypesJsonWithRetry(url: string, retries = 5, delay = 500) {
|
||||
for (let attempt = 0; attempt < retries; attempt++) {
|
||||
const response = await axios.get(url, { withCredentials: true });
|
||||
|
||||
if (typeof response.data === 'object' && response.data !== null) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
await sleep(delay * attempt);
|
||||
}
|
||||
|
||||
throw new Error('Could not fetch node types');
|
||||
}
|
||||
|
||||
export async function getNodeTypes(baseUrl: string) {
|
||||
return await fetchNodeTypesJsonWithRetry(baseUrl + 'types/nodes.json');
|
||||
}
|
||||
|
||||
export async function fetchCommunityNodeTypes(
|
||||
context: IRestApiContext,
|
||||
): Promise<CommunityNodeType[]> {
|
||||
return await makeRestApiRequest(context, 'GET', '/community-node-types');
|
||||
}
|
||||
|
||||
export async function fetchCommunityNodeAttributes(
|
||||
context: IRestApiContext,
|
||||
type: string,
|
||||
): Promise<CommunityNodeType | null> {
|
||||
return await makeRestApiRequest(
|
||||
context,
|
||||
'GET',
|
||||
`/community-node-types/${encodeURIComponent(type)}`,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getNodeTranslationHeaders(
|
||||
context: IRestApiContext,
|
||||
): Promise<INodeTranslationHeaders | undefined> {
|
||||
return await makeRestApiRequest(context, 'GET', '/node-translation-headers');
|
||||
}
|
||||
|
||||
export async function getNodesInformation(
|
||||
context: IRestApiContext,
|
||||
nodeInfos: INodeTypeNameVersion[],
|
||||
): Promise<INodeTypeDescription[]> {
|
||||
return await makeRestApiRequest(context, 'POST', '/node-types', { nodeInfos });
|
||||
}
|
||||
|
||||
export async function getNodeParameterOptions(
|
||||
context: IRestApiContext,
|
||||
sendData: OptionsRequestDto,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
return await makeRestApiRequest(context, 'POST', '/dynamic-node-parameters/options', sendData);
|
||||
}
|
||||
|
||||
export async function getResourceLocatorResults(
|
||||
context: IRestApiContext,
|
||||
sendData: ResourceLocatorRequestDto,
|
||||
): Promise<INodeListSearchResult> {
|
||||
return await makeRestApiRequest(
|
||||
context,
|
||||
'POST',
|
||||
'/dynamic-node-parameters/resource-locator-results',
|
||||
sendData,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getResourceMapperFields(
|
||||
context: IRestApiContext,
|
||||
sendData: ResourceMapperFieldsRequestDto,
|
||||
): Promise<ResourceMapperFields> {
|
||||
return await makeRestApiRequest(
|
||||
context,
|
||||
'POST',
|
||||
'/dynamic-node-parameters/resource-mapper-fields',
|
||||
sendData,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getLocalResourceMapperFields(
|
||||
context: IRestApiContext,
|
||||
sendData: ResourceMapperFieldsRequestDto,
|
||||
): Promise<ResourceMapperFields> {
|
||||
return await makeRestApiRequest(
|
||||
context,
|
||||
'POST',
|
||||
'/dynamic-node-parameters/local-resource-mapper-fields',
|
||||
sendData,
|
||||
);
|
||||
}
|
||||
|
||||
export async function getNodeParameterActionResult(
|
||||
context: IRestApiContext,
|
||||
sendData: ActionResultRequestDto,
|
||||
): Promise<NodeParameterValueType> {
|
||||
return await makeRestApiRequest(
|
||||
context,
|
||||
'POST',
|
||||
'/dynamic-node-parameters/action-result',
|
||||
sendData,
|
||||
);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { NpsSurveyState } from 'n8n-workflow';
|
||||
|
||||
export async function updateNpsSurveyState(context: IRestApiContext, state: NpsSurveyState) {
|
||||
await makeRestApiRequest(context, 'PATCH', '/user-settings/nps-survey', state);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
|
||||
const GET_STATUS_ENDPOINT = '/orchestration/worker/status';
|
||||
|
||||
export const sendGetWorkerStatus = async (context: IRestApiContext): Promise<void> => {
|
||||
await makeRestApiRequest(context, 'POST', GET_STATUS_ENDPOINT);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import type { Project, ProjectListItem, ProjectsCount } from '@/types/projects.types';
|
||||
import type { CreateProjectDto, UpdateProjectDto } from '@n8n/api-types';
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { AllRolesMap } from '@n8n/permissions';
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
|
||||
export const getRoles = async (context: IRestApiContext): Promise<AllRolesMap> => {
|
||||
return await makeRestApiRequest(context, 'GET', '/roles');
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getSchemaPreview } from './schemaPreview';
|
||||
import * as apiUtils from '@/utils/apiUtils';
|
||||
import * as apiUtils from '@n8n/rest-api-client';
|
||||
|
||||
vi.mock('@/utils/apiUtils');
|
||||
vi.mock('@n8n/rest-api-client');
|
||||
|
||||
describe('API: schemaPreview', () => {
|
||||
describe('getSchemaPreview', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { request } from '@/utils/apiUtils';
|
||||
import { request } from '@n8n/rest-api-client';
|
||||
import type { JSONSchema7 } from 'json-schema';
|
||||
import type { NodeParameterValueType } from 'n8n-workflow';
|
||||
import { isEmpty } from '@/utils/typesUtils';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { IRestApiContext, IN8nPrompts, IN8nPromptResponse } from '../Interface';
|
||||
import { makeRestApiRequest, get, post } from '@/utils/apiUtils';
|
||||
import type { IN8nPrompts, IN8nPromptResponse } from '../Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import { makeRestApiRequest, get, post } from '@n8n/rest-api-client';
|
||||
import { N8N_IO_BASE_URL, NPM_COMMUNITY_NODE_SEARCH_API_URL } from '@/constants';
|
||||
import type { FrontendSettings } from '@n8n/api-types';
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ import type {
|
||||
PushWorkFolderRequestDto,
|
||||
SourceControlledFile,
|
||||
} from '@n8n/api-types';
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import type {
|
||||
SourceControlPreferences,
|
||||
SourceControlStatus,
|
||||
SshKeyTypes,
|
||||
} from '@/types/sourceControl.types';
|
||||
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import type { TupleToUnion } from '@/utils/typeHelpers';
|
||||
|
||||
const sourceControlApiRoot = '/source-control';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SamlPreferences, SamlToggleDto } from '@n8n/api-types';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { IRestApiContext, SamlPreferencesExtractedData } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import type { SamlPreferencesExtractedData } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
|
||||
export const initSSO = async (context: IRestApiContext, redirectUrl = ''): Promise<string> => {
|
||||
return await makeRestApiRequest(context, 'GET', `/sso/saml/initsso?redirect=${redirectUrl}`);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { IRestApiContext, ITag } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { ITag } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import type { CreateOrUpdateTagRequestDto, RetrieveTagQueryDto } from '@n8n/api-types';
|
||||
|
||||
type TagsApiEndpoint = '/tags' | '/annotation-tags';
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
IWorkflowTemplate,
|
||||
TemplateSearchFacet,
|
||||
} from '@/Interface';
|
||||
import { get } from '@/utils/apiUtils';
|
||||
import { get } from '@n8n/rest-api-client';
|
||||
|
||||
function stringifyArray(arr: string[]) {
|
||||
return arr.join(',');
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { BannerName } from '@n8n/api-types';
|
||||
|
||||
export async function dismissBannerPermanently(
|
||||
context: IRestApiContext,
|
||||
data: { bannerName: BannerName; dismissedBanners: string[] },
|
||||
): Promise<void> {
|
||||
return await makeRestApiRequest(context, 'POST', '/owner/dismiss-banner', {
|
||||
banner: data.bannerName,
|
||||
});
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { CommunityRegisteredRequestDto } from '@n8n/api-types';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { IRestApiContext, UsageState } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import type { UsageState } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
|
||||
export const getLicense = async (context: IRestApiContext): Promise<UsageState['data']> => {
|
||||
return await makeRestApiRequest(context, 'GET', '/license');
|
||||
|
||||
@@ -7,12 +7,12 @@ import type {
|
||||
import type {
|
||||
CurrentUserResponse,
|
||||
IPersonalizationLatestVersion,
|
||||
IRestApiContext,
|
||||
IUserResponse,
|
||||
InvitableRoleName,
|
||||
} from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import type { IDataObject, IUserSettings } from 'n8n-workflow';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
|
||||
export async function loginCurrentUser(
|
||||
context: IRestApiContext,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { IVersion } from '@/Interface';
|
||||
import { INSTANCE_ID_HEADER } from '@/constants';
|
||||
import { get } from '@/utils/apiUtils';
|
||||
import { get } from '@n8n/rest-api-client';
|
||||
|
||||
export async function getNextVersions(
|
||||
endpoint: string,
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import type { IHttpRequestMethods } from 'n8n-workflow';
|
||||
|
||||
type WebhookData = {
|
||||
workflowId: string;
|
||||
webhookPath: string;
|
||||
method: IHttpRequestMethods;
|
||||
node: string;
|
||||
};
|
||||
|
||||
export const findWebhook = async (
|
||||
context: IRestApiContext,
|
||||
data: { path: string; method: string },
|
||||
): Promise<WebhookData | null> => {
|
||||
return await makeRestApiRequest(context, 'POST', '/webhooks/find', data);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { IUser } from '@/Interface';
|
||||
import { post } from '@/utils/apiUtils';
|
||||
import { post } from '@n8n/rest-api-client';
|
||||
|
||||
const N8N_API_BASE_URL = 'https://api.n8n.io/api';
|
||||
const CONTACT_EMAIL_SUBMISSION_ENDPOINT = '/accounts/onboarding';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { get } from '@/utils/apiUtils';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import { get } from '@n8n/rest-api-client';
|
||||
import type {
|
||||
WorkflowHistory,
|
||||
WorkflowVersion,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { TransferWorkflowBodyDto } from '@n8n/api-types';
|
||||
import type { IRestApiContext, IShareWorkflowsPayload, IWorkflowsShareResponse } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { IShareWorkflowsPayload, IWorkflowsShareResponse } from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
|
||||
export async function setWorkflowSharedWith(
|
||||
|
||||
@@ -4,19 +4,19 @@ import type {
|
||||
FolderTreeResponseItem,
|
||||
IExecutionResponse,
|
||||
IExecutionsCurrentSummaryExtended,
|
||||
IRestApiContext,
|
||||
IUsedCredential,
|
||||
IWorkflowDb,
|
||||
NewWorkflowResponse,
|
||||
WorkflowListResource,
|
||||
} from '@/Interface';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import type {
|
||||
ExecutionFilters,
|
||||
ExecutionOptions,
|
||||
ExecutionSummary,
|
||||
IDataObject,
|
||||
} from 'n8n-workflow';
|
||||
import { getFullApiResponse, makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import { getFullApiResponse, makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
|
||||
export async function getNewWorkflow(context: IRestApiContext, data?: IDataObject) {
|
||||
const response = await makeRestApiRequest<NewWorkflowResponse>(
|
||||
|
||||
@@ -5,7 +5,7 @@ import { STORES } from '@n8n/stores';
|
||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||
import { useStorage } from '@/composables/useStorage';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { getBecomeCreatorCta } from '@/api/ctas';
|
||||
import { getBecomeCreatorCta } from '@n8n/rest-api-client/api/ctas';
|
||||
|
||||
const LOCAL_STORAGE_KEY = 'N8N_BECOME_TEMPLATE_CREATOR_CTA_DISMISSED_AT';
|
||||
const RESHOW_DISMISSED_AFTER_DAYS = 30;
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useTagsStore } from '@/stores/tags.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { createTestNode, createTestWorkflow } from '@/__tests__/mocks';
|
||||
import { WEBHOOK_NODE_TYPE, type AssignmentCollectionValue } from 'n8n-workflow';
|
||||
import * as apiWebhooks from '../api/webhooks';
|
||||
import * as apiWebhooks from '@n8n/rest-api-client/api/webhooks';
|
||||
import { mockedStore } from '@/__tests__/utils';
|
||||
import { nodeTypes } from '@/components/CanvasChat/__test__/data';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
|
||||
@@ -69,7 +69,7 @@ import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { useTagsStore } from '@/stores/tags.store';
|
||||
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
|
||||
import { findWebhook } from '../api/webhooks';
|
||||
import { findWebhook } from '@n8n/rest-api-client/api/webhooks';
|
||||
|
||||
export type ResolveParameterOptions = {
|
||||
targetItem?: TargetItem;
|
||||
|
||||
@@ -919,8 +919,6 @@ export const IsInPiPWindowSymbol = 'IsInPipWindow' as unknown as InjectionKey<
|
||||
>;
|
||||
|
||||
/** Auth */
|
||||
export const BROWSER_ID_STORAGE_KEY = 'n8n-browserId';
|
||||
|
||||
export const APP_MODALS_ELEMENT_ID = 'app-modals';
|
||||
|
||||
export const AI_NODES_PACKAGE_NAME = '@n8n/n8n-nodes-langchain';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import type { IRestApiContext } from '@/Interface';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import type { IRestApiContext } from '@n8n/rest-api-client';
|
||||
import type {
|
||||
InsightsSummary,
|
||||
InsightsByTime,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type * as Sentry from '@sentry/vue';
|
||||
import { beforeSend } from '@/plugins/sentry';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ResponseError } from '@/utils/apiUtils';
|
||||
import { ResponseError } from '@n8n/rest-api-client';
|
||||
|
||||
function createErrorEvent(): Sentry.ErrorEvent {
|
||||
return {} as Sentry.ErrorEvent;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Plugin } from 'vue';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ResponseError } from '@/utils/apiUtils';
|
||||
import { ResponseError } from '@n8n/rest-api-client';
|
||||
import * as Sentry from '@sentry/vue';
|
||||
|
||||
const ignoredErrors = [
|
||||
|
||||
@@ -2,7 +2,7 @@ import { STORES } from '@n8n/stores';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
|
||||
import * as publicApiApi from '@/api/api-keys';
|
||||
import * as publicApiApi from '@n8n/rest-api-client/api/api-keys';
|
||||
import { computed, ref } from 'vue';
|
||||
import type { ApiKey, CreateApiKeyRequestDto, UpdateApiKeyRequestDto } from '@n8n/api-types';
|
||||
import type { ApiKeyScope } from '@n8n/permissions';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as communityNodesApi from '@/api/communityNodes';
|
||||
import * as communityNodesApi from '@n8n/rest-api-client/api/communityNodes';
|
||||
import { getAvailableCommunityPackageCount } from '@/api/settings';
|
||||
import { defineStore } from 'pinia';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
|
||||
@@ -13,7 +13,7 @@ import { EnterpriseEditionFeature } from '@/constants';
|
||||
import { STORES } from '@n8n/stores';
|
||||
import { i18n } from '@n8n/i18n';
|
||||
import type { ProjectSharingData } from '@/types/projects.types';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import { getAppNameFromCredType } from '@/utils/nodeTypesUtils';
|
||||
import { splitName } from '@/utils/projects.utils';
|
||||
import { isEmpty, isPresent } from '@/utils/typesUtils';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { setActivePinia, createPinia } from 'pinia';
|
||||
import type { ExecutionSummaryWithScopes } from '@/Interface';
|
||||
import { useExecutionsStore } from '@/stores/executions.store';
|
||||
|
||||
vi.mock('@/utils/apiUtils', () => ({
|
||||
vi.mock('@n8n/rest-api-client', () => ({
|
||||
makeRestApiRequest: vi.fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ import type {
|
||||
IExecutionsStopData,
|
||||
} from '@/Interface';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { makeRestApiRequest, unflattenExecutionData } from '@/utils/apiUtils';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import { unflattenExecutionData } from '@/utils/executionUtils';
|
||||
import { executionFilterToQueryFilter, getDefaultExecutionFilters } from '@/utils/executionUtils';
|
||||
import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
hasDestinationId,
|
||||
saveDestinationToDb,
|
||||
sendTestMessageToDestination,
|
||||
} from '@/api/eventbus.ee';
|
||||
} from '@n8n/rest-api-client/api/eventbus.ee';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { ref } from 'vue';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
ResourceLocatorRequestDto,
|
||||
ResourceMapperFieldsRequestDto,
|
||||
} from '@n8n/api-types';
|
||||
import * as nodeTypesApi from '@/api/nodeTypes';
|
||||
import * as nodeTypesApi from '@n8n/rest-api-client/api/nodeTypes';
|
||||
import { HTTP_REQUEST_NODE_TYPE, CREDENTIAL_ONLY_HTTP_NODE_VERSION } from '@/constants';
|
||||
import { STORES } from '@n8n/stores';
|
||||
import type { NodeTypesByTypeNameAndVersion } from '@/Interface';
|
||||
|
||||
@@ -16,7 +16,7 @@ vi.mock('@/stores/ui.store', () => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock('@/api/npsSurvey', () => ({
|
||||
vi.mock('@n8n/rest-api-client/api/npsSurvey', () => ({
|
||||
updateNpsSurveyState,
|
||||
}));
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import type { IUserSettings, NpsSurveyState } from 'n8n-workflow';
|
||||
import { useSettingsStore } from './settings.store';
|
||||
import { updateNpsSurveyState } from '@/api/npsSurvey';
|
||||
import { updateNpsSurveyState } from '@n8n/rest-api-client/api/npsSurvey';
|
||||
import type { IN8nPrompts } from '@/Interface';
|
||||
import { getPromptsData } from '@/api/settings';
|
||||
import { assert } from '@n8n/utils/assert';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
|
||||
import type { WorkerStatus } from '@n8n/api-types';
|
||||
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { sendGetWorkerStatus } from '../api/orchestration';
|
||||
import { sendGetWorkerStatus } from '@n8n/rest-api-client/api/orchestration';
|
||||
|
||||
export const WORKER_HISTORY_LENGTH = 100;
|
||||
const STALE_SECONDS = 120 * 1000;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useRolesStore } from '@/stores/roles.store';
|
||||
import * as rolesApi from '@/api/roles.api';
|
||||
import * as rolesApi from '@n8n/rest-api-client/api/roles';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
|
||||
let rolesStore: ReturnType<typeof useRolesStore>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ProjectRole, AllRolesMap } from '@n8n/permissions';
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import * as rolesApi from '@/api/roles.api';
|
||||
import * as rolesApi from '@n8n/rest-api-client/api/roles';
|
||||
import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
|
||||
export const useRolesStore = defineStore('roles', () => {
|
||||
|
||||
@@ -17,7 +17,7 @@ vi.mock('@/api/settings', () => ({
|
||||
getSettings,
|
||||
}));
|
||||
|
||||
vi.mock('@/api/events', () => ({
|
||||
vi.mock('@n8n/rest-api-client/api/events', () => ({
|
||||
sessionStarted,
|
||||
}));
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { computed, ref } from 'vue';
|
||||
import Bowser from 'bowser';
|
||||
import type { IUserManagementSettings, FrontendSettings } from '@n8n/api-types';
|
||||
|
||||
import * as eventsApi from '@/api/events';
|
||||
import * as eventsApi from '@n8n/rest-api-client/api/events';
|
||||
import * as ldapApi from '@/api/ldap';
|
||||
import * as settingsApi from '@/api/settings';
|
||||
import { testHealthEndpoint } from '@/api/templates';
|
||||
@@ -19,7 +19,7 @@ import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { useUIStore } from './ui.store';
|
||||
import { useUsersStore } from './users.store';
|
||||
import { useVersionsStore } from './versions.store';
|
||||
import { makeRestApiRequest } from '@/utils/apiUtils';
|
||||
import { makeRestApiRequest } from '@n8n/rest-api-client';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
|
||||
@@ -59,7 +59,7 @@ import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useUsersStore } from '@/stores/users.store';
|
||||
import { dismissBannerPermanently } from '@/api/ui';
|
||||
import { dismissBannerPermanently } from '@n8n/rest-api-client';
|
||||
import type { BannerName } from '@n8n/api-types';
|
||||
import {
|
||||
addThemeToBody,
|
||||
|
||||
@@ -6,7 +6,8 @@ import type {
|
||||
} from '@n8n/api-types';
|
||||
import type { UpdateGlobalRolePayload } from '@/api/users';
|
||||
import * as usersApi from '@/api/users';
|
||||
import { BROWSER_ID_STORAGE_KEY, PERSONALIZATION_MODAL_KEY, ROLE } from '@/constants';
|
||||
import { BROWSER_ID_STORAGE_KEY } from '@n8n/constants';
|
||||
import { PERSONALIZATION_MODAL_KEY, ROLE } from '@/constants';
|
||||
import { STORES } from '@n8n/stores';
|
||||
import type {
|
||||
Cloud,
|
||||
@@ -22,7 +23,7 @@ import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { usePostHog } from './posthog.store';
|
||||
import { useUIStore } from './ui.store';
|
||||
import { useCloudPlanStore } from './cloudPlan.store';
|
||||
import * as mfaApi from '@/api/mfa';
|
||||
import * as mfaApi from '@n8n/rest-api-client/api/mfa';
|
||||
import * as cloudApi from '@/api/cloudPlans';
|
||||
import { useRBACStore } from '@/stores/rbac.store';
|
||||
import type { Scope } from '@n8n/permissions';
|
||||
|
||||
@@ -27,7 +27,7 @@ import { flushPromises } from '@vue/test-utils';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { mock } from 'vitest-mock-extended';
|
||||
import { mockedStore, type MockedStore } from '@/__tests__/utils';
|
||||
import * as apiUtils from '@/utils/apiUtils';
|
||||
import * as apiUtils from '@n8n/rest-api-client';
|
||||
import { useSettingsStore } from '@/stores/settings.store';
|
||||
import { useLocalStorage } from '@vueuse/core';
|
||||
import { ref } from 'vue';
|
||||
|
||||
@@ -75,7 +75,8 @@ import { dataPinningEventBus } from '@/event-bus';
|
||||
import { isObject } from '@/utils/objectUtils';
|
||||
import { getPairedItemsMapping } from '@/utils/pairedItemUtils';
|
||||
import { isJsonKeyObject, isEmpty, stringSizeInBytes, isPresent } from '@/utils/typesUtils';
|
||||
import { makeRestApiRequest, unflattenExecutionData, ResponseError } from '@/utils/apiUtils';
|
||||
import { makeRestApiRequest, ResponseError } from '@n8n/rest-api-client';
|
||||
import { unflattenExecutionData } from '@/utils/executionUtils';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { getCredentialOnlyNodeTypeName } from '@/utils/credentialOnlyNodes';
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
import { ResponseError, STREAM_SEPERATOR, streamRequest } from './apiUtils';
|
||||
|
||||
describe('streamRequest', () => {
|
||||
it('should stream data from the API endpoint', async () => {
|
||||
const encoder = new TextEncoder();
|
||||
const mockResponse = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(encoder.encode(`${JSON.stringify({ chunk: 1 })}${STREAM_SEPERATOR}`));
|
||||
controller.enqueue(encoder.encode(`${JSON.stringify({ chunk: 2 })}${STREAM_SEPERATOR}`));
|
||||
controller.enqueue(encoder.encode(`${JSON.stringify({ chunk: 3 })}${STREAM_SEPERATOR}`));
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
const mockFetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
body: mockResponse,
|
||||
});
|
||||
|
||||
global.fetch = mockFetch;
|
||||
|
||||
const onChunkMock = vi.fn();
|
||||
const onDoneMock = vi.fn();
|
||||
const onErrorMock = vi.fn();
|
||||
|
||||
await streamRequest(
|
||||
{
|
||||
baseUrl: 'https://api.example.com',
|
||||
pushRef: '',
|
||||
},
|
||||
'/data',
|
||||
{ key: 'value' },
|
||||
onChunkMock,
|
||||
onDoneMock,
|
||||
onErrorMock,
|
||||
);
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/data', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ key: 'value' }),
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'browser-id': expect.stringContaining('-'),
|
||||
},
|
||||
});
|
||||
|
||||
expect(onChunkMock).toHaveBeenCalledTimes(3);
|
||||
expect(onChunkMock).toHaveBeenNthCalledWith(1, { chunk: 1 });
|
||||
expect(onChunkMock).toHaveBeenNthCalledWith(2, { chunk: 2 });
|
||||
expect(onChunkMock).toHaveBeenNthCalledWith(3, { chunk: 3 });
|
||||
|
||||
expect(onDoneMock).toHaveBeenCalledTimes(1);
|
||||
expect(onErrorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should stream error response from the API endpoint', async () => {
|
||||
const testError = { code: 500, message: 'Error happened' };
|
||||
const encoder = new TextEncoder();
|
||||
const mockResponse = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(encoder.encode(JSON.stringify(testError)));
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
const mockFetch = vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
body: mockResponse,
|
||||
});
|
||||
|
||||
global.fetch = mockFetch;
|
||||
|
||||
const onChunkMock = vi.fn();
|
||||
const onDoneMock = vi.fn();
|
||||
const onErrorMock = vi.fn();
|
||||
|
||||
await streamRequest(
|
||||
{
|
||||
baseUrl: 'https://api.example.com',
|
||||
pushRef: '',
|
||||
},
|
||||
'/data',
|
||||
{ key: 'value' },
|
||||
onChunkMock,
|
||||
onDoneMock,
|
||||
onErrorMock,
|
||||
);
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/data', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ key: 'value' }),
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'browser-id': expect.stringContaining('-'),
|
||||
},
|
||||
});
|
||||
|
||||
expect(onChunkMock).not.toHaveBeenCalled();
|
||||
expect(onErrorMock).toHaveBeenCalledTimes(1);
|
||||
expect(onErrorMock).toHaveBeenCalledWith(new ResponseError(testError.message));
|
||||
});
|
||||
|
||||
it('should handle broken stream data', async () => {
|
||||
const encoder = new TextEncoder();
|
||||
const mockResponse = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(
|
||||
encoder.encode(`${JSON.stringify({ chunk: 1 })}${STREAM_SEPERATOR}{"chunk": `),
|
||||
);
|
||||
controller.enqueue(encoder.encode(`2}${STREAM_SEPERATOR}{"ch`));
|
||||
controller.enqueue(encoder.encode('unk":'));
|
||||
controller.enqueue(encoder.encode(`3}${STREAM_SEPERATOR}`));
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
|
||||
const mockFetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
body: mockResponse,
|
||||
});
|
||||
|
||||
global.fetch = mockFetch;
|
||||
|
||||
const onChunkMock = vi.fn();
|
||||
const onDoneMock = vi.fn();
|
||||
const onErrorMock = vi.fn();
|
||||
|
||||
await streamRequest(
|
||||
{
|
||||
baseUrl: 'https://api.example.com',
|
||||
pushRef: '',
|
||||
},
|
||||
'/data',
|
||||
{ key: 'value' },
|
||||
onChunkMock,
|
||||
onDoneMock,
|
||||
onErrorMock,
|
||||
);
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/data', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ key: 'value' }),
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'browser-id': expect.stringContaining('-'),
|
||||
},
|
||||
});
|
||||
|
||||
expect(onChunkMock).toHaveBeenCalledTimes(3);
|
||||
expect(onChunkMock).toHaveBeenNthCalledWith(1, { chunk: 1 });
|
||||
expect(onChunkMock).toHaveBeenNthCalledWith(2, { chunk: 2 });
|
||||
expect(onChunkMock).toHaveBeenNthCalledWith(3, { chunk: 3 });
|
||||
|
||||
expect(onDoneMock).toHaveBeenCalledTimes(1);
|
||||
expect(onErrorMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,311 +0,0 @@
|
||||
import type { AxiosRequestConfig, Method, RawAxiosRequestHeaders } from 'axios';
|
||||
import axios from 'axios';
|
||||
import { ApplicationError, jsonParse, type GenericValue, type IDataObject } from 'n8n-workflow';
|
||||
import { parse } from 'flatted';
|
||||
import { assert } from '@n8n/utils/assert';
|
||||
|
||||
import { BROWSER_ID_STORAGE_KEY } from '@/constants';
|
||||
import type { IExecutionFlattedResponse, IExecutionResponse, IRestApiContext } from '@/Interface';
|
||||
|
||||
const getBrowserId = () => {
|
||||
let browserId = localStorage.getItem(BROWSER_ID_STORAGE_KEY);
|
||||
if (!browserId) {
|
||||
browserId = crypto.randomUUID();
|
||||
localStorage.setItem(BROWSER_ID_STORAGE_KEY, browserId);
|
||||
}
|
||||
return browserId;
|
||||
};
|
||||
|
||||
export const NO_NETWORK_ERROR_CODE = 999;
|
||||
export const STREAM_SEPERATOR = '⧉⇋⇋➽⌑⧉§§\n';
|
||||
|
||||
export class ResponseError extends ApplicationError {
|
||||
// The HTTP status code of response
|
||||
httpStatusCode?: number;
|
||||
|
||||
// The error code in the response
|
||||
errorCode?: number;
|
||||
|
||||
// The stack trace of the server
|
||||
serverStackTrace?: string;
|
||||
|
||||
/**
|
||||
* Creates an instance of ResponseError.
|
||||
* @param {string} message The error message
|
||||
* @param {number} [errorCode] The error code which can be used by frontend to identify the actual error
|
||||
* @param {number} [httpStatusCode] The HTTP status code the response should have
|
||||
* @param {string} [stack] The stack trace
|
||||
*/
|
||||
constructor(
|
||||
message: string,
|
||||
options: { errorCode?: number; httpStatusCode?: number; stack?: string } = {},
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ResponseError';
|
||||
|
||||
const { errorCode, httpStatusCode, stack } = options;
|
||||
if (errorCode) {
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
if (httpStatusCode) {
|
||||
this.httpStatusCode = httpStatusCode;
|
||||
}
|
||||
if (stack) {
|
||||
this.serverStackTrace = stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const legacyParamSerializer = (params: Record<string, any>) =>
|
||||
Object.keys(params)
|
||||
.filter((key) => params[key] !== undefined)
|
||||
.map((key) => {
|
||||
if (Array.isArray(params[key])) {
|
||||
return params[key].map((v: string) => `${key}[]=${encodeURIComponent(v)}`).join('&');
|
||||
}
|
||||
if (typeof params[key] === 'object') {
|
||||
params[key] = JSON.stringify(params[key]);
|
||||
}
|
||||
return `${key}=${encodeURIComponent(params[key])}`;
|
||||
})
|
||||
.join('&');
|
||||
|
||||
export async function request(config: {
|
||||
method: Method;
|
||||
baseURL: string;
|
||||
endpoint: string;
|
||||
headers?: RawAxiosRequestHeaders;
|
||||
data?: GenericValue | GenericValue[];
|
||||
withCredentials?: boolean;
|
||||
}) {
|
||||
const { method, baseURL, endpoint, headers, data } = config;
|
||||
const options: AxiosRequestConfig = {
|
||||
method,
|
||||
url: endpoint,
|
||||
baseURL,
|
||||
headers: headers ?? {},
|
||||
};
|
||||
if (baseURL.startsWith('/')) {
|
||||
options.headers!['browser-id'] = getBrowserId();
|
||||
}
|
||||
if (
|
||||
import.meta.env.NODE_ENV !== 'production' &&
|
||||
!baseURL.includes('api.n8n.io') &&
|
||||
!baseURL.includes('n8n.cloud')
|
||||
) {
|
||||
options.withCredentials = options.withCredentials ?? true;
|
||||
}
|
||||
if (['POST', 'PATCH', 'PUT'].includes(method)) {
|
||||
options.data = data;
|
||||
} else if (data) {
|
||||
options.params = data;
|
||||
options.paramsSerializer = legacyParamSerializer;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await axios.request(options);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (error.message === 'Network Error') {
|
||||
throw new ResponseError("Can't connect to n8n.", {
|
||||
errorCode: NO_NETWORK_ERROR_CODE,
|
||||
});
|
||||
}
|
||||
|
||||
const errorResponseData = error.response?.data;
|
||||
if (errorResponseData?.message !== undefined) {
|
||||
if (errorResponseData.name === 'NodeApiError') {
|
||||
errorResponseData.httpStatusCode = error.response.status;
|
||||
throw errorResponseData;
|
||||
}
|
||||
|
||||
throw new ResponseError(errorResponseData.message, {
|
||||
errorCode: errorResponseData.code,
|
||||
httpStatusCode: error.response.status,
|
||||
stack: errorResponseData.stack,
|
||||
});
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the API and returns the response without extracting the data key.
|
||||
* @param context Rest API context
|
||||
* @param method HTTP method
|
||||
* @param endpoint relative path to the API endpoint
|
||||
* @param data request data
|
||||
* @returns data and total count
|
||||
*/
|
||||
export async function getFullApiResponse<T>(
|
||||
context: IRestApiContext,
|
||||
method: Method,
|
||||
endpoint: string,
|
||||
data?: GenericValue | GenericValue[],
|
||||
) {
|
||||
const response = await request({
|
||||
method,
|
||||
baseURL: context.baseUrl,
|
||||
endpoint,
|
||||
headers: { 'push-ref': context.pushRef },
|
||||
data,
|
||||
});
|
||||
|
||||
return response as { count: number; data: T };
|
||||
}
|
||||
|
||||
export async function makeRestApiRequest<T>(
|
||||
context: IRestApiContext,
|
||||
method: Method,
|
||||
endpoint: string,
|
||||
data?: GenericValue | GenericValue[],
|
||||
) {
|
||||
const response = await request({
|
||||
method,
|
||||
baseURL: context.baseUrl,
|
||||
endpoint,
|
||||
headers: { 'push-ref': context.pushRef },
|
||||
data,
|
||||
});
|
||||
|
||||
// @ts-ignore all cli rest api endpoints return data wrapped in `data` key
|
||||
return response.data as T;
|
||||
}
|
||||
|
||||
export async function get(
|
||||
baseURL: string,
|
||||
endpoint: string,
|
||||
params?: IDataObject,
|
||||
headers?: RawAxiosRequestHeaders,
|
||||
) {
|
||||
return await request({ method: 'GET', baseURL, endpoint, headers, data: params });
|
||||
}
|
||||
|
||||
export async function post(
|
||||
baseURL: string,
|
||||
endpoint: string,
|
||||
params?: IDataObject,
|
||||
headers?: RawAxiosRequestHeaders,
|
||||
) {
|
||||
return await request({ method: 'POST', baseURL, endpoint, headers, data: params });
|
||||
}
|
||||
|
||||
export async function patch(
|
||||
baseURL: string,
|
||||
endpoint: string,
|
||||
params?: IDataObject,
|
||||
headers?: RawAxiosRequestHeaders,
|
||||
) {
|
||||
return await request({ method: 'PATCH', baseURL, endpoint, headers, data: params });
|
||||
}
|
||||
|
||||
/**
|
||||
* Unflattens the Execution data.
|
||||
*
|
||||
* @param {IExecutionFlattedResponse} fullExecutionData The data to unflatten
|
||||
*/
|
||||
export function unflattenExecutionData(fullExecutionData: IExecutionFlattedResponse) {
|
||||
// Unflatten the data
|
||||
const returnData: IExecutionResponse = {
|
||||
...fullExecutionData,
|
||||
workflowData: fullExecutionData.workflowData,
|
||||
data: parse(fullExecutionData.data),
|
||||
};
|
||||
|
||||
returnData.finished = returnData.finished ? returnData.finished : false;
|
||||
|
||||
if (fullExecutionData.id) {
|
||||
returnData.id = fullExecutionData.id;
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function streamRequest<T extends object>(
|
||||
context: IRestApiContext,
|
||||
apiEndpoint: string,
|
||||
payload: object,
|
||||
onChunk?: (chunk: T) => void,
|
||||
onDone?: () => void,
|
||||
onError?: (e: Error) => void,
|
||||
separator = STREAM_SEPERATOR,
|
||||
): Promise<void> {
|
||||
const headers: Record<string, string> = {
|
||||
'browser-id': getBrowserId(),
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
const assistantRequest: RequestInit = {
|
||||
headers,
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(payload),
|
||||
};
|
||||
try {
|
||||
const response = await fetch(`${context.baseUrl}${apiEndpoint}`, assistantRequest);
|
||||
|
||||
if (response.body) {
|
||||
// Handle the streaming response
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
|
||||
let buffer = '';
|
||||
|
||||
async function readStream() {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
onDone?.();
|
||||
return;
|
||||
}
|
||||
const chunk = decoder.decode(value);
|
||||
buffer += chunk;
|
||||
|
||||
const splitChunks = buffer.split(separator);
|
||||
|
||||
buffer = '';
|
||||
for (const splitChunk of splitChunks) {
|
||||
if (splitChunk) {
|
||||
let data: T;
|
||||
try {
|
||||
data = jsonParse<T>(splitChunk, { errorMessage: 'Invalid json' });
|
||||
} catch (e) {
|
||||
// incomplete json. append to buffer to complete
|
||||
buffer += splitChunk;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (response.ok) {
|
||||
// Call chunk callback if request was successful
|
||||
onChunk?.(data);
|
||||
} else {
|
||||
// Otherwise, call error callback
|
||||
const message = 'message' in data ? data.message : response.statusText;
|
||||
onError?.(
|
||||
new ResponseError(String(message), {
|
||||
httpStatusCode: response.status,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
onError?.(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await readStream();
|
||||
}
|
||||
|
||||
// Start reading the stream
|
||||
await readStream();
|
||||
} else if (onError) {
|
||||
onError(new Error(response.statusText));
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
assert(e instanceof Error);
|
||||
onError?.(e);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,13 @@ import type {
|
||||
IRunData,
|
||||
ExecutionError,
|
||||
} from 'n8n-workflow';
|
||||
import type { ExecutionFilterType, ExecutionsQueryFilter, INodeUi } from '@/Interface';
|
||||
import type {
|
||||
ExecutionFilterType,
|
||||
ExecutionsQueryFilter,
|
||||
IExecutionFlattedResponse,
|
||||
IExecutionResponse,
|
||||
INodeUi,
|
||||
} from '@/Interface';
|
||||
import { isEmpty } from '@/utils/typesUtils';
|
||||
import { FORM_NODE_TYPE, FORM_TRIGGER_NODE_TYPE, GITHUB_NODE_TYPE } from '../constants';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
@@ -16,6 +22,7 @@ import { useRootStore } from '@n8n/stores/useRootStore';
|
||||
import { i18n } from '@n8n/i18n';
|
||||
import { h } from 'vue';
|
||||
import NodeExecutionErrorMessage from '@/components/NodeExecutionErrorMessage.vue';
|
||||
import { parse } from 'flatted';
|
||||
|
||||
export function getDefaultExecutionFilters(): ExecutionFilterType {
|
||||
return {
|
||||
@@ -356,3 +363,25 @@ export function getExecutionErrorToastConfiguration({
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unflattens the Execution data.
|
||||
*
|
||||
* @param {IExecutionFlattedResponse} fullExecutionData The data to unflatten
|
||||
*/
|
||||
export function unflattenExecutionData(fullExecutionData: IExecutionFlattedResponse) {
|
||||
// Unflatten the data
|
||||
const returnData: IExecutionResponse = {
|
||||
...fullExecutionData,
|
||||
workflowData: fullExecutionData.workflowData,
|
||||
data: parse(fullExecutionData.data),
|
||||
};
|
||||
|
||||
returnData.finished = returnData.finished ? returnData.finished : false;
|
||||
|
||||
if (fullExecutionData.id) {
|
||||
returnData.id = fullExecutionData.id;
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useI18n } from '@n8n/i18n';
|
||||
import type { ExecutionFilterType, IWorkflowDb } from '@/Interface';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { NO_NETWORK_ERROR_CODE } from '@/utils/apiUtils';
|
||||
import { NO_NETWORK_ERROR_CODE } from '@n8n/rest-api-client';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
import { NEW_WORKFLOW_ID, PLACEHOLDER_EMPTY_WORKFLOW_ID, VIEWS } from '@/constants';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
Reference in New Issue
Block a user