Added Azure Cosmos DB node

This commit is contained in:
Adina Totorean 2025-01-26 15:27:25 +02:00
parent 9e65e853f4
commit d28af0f96a
8 changed files with 1677 additions and 28 deletions

View file

@ -58,6 +58,7 @@
"jest-mock": "^29.6.2",
"jest-mock-extended": "^3.0.4",
"lefthook": "^1.7.15",
"loader": "^2.1.1",
"nock": "^13.3.2",
"nodemon": "^3.0.1",
"npm-run-all2": "^7.0.2",
@ -69,7 +70,7 @@
"tsc-alias": "^1.8.10",
"tsc-watch": "^6.2.0",
"turbo": "2.3.3",
"typescript": "*",
"typescript": "^5.6.2",
"zx": "^8.1.4"
},
"pnpm": {

View file

@ -0,0 +1,113 @@
import {
ApplicationError,
type ICredentialDataDecryptedObject,
type ICredentialType,
type IHttpRequestOptions,
type INodeProperties,
} from 'n8n-workflow';
import { getAuthorizationTokenUsingMasterKey } from '../nodes/Microsoft/AzureCosmosDB/GenericFunctions';
export class AzureCosmosDbSharedKeyApi implements ICredentialType {
name = 'azureCosmosDbSharedKeyApi';
displayName = 'Azure Cosmos DB API';
documentationUrl = 'azureCosmosDb';
properties: INodeProperties[] = [
{
displayName: 'Database',
name: 'databaseAccount',
description: 'Database account',
type: 'string',
default: '',
},
{
displayName: 'Key',
name: 'key',
description: 'Account key',
type: 'string',
typeOptions: {
password: true,
},
default: '',
},
];
async authenticate(
credentials: ICredentialDataDecryptedObject,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
if (requestOptions.qs) {
for (const [key, value] of Object.entries(requestOptions.qs)) {
if (value === undefined) {
delete requestOptions.qs[key];
}
}
}
requestOptions.headers ??= {};
const date = new Date().toUTCString();
requestOptions.headers = {
...requestOptions.headers,
'x-ms-date': date,
'x-ms-version': '2020-04-08',
};
if (credentials.sessionToken) {
requestOptions.headers['x-ms-session-token'] = credentials.sessionToken;
}
let resourceType = '';
let resourceLink = '';
if (requestOptions.body && typeof requestOptions.body === 'object') {
const isCollectionRequest = 'colls' in requestOptions.body;
const isDocumentRequest = 'docs' in requestOptions.body;
if (isCollectionRequest) {
resourceType = 'dbs';
resourceLink = `dbs/${credentials.database}/colls`;
} else if (isDocumentRequest) {
resourceType = 'colls';
const collId = requestOptions.qs?.collId || '';
if (!collId) {
throw new ApplicationError('Collection ID (collId) is required for document requests.');
}
resourceLink = `dbs/${credentials.database}/colls/${collId}/docs`;
}
} else if (requestOptions.qs && typeof requestOptions.qs === 'object') {
const queryType = requestOptions.qs.queryType;
if (queryType === 'colls') {
resourceType = 'dbs';
resourceLink = `dbs/${credentials.database}/colls`;
} else if (queryType === 'docs') {
resourceType = 'colls';
const collId = requestOptions.qs.collId || '';
if (!collId) {
throw new ApplicationError('Collection ID (collId) is required for document queries.');
}
resourceLink = `dbs/${credentials.database}/colls/${collId}/docs`;
}
} else {
throw new ApplicationError(
'Invalid requestOptions: Either body or query string (qs) is required.',
);
}
if (requestOptions.method) {
const authToken = getAuthorizationTokenUsingMasterKey(
requestOptions.method,
resourceType,
resourceLink,
date,
credentials.key as string,
);
requestOptions.headers.authorization = authToken;
}
return requestOptions;
}
}

View file

@ -0,0 +1,84 @@
import type { INodeType, INodeTypeDescription } from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import { containerFields, containerOperations } from './descriptions/ContainerDescription';
import { itemFields, itemOperations } from '../../Aws/DynamoDB/ItemDescription';
export class AzureCosmosDb implements INodeType {
description: INodeTypeDescription = {
displayName: 'Azure Cosmos DB',
name: 'azureCosmosDb',
icon: {
light: 'file:CosmosDB.svg',
dark: 'file:CosmosDB.svg',
},
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Interact with Azure Cosmos DB API',
defaults: {
name: 'Azure Cosmos Db',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
credentials: [
{
name: 'azureCosmosDbSharedKeyApi',
required: true,
displayOptions: {
show: {
authentication: ['sharedKey'],
},
},
},
],
requestDefaults: {
baseURL: '=https://{$credentials.databaseAccount}.documents.azure.com',
headers: {
Accept: 'application/json',
},
},
properties: [
{
displayName: 'Authentication',
name: 'authentication',
type: 'options',
options: [
{
name: 'Shared Key',
value: 'sharedKey',
},
],
default: 'sharedKey',
},
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Container',
value: 'container',
},
{
name: 'Item',
value: 'item',
},
],
default: 'container',
},
...itemFields,
...itemOperations,
...containerOperations,
...containerFields,
],
};
methods = {
listSearch: {
// searchCollections,
// searchDatabases,
},
};
}

View file

@ -0,0 +1,212 @@
import * as crypto from 'crypto';
import type {
IDataObject,
IHttpRequestOptions,
ILoadOptionsFunctions,
INodeListSearchItems,
INodeListSearchResult,
} from 'n8n-workflow';
import { ApplicationError } from 'n8n-workflow';
import * as querystring from 'querystring';
export const HeaderConstants = {
// Required
AUTHORIZATION: 'Authorization',
CONTENT_TYPE: 'Content-Type',
X_MS_DATE: 'x-ms-date',
X_MS_VERSION: 'x-ms-version',
//Required - for session consistency only
X_MS_SESSION_TOKEN: 'x-ms-session-token',
// Optional
IF_MATCH: 'If-Match',
IF_NONE_MATCH: 'If-None-Match',
IF_MODIFIED_SINCE: 'If-Modified-Since',
USER_AGENT: 'User-Agent',
X_MS_ACTIVITY_ID: 'x-ms-activity-id',
X_MS_CONSISTENCY_LEVEL: 'x-ms-consistency-level',
X_MS_CONTINUATION: 'x-ms-continuation',
X_MS_MAX_ITEM_COUNT: 'x-ms-max-item-count',
X_MS_DOCUMENTDB_PARTITIONKEY: 'x-ms-documentdb-partitionkey',
X_MS_DOCUMENTDB_QUERY_ENABLECROSSPARTITION: 'x-ms-documentdb-query-enablecrosspartition',
A_IM: 'A-IM',
X_MS_DOCUMENTDB_PARTITIONKEYRANGEID: 'x-ms-documentdb-partitionkeyrangeid',
X_MS_COSMOS_ALLOW_TENTATIVE_WRITES: 'x-ms-cosmos-allow-tentative-writes',
PREFIX_FOR_STORAGE: 'x-ms-',
};
export function getAuthorizationTokenUsingMasterKey(
verb: string,
resourceType: string,
resourceLink: string,
date: string,
masterKey: string,
): string {
const key = Buffer.from(masterKey, 'base64');
const payload =
`${verb.toLowerCase()}\n` +
`${resourceType.toLowerCase()}\n` +
`${resourceLink}\n` +
`${date.toLowerCase()}\n` +
'\n';
const hmacSha256 = crypto.createHmac('sha256', key);
const hashPayload = hmacSha256.update(payload, 'utf8').digest('base64');
const authorizationString = querystring.escape(`type=master&ver=1.0&sig=${hashPayload}`);
return authorizationString;
}
export async function azureCosmosDbRequest(
this: ILoadOptionsFunctions,
opts: IHttpRequestOptions,
): Promise<IDataObject> {
const credentials = await this.getCredentials('azureCosmosDb');
const databaseAccount = credentials?.database;
if (!databaseAccount) {
throw new ApplicationError('Database account not found in credentials!', { level: 'error' });
}
const requestOptions: IHttpRequestOptions = {
...opts,
baseURL: `https://${databaseAccount}.documents.azure.com`,
json: true,
};
const errorMapping: Record<number, Record<string, string>> = {
403: {
'The security token included in the request is invalid.':
'The Cosmos DB credentials are not valid!',
'The request signature we calculated does not match the signature you provided':
'The Cosmos DB credentials are not valid!',
},
404: {
'The specified resource does not exist.': 'The requested resource was not found!',
},
};
try {
return (await this.helpers.requestWithAuthentication.call(
this,
'azureCosmosDb',
requestOptions,
)) as IDataObject;
} catch (error) {
const statusCode = (error.statusCode || error.cause?.statusCode) as number;
let errorMessage = (error.response?.body?.message ||
error.response?.body?.Message ||
error.message) as string;
if (statusCode in errorMapping && errorMessage in errorMapping[statusCode]) {
throw new ApplicationError(errorMapping[statusCode][errorMessage], {
level: 'error',
});
}
if (error.cause?.error) {
try {
errorMessage = error.cause?.error?.message as string;
} catch (ex) {
throw new ApplicationError(
`Failed to extract error details: ${ex.message || 'Unknown error'}`,
{ level: 'error' },
);
}
}
throw new ApplicationError(`Cosmos DB error response [${statusCode}]: ${errorMessage}`, {
level: 'error',
});
}
}
export async function searchCollections(
this: ILoadOptionsFunctions,
filter?: string,
paginationToken?: string,
): Promise<INodeListSearchResult> {
const dbId = this.getNodeParameter('dbId') as string;
if (!dbId) {
throw new ApplicationError('Database ID is required');
}
const credentials = await this.getCredentials('azureCosmosDb');
const databaseAccount = credentials?.databaseAccount;
if (!databaseAccount) {
throw new ApplicationError('Database account not found in credentials!', { level: 'error' });
}
const opts: IHttpRequestOptions = {
method: 'GET',
url: `/dbs/${dbId}/colls`,
baseURL: `https://${databaseAccount}.documents.azure.com`,
headers: {
'Content-Type': 'application/json',
},
};
const responseData: IDataObject = await azureCosmosDbRequest.call(this, opts);
const responseBody = responseData as {
Collections: IDataObject[];
};
const collections = responseBody.Collections;
if (!collections) {
return { results: [] };
}
const results: INodeListSearchItems[] = collections
.map((collection) => ({
name: String(collection.id),
value: String(collection.id),
}))
.filter((collection) => !filter || collection.name.includes(filter))
.sort((a, b) => a.name.localeCompare(b.name));
return {
results,
};
}
export async function searchDatabases(
this: ILoadOptionsFunctions,
filter?: string,
): Promise<INodeListSearchResult> {
const opts: IHttpRequestOptions = {
method: 'GET',
url: '/dbs',
headers: {
'Content-Type': 'application/json',
},
};
const responseData: IDataObject = await azureCosmosDbRequest.call(this, opts);
const responseBody = responseData as {
Databases: IDataObject[];
};
const databases = responseBody.Databases;
if (!databases) {
return { results: [] };
}
const results: INodeListSearchItems[] = databases
.map((database) => ({
name: String(database.id),
value: String(database.id),
}))
.filter((database) => !filter || database.name.includes(filter))
.sort((a, b) => a.name.localeCompare(b.name));
return {
results,
};
}

View file

@ -0,0 +1,429 @@
import type { INodeProperties } from 'n8n-workflow';
export const containerOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['container'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create a container',
routing: {
request: {
ignoreHttpStatusErrors: true,
method: 'POST',
url: '=/dbs/{{ $parameter["dbId"] }}/colls',
},
},
action: 'Create container',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a container',
routing: {
request: {
ignoreHttpStatusErrors: true,
method: 'DELETE',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}',
},
},
action: 'Delete container',
},
{
name: 'Get',
value: 'get',
description: 'Retrieve a container',
routing: {
request: {
ignoreHttpStatusErrors: true,
method: 'GET',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}',
},
},
action: 'Get item',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Retrieve a list of containers',
routing: {
request: {
ignoreHttpStatusErrors: true,
method: 'GET',
url: '=/dbs/{{ $parameter["dbId"] }}/colls',
},
},
action: 'Get many containers',
},
],
default: 'getAll',
},
];
export const createFields: INodeProperties[] = [
{
displayName: 'Database ID',
name: 'dbId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the database you want to use',
displayOptions: {
show: {
resource: ['container'],
operation: ['create'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchDatabases',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'databaseName',
type: 'string',
hint: 'Enter the database name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The database name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersDB',
},
],
},
{
displayName: 'ID',
name: 'id',
type: 'string',
default: '',
placeholder: 'e.g. AndersenFamily',
description: "Container's ID",
required: true,
displayOptions: {
show: {
resource: ['container'],
operation: ['create'],
},
},
routing: {
send: {
type: 'body',
property: 'id',
value: '={{$value}}',
},
},
},
{
displayName: 'Partition Key',
name: 'partitionKey',
type: 'json',
default: '{}',
placeholder: '"paths": ["/AccountNumber"],"kind": "Hash", "Version": 2',
description: 'User-defined JSON object representing the partition key',
required: true,
displayOptions: {
show: {
resource: ['container'],
operation: ['create'],
},
},
routing: {
send: {
type: 'body',
property: 'partitionKey',
value: '={{$value}}',
},
},
},
{
displayName: 'Additional Keys',
name: 'additionalKeys',
type: 'fixedCollection',
default: {},
placeholder: '"paths": ["/AccountNumber"],"kind": "Hash", "Version": 2',
description: 'User-defined JSON object representing the document properties',
displayOptions: {
show: {
resource: ['container'],
operation: ['create'],
},
},
options: [
{
displayName: 'Indexing Policy',
name: 'indexingPolicy',
type: 'json',
default: '{}',
placeholder:
'"automatic": true, "indexingMode": "Consistent", "includedPaths": [{ "path": "/*", "indexes": [{ "dataType": "String", "precision": -1, "kind": "Range" }]}]',
description: 'This value is used to configure indexing policy',
routing: {
send: {
type: 'body',
property: 'indexingPolicy',
value: '={{$value}}',
},
},
},
],
},
];
export const getFields: INodeProperties[] = [
{
displayName: 'Database ID',
name: 'dbId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the database you want to use',
displayOptions: {
show: {
resource: ['container'],
operation: ['get'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchDatabases',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'databaseName',
type: 'string',
hint: 'Enter the database name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The database name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersDB',
},
],
},
{
displayName: 'Container ID',
name: 'collId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the container you want to retrieve',
displayOptions: {
show: {
resource: ['container'],
operation: ['get'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchCollections',
searchable: true,
},
},
{
displayName: 'By ID',
name: 'containerId',
type: 'string',
hint: 'Enter the container ID',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The container ID must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. AndersenFamily',
},
],
},
];
export const getAllFields: INodeProperties[] = [
{
displayName: 'Database ID',
name: 'dbId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the database you want to use',
displayOptions: {
show: {
resource: ['container'],
operation: ['getAll'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchDatabases',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'databaseName',
type: 'string',
hint: 'Enter the database name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The database name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersDB',
},
],
},
];
export const deleteFields: INodeProperties[] = [
{
displayName: 'Database ID',
name: 'dbId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the database you want to use',
displayOptions: {
show: {
resource: ['container'],
operation: ['delete'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchDatabases',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'databaseName',
type: 'string',
hint: 'Enter the database name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The database name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersDB',
},
],
},
{
displayName: 'Container ID',
name: 'collId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the container you want to delete',
displayOptions: {
show: {
resource: ['container'],
operation: ['delete'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchCollections',
searchable: true,
},
},
{
displayName: 'By ID',
name: 'containerId',
type: 'string',
hint: 'Enter the container ID',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The container ID must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. AndersenFamily',
},
],
},
];
export const containerFields: INodeProperties[] = [
...createFields,
...deleteFields,
...getFields,
...getAllFields,
];

View file

@ -0,0 +1,807 @@
import type { INodeProperties } from 'n8n-workflow';
export const itemOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['item'],
},
},
options: [
{
name: 'Create',
value: 'create',
description: 'Create an item',
routing: {
request: {
ignoreHttpStatusErrors: true,
method: 'POST',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/item',
},
},
action: 'Create item',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete an item',
routing: {
request: {
ignoreHttpStatusErrors: true,
method: 'DELETE',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/item/{{ $parameter["id"] }}',
},
},
action: 'Delete item',
},
{
name: 'Get',
value: 'get',
description: 'Retrieve an item',
routing: {
request: {
ignoreHttpStatusErrors: true,
method: 'GET',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/item/{{ $parameter["id"] }}',
},
},
action: 'Get item',
},
{
name: 'Get Many',
value: 'getAll',
description: 'Retrieve a list of items',
routing: {
request: {
ignoreHttpStatusErrors: true,
method: 'GET',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/docs',
},
},
action: 'Get many items',
},
{
name: 'Query',
value: 'query',
description: 'Query items',
routing: {
request: {
ignoreHttpStatusErrors: true,
method: 'POST',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/item',
},
},
action: 'Query items',
},
{
name: 'Update',
value: 'update',
description: 'Update an item',
routing: {
request: {
ignoreHttpStatusErrors: true,
method: 'PATCH',
url: '=/dbs/{{ $parameter["dbId"] }}/colls/{{ $parameter["collId"] }}/item/{{ $parameter["id"] }}',
},
},
action: 'Create item',
},
],
default: 'getAll',
},
];
export const createFields: INodeProperties[] = [
{
displayName: 'Database ID',
name: 'dbId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the database you want to use',
displayOptions: {
show: {
resource: ['item'],
operation: ['create'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchDatabases',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'databaseName',
type: 'string',
hint: 'Enter the database name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The database name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersDB',
},
],
},
{
displayName: 'Collection ID',
name: 'collId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the collection you want to use',
displayOptions: {
show: {
resource: ['item'],
operation: ['create'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchCollections',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'collectionName',
type: 'string',
hint: 'Enter the collection name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The collection name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersCollection',
},
],
},
{
displayName: 'ID',
name: 'id',
type: 'string',
default: '',
placeholder: 'e.g. AndersenFamily',
description: "Item's ID",
required: true,
displayOptions: {
show: {
resource: ['item'],
operation: ['create'],
},
},
routing: {
send: {
type: 'body',
property: 'id',
value: '={{$value}}',
},
},
},
{
displayName: 'Custom Properties',
name: 'customProperties',
type: 'json',
default: '{}',
placeholder: '{ "LastName": "Andersen", "Address": { "State": "WA", "City": "Seattle" } }',
description: 'User-defined JSON object representing the document properties',
required: true,
displayOptions: {
show: {
resource: ['item'],
operation: ['create'],
},
},
routing: {
send: {
type: 'body',
property: '',
value: '={{$value}}',
},
},
},
];
export const deleteFields: INodeProperties[] = [
{
displayName: 'Database ID',
name: 'dbId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the database you want to use',
displayOptions: {
show: {
resource: ['item'],
operation: ['delete'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchDatabases',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'databaseName',
type: 'string',
hint: 'Enter the database name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The database name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersDB',
},
],
},
{
displayName: 'Collection ID',
name: 'collId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the collection you want to use',
displayOptions: {
show: {
resource: ['item'],
operation: ['delete'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchCollections',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'collectionName',
type: 'string',
hint: 'Enter the collection name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The collection name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersCollection',
},
],
},
{
displayName: 'ID',
name: 'id',
type: 'string',
default: '',
placeholder: 'e.g. AndersenFamily',
description: 'Unique ID for the item',
required: true,
displayOptions: {
show: {
resource: ['item'],
operation: ['delete'],
},
},
},
];
export const getFields: INodeProperties[] = [
{
displayName: 'Database ID',
name: 'dbId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the database you want to use',
displayOptions: {
show: {
resource: ['item'],
operation: ['get'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchDatabases',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'databaseName',
type: 'string',
hint: 'Enter the database name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The database name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersDB',
},
],
},
{
displayName: 'Collection ID',
name: 'collId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the collection you want to use',
displayOptions: {
show: {
resource: ['item'],
operation: ['get'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchCollections',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'collectionName',
type: 'string',
hint: 'Enter the collection name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The collection name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersCollection',
},
],
},
{
displayName: 'ID',
name: 'id',
type: 'string',
default: '',
placeholder: 'e.g. AndersenFamily',
description: "Item's ID",
required: true,
displayOptions: {
show: {
resource: ['item'],
operation: ['get'],
},
},
routing: {
send: {
type: 'body',
property: 'id',
value: '={{$value}}',
},
},
},
];
export const getAllFields: INodeProperties[] = [
{
displayName: 'Database ID',
name: 'dbId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the database you want to use',
displayOptions: {
show: {
resource: ['item'],
operation: ['getAll'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchDatabases',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'databaseName',
type: 'string',
hint: 'Enter the database name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The database name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersDB',
},
],
},
{
displayName: 'Collection ID',
name: 'collId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the collection you want to use',
displayOptions: {
show: {
resource: ['item'],
operation: ['getAll'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchCollections',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'collectionName',
type: 'string',
hint: 'Enter the collection name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The collection name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersCollection',
},
],
},
];
//TO-DO-check-fields
export const queryFields: INodeProperties[] = [
{
displayName: 'Database ID',
name: 'dbId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the database you want to use',
displayOptions: {
show: {
resource: ['item'],
operation: ['query'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchDatabases',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'databaseName',
type: 'string',
hint: 'Enter the database name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The database name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersDB',
},
],
},
{
displayName: 'Collection ID',
name: 'collId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the collection you want to use',
displayOptions: {
show: {
resource: ['item'],
operation: ['query'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchCollections',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'collectionName',
type: 'string',
hint: 'Enter the collection name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The collection name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersCollection',
},
],
},
{
displayName: 'ID',
name: 'id',
type: 'string',
default: '',
placeholder: 'e.g. AndersenFamily',
description: "Item's ID",
required: true,
displayOptions: {
show: {
resource: ['item'],
operation: ['query'],
},
},
routing: {
send: {
type: 'body',
property: 'id',
value: '={{$value}}',
},
},
},
];
export const updateFields: INodeProperties[] = [
{
displayName: 'Database ID',
name: 'dbId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the database you want to use',
displayOptions: {
show: {
resource: ['item'],
operation: ['update'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchDatabases',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'databaseName',
type: 'string',
hint: 'Enter the database name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The database name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersDB',
},
],
},
{
displayName: 'Collection ID',
name: 'collId',
type: 'resourceLocator',
required: true,
default: {
mode: 'list',
value: '',
},
description: 'Select the collection you want to use',
displayOptions: {
show: {
resource: ['item'],
operation: ['update'],
},
},
modes: [
{
displayName: 'From list',
name: 'list',
type: 'list',
typeOptions: {
searchListMethod: 'searchCollections',
searchable: true,
},
},
{
displayName: 'By Name',
name: 'collectionName',
type: 'string',
hint: 'Enter the collection name',
validation: [
{
type: 'regex',
properties: {
regex: '^[\\w+=,.@-]+$',
errorMessage: 'The collection name must follow the allowed pattern.',
},
},
],
placeholder: 'e.g. UsersCollection',
},
],
},
{
displayName: 'ID',
name: 'id',
type: 'string',
default: '',
placeholder: 'e.g. AndersenFamily',
description: 'Unique ID for the document',
required: true,
displayOptions: {
show: {
resource: ['item'],
operation: ['update'],
},
},
},
//TO-DO-check-this
{
displayName: 'Operations',
name: 'operations',
type: 'resourceMapper',
default: {
mappingMode: 'defineBelow',
value: null,
},
required: true,
typeOptions: {
resourceMapper: {
resourceMapperMethod: 'getMappingColumns',
mode: 'update',
fieldWords: {
singular: 'operation',
plural: 'operations',
},
addAllFields: true,
multiKeyMatch: false,
supportAutoMap: true,
matchingFieldsLabels: {
title: 'Custom Matching Operations',
description: 'Define the operations to perform, such as "set", "delete", or "add".',
hint: 'Map input data to the expected structure of the operations array.',
},
},
},
description: 'Define the operations to perform, such as setting or updating document fields',
displayOptions: {
show: {
resource: ['item'],
operation: ['update'],
},
},
//TO-DO-presend-function
},
];
export const itemFields: INodeProperties[] = [
...createFields,
...deleteFields,
...getFields,
...getAllFields,
...queryFields,
...updateFields,
];

View file

@ -1,6 +1,6 @@
{
"name": "n8n-nodes-base",
"version": "1.74.0",
"version": "1.75.0",
"description": "Base nodes of n8n",
"main": "index.js",
"scripts": {
@ -39,6 +39,7 @@
"dist/credentials/AutomizyApi.credentials.js",
"dist/credentials/AutopilotApi.credentials.js",
"dist/credentials/Aws.credentials.js",
"dist/credentials/AzureCosmosDbSharedKeyApi.credentials.js",
"dist/credentials/BambooHrApi.credentials.js",
"dist/credentials/BannerbearApi.credentials.js",
"dist/credentials/BaserowApi.credentials.js",
@ -625,6 +626,7 @@
"dist/nodes/Merge/Merge.node.js",
"dist/nodes/MessageBird/MessageBird.node.js",
"dist/nodes/Metabase/Metabase.node.js",
"dist/nodes/Microsoft/AzureCosmosDB/AzureCosmosDb.node.js",
"dist/nodes/Microsoft/Dynamics/MicrosoftDynamicsCrm.node.js",
"dist/nodes/Microsoft/Entra/MicrosoftEntra.node.js",
"dist/nodes/Microsoft/Excel/MicrosoftExcel.node.js",

View file

@ -180,6 +180,9 @@ importers:
lefthook:
specifier: ^1.7.15
version: 1.7.15
loader:
specifier: ^2.1.1
version: 2.1.1
nock:
specifier: ^13.3.2
version: 13.3.2
@ -440,7 +443,7 @@ importers:
version: 3.666.0(@aws-sdk/client-sts@3.666.0)
'@getzep/zep-cloud':
specifier: 1.0.12
version: 1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(e4rnrwhosnp2xiru36mqgdy2bu))
version: 1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i))
'@getzep/zep-js':
specifier: 0.9.0
version: 0.9.0
@ -467,7 +470,7 @@ importers:
version: 0.3.1(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)
'@langchain/community':
specifier: 0.3.15
version: 0.3.15(vc5hvyy27o4cmm4jplsptc2fqm)
version: 0.3.15(v4qhcw25bevfr6xzz4fnsvjiqe)
'@langchain/core':
specifier: 'catalog:'
version: 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
@ -554,7 +557,7 @@ importers:
version: 23.0.1
langchain:
specifier: 0.3.6
version: 0.3.6(e4rnrwhosnp2xiru36mqgdy2bu)
version: 0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i)
lodash:
specifier: 'catalog:'
version: 4.17.21
@ -9734,6 +9737,9 @@ packages:
enquirer:
optional: true
loader@2.1.1:
resolution: {integrity: sha512-Z6nHbyKiECMexVUmpIAVXp+f+8okRtfiMAQ9j0eTtImCghxSqQwrbeSiAyCYKBnfvRlgXLdQt0tPuaZmcBQzpw==}
local-pkg@0.5.0:
resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
engines: {node: '>=14'}
@ -9788,6 +9794,7 @@ packages:
lodash.isequal@4.5.0:
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
lodash.isinteger@4.0.4:
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
@ -15764,7 +15771,7 @@ snapshots:
'@gar/promisify@1.1.3':
optional: true
'@getzep/zep-cloud@1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(e4rnrwhosnp2xiru36mqgdy2bu))':
'@getzep/zep-cloud@1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i))':
dependencies:
form-data: 4.0.0
node-fetch: 2.7.0(encoding@0.1.13)
@ -15773,7 +15780,7 @@ snapshots:
zod: 3.23.8
optionalDependencies:
'@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
langchain: 0.3.6(e4rnrwhosnp2xiru36mqgdy2bu)
langchain: 0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i)
transitivePeerDependencies:
- encoding
@ -16237,7 +16244,7 @@ snapshots:
- aws-crt
- encoding
'@langchain/community@0.3.15(vc5hvyy27o4cmm4jplsptc2fqm)':
'@langchain/community@0.3.15(v4qhcw25bevfr6xzz4fnsvjiqe)':
dependencies:
'@ibm-cloud/watsonx-ai': 1.1.2
'@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
@ -16247,7 +16254,7 @@ snapshots:
flat: 5.0.2
ibm-cloud-sdk-core: 5.1.0
js-yaml: 4.1.0
langchain: 0.3.6(e4rnrwhosnp2xiru36mqgdy2bu)
langchain: 0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i)
langsmith: 0.2.3(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
uuid: 10.0.0
zod: 3.23.8
@ -16260,7 +16267,7 @@ snapshots:
'@aws-sdk/client-s3': 3.666.0
'@aws-sdk/credential-provider-node': 3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@aws-sdk/client-sts@3.666.0)
'@azure/storage-blob': 12.18.0(encoding@0.1.13)
'@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(e4rnrwhosnp2xiru36mqgdy2bu))
'@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i))
'@getzep/zep-js': 0.9.0
'@google-ai/generativelanguage': 2.6.0(encoding@0.1.13)
'@google-cloud/storage': 7.12.1(encoding@0.1.13)
@ -19553,14 +19560,6 @@ snapshots:
transitivePeerDependencies:
- debug
axios@1.7.4(debug@4.3.7):
dependencies:
follow-redirects: 1.15.6(debug@4.3.7)
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
axios@1.7.7:
dependencies:
follow-redirects: 1.15.6(debug@4.3.6)
@ -21284,7 +21283,7 @@ snapshots:
eslint-import-resolver-node@0.3.9:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
is-core-module: 2.13.1
resolve: 1.22.8
transitivePeerDependencies:
@ -21309,7 +21308,7 @@ snapshots:
eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.7.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
optionalDependencies:
'@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.7.2)
eslint: 8.57.0
@ -21329,7 +21328,7 @@ snapshots:
array.prototype.findlastindex: 1.2.3
array.prototype.flat: 1.3.2
array.prototype.flatmap: 1.3.2
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
@ -22108,7 +22107,7 @@ snapshots:
array-parallel: 0.1.3
array-series: 0.1.5
cross-spawn: 4.0.2
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
@ -22414,7 +22413,7 @@ snapshots:
'@types/debug': 4.1.12
'@types/node': 18.16.16
'@types/tough-cookie': 4.0.2
axios: 1.7.4(debug@4.3.7)
axios: 1.7.4
camelcase: 6.3.0
debug: 4.3.7
dotenv: 16.4.5
@ -22424,7 +22423,7 @@ snapshots:
isstream: 0.1.2
jsonwebtoken: 9.0.2
mime-types: 2.1.35
retry-axios: 2.6.0(axios@1.7.4)
retry-axios: 2.6.0(axios@1.7.4(debug@4.3.7))
tough-cookie: 4.1.3
transitivePeerDependencies:
- supports-color
@ -23429,7 +23428,7 @@ snapshots:
kuler@2.0.0: {}
langchain@0.3.6(e4rnrwhosnp2xiru36mqgdy2bu):
langchain@0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i):
dependencies:
'@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
'@langchain/openai': 0.3.14(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)
@ -23594,6 +23593,8 @@ snapshots:
optionalDependencies:
enquirer: 2.3.6
loader@2.1.1: {}
local-pkg@0.5.0:
dependencies:
mlly: 1.4.2
@ -24999,7 +25000,7 @@ snapshots:
pdf-parse@1.1.1:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
node-ensure: 0.0.0
transitivePeerDependencies:
- supports-color
@ -25821,7 +25822,7 @@ snapshots:
ret@0.1.15: {}
retry-axios@2.6.0(axios@1.7.4):
retry-axios@2.6.0(axios@1.7.4(debug@4.3.7)):
dependencies:
axios: 1.7.4
@ -25848,7 +25849,7 @@ snapshots:
rhea@1.0.24:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color