first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.awsDynamoDb",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Data & Storage", "Development"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/aws/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.awsdynamodb/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||
import {
|
||||
type IExecuteFunctions,
|
||||
type IDataObject,
|
||||
type ILoadOptionsFunctions,
|
||||
type INodeExecutionData,
|
||||
type INodeType,
|
||||
type INodeTypeDescription,
|
||||
type NodeParameterValue,
|
||||
NodeConnectionTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { awsApiRequest, awsApiRequestAllItems } from './GenericFunctions';
|
||||
import { itemFields, itemOperations } from './ItemDescription';
|
||||
import type {
|
||||
FieldsUiValues,
|
||||
IAttributeNameUi,
|
||||
IAttributeValue,
|
||||
IAttributeValueUi,
|
||||
IRequestBody,
|
||||
PutItemUi,
|
||||
} from './types';
|
||||
import {
|
||||
adjustExpressionAttributeName,
|
||||
adjustExpressionAttributeValues,
|
||||
adjustPutItem,
|
||||
decodeItem,
|
||||
simplify,
|
||||
} from './utils';
|
||||
import { awsNodeAuthOptions, awsNodeCredentials } from '../utils';
|
||||
|
||||
export class AwsDynamoDB implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'AWS DynamoDB',
|
||||
name: 'awsDynamoDb',
|
||||
icon: 'file:dynamodb.svg',
|
||||
group: ['transform'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume the AWS DynamoDB API',
|
||||
schemaPath: 'Aws/DynamoDB',
|
||||
defaults: {
|
||||
name: 'AWS DynamoDB',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: awsNodeCredentials,
|
||||
properties: [
|
||||
awsNodeAuthOptions,
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Item',
|
||||
value: 'item',
|
||||
},
|
||||
],
|
||||
default: 'item',
|
||||
},
|
||||
...itemOperations,
|
||||
...itemFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getTables(this: ILoadOptionsFunctions) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/x-amz-json-1.0',
|
||||
'X-Amz-Target': 'DynamoDB_20120810.ListTables',
|
||||
};
|
||||
|
||||
const responseData = await awsApiRequest.call(this, 'dynamodb', 'POST', '/', {}, headers);
|
||||
|
||||
return responseData.TableNames.map((table: string) => ({ name: table, value: table }));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0);
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
|
||||
let responseData;
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
if (resource === 'item') {
|
||||
if (operation === 'upsert') {
|
||||
// ----------------------------------
|
||||
// upsert
|
||||
// ----------------------------------
|
||||
|
||||
// https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html
|
||||
|
||||
const eavUi = this.getNodeParameter(
|
||||
'additionalFields.eavUi.eavValues',
|
||||
i,
|
||||
[],
|
||||
) as IAttributeValueUi[];
|
||||
const conditionExpession = this.getNodeParameter(
|
||||
'additionalFields.conditionExpression',
|
||||
i,
|
||||
'',
|
||||
) as string;
|
||||
const eanUi = this.getNodeParameter(
|
||||
'additionalFields.eanUi.eanValues',
|
||||
i,
|
||||
[],
|
||||
) as IAttributeNameUi[];
|
||||
|
||||
const body: IRequestBody = {
|
||||
TableName: this.getNodeParameter('tableName', i) as string,
|
||||
};
|
||||
|
||||
const expressionAttributeValues = adjustExpressionAttributeValues(eavUi);
|
||||
|
||||
if (Object.keys(expressionAttributeValues).length) {
|
||||
body.ExpressionAttributeValues = expressionAttributeValues;
|
||||
}
|
||||
|
||||
const expressionAttributeName = adjustExpressionAttributeName(eanUi);
|
||||
|
||||
if (Object.keys(expressionAttributeName).length) {
|
||||
body.ExpressionAttributeNames = expressionAttributeName;
|
||||
}
|
||||
|
||||
if (conditionExpession) {
|
||||
body.ConditionExpression = conditionExpession;
|
||||
}
|
||||
|
||||
const dataToSend = this.getNodeParameter('dataToSend', 0) as
|
||||
| 'defineBelow'
|
||||
| 'autoMapInputData';
|
||||
const item: { [key: string]: string } = {};
|
||||
|
||||
if (dataToSend === 'autoMapInputData') {
|
||||
const incomingKeys = Object.keys(items[i].json);
|
||||
const rawInputsToIgnore = this.getNodeParameter('inputsToIgnore', i) as string;
|
||||
const inputsToIgnore = rawInputsToIgnore.split(',').map((c) => c.trim());
|
||||
|
||||
for (const key of incomingKeys) {
|
||||
if (inputsToIgnore.includes(key)) continue;
|
||||
item[key] = items[i].json[key] as string;
|
||||
}
|
||||
|
||||
body.Item = adjustPutItem(item as PutItemUi);
|
||||
} else {
|
||||
const fields = this.getNodeParameter('fieldsUi.fieldValues', i, []) as FieldsUiValues;
|
||||
fields.forEach(({ fieldId, fieldValue }) => (item[fieldId] = fieldValue));
|
||||
body.Item = adjustPutItem(item as PutItemUi);
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/x-amz-json-1.0',
|
||||
'X-Amz-Target': 'DynamoDB_20120810.PutItem',
|
||||
};
|
||||
|
||||
responseData = await awsApiRequest.call(this, 'dynamodb', 'POST', '/', body, headers);
|
||||
responseData = item;
|
||||
} else if (operation === 'delete') {
|
||||
// ----------------------------------
|
||||
// delete
|
||||
// ----------------------------------
|
||||
|
||||
// https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html
|
||||
|
||||
const body: { [key: string]: any } = {
|
||||
TableName: this.getNodeParameter('tableName', i) as string,
|
||||
Key: {},
|
||||
ReturnValues: this.getNodeParameter('returnValues', 0) as string,
|
||||
};
|
||||
|
||||
const eavUi = this.getNodeParameter(
|
||||
'additionalFields.expressionAttributeUi.expressionAttributeValues',
|
||||
i,
|
||||
[],
|
||||
) as IAttributeValueUi[];
|
||||
const eanUi = this.getNodeParameter(
|
||||
'additionalFields.eanUi.eanValues',
|
||||
i,
|
||||
[],
|
||||
) as IAttributeNameUi[];
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
const simple = this.getNodeParameter('simple', 0, false) as boolean;
|
||||
|
||||
const keyValues = this.getNodeParameter('keysUi.keyValues', i, []) as [
|
||||
{ key: string; type: string; value: string },
|
||||
];
|
||||
|
||||
for (const item of keyValues) {
|
||||
let value = item.value as NodeParameterValue;
|
||||
// All data has to get send as string even numbers
|
||||
// @ts-ignore
|
||||
value = ![null, undefined].includes(value) ? value?.toString() : '';
|
||||
body.Key[item.key] = { [item.type]: value };
|
||||
}
|
||||
|
||||
const expressionAttributeValues = adjustExpressionAttributeValues(eavUi);
|
||||
|
||||
if (Object.keys(expressionAttributeValues).length) {
|
||||
body.ExpressionAttributeValues = expressionAttributeValues;
|
||||
}
|
||||
|
||||
const expressionAttributeName = adjustExpressionAttributeName(eanUi);
|
||||
|
||||
if (Object.keys(expressionAttributeName).length) {
|
||||
body.ExpressionAttributeNames = expressionAttributeName;
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/x-amz-json-1.0',
|
||||
'X-Amz-Target': 'DynamoDB_20120810.DeleteItem',
|
||||
};
|
||||
|
||||
if (additionalFields.conditionExpression) {
|
||||
body.ConditionExpression = additionalFields.conditionExpression as string;
|
||||
}
|
||||
|
||||
responseData = await awsApiRequest.call(this, 'dynamodb', 'POST', '/', body, headers);
|
||||
|
||||
if (!Object.keys(responseData as IDataObject).length) {
|
||||
responseData = { success: true };
|
||||
} else if (simple) {
|
||||
responseData = decodeItem(responseData.Attributes as IAttributeValue);
|
||||
}
|
||||
} else if (operation === 'get') {
|
||||
// ----------------------------------
|
||||
// get
|
||||
// ----------------------------------
|
||||
|
||||
// https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html
|
||||
|
||||
const tableName = this.getNodeParameter('tableName', 0) as string;
|
||||
const simple = this.getNodeParameter('simple', 0, false) as boolean;
|
||||
const select = this.getNodeParameter('select', 0) as string;
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
const eanUi = this.getNodeParameter(
|
||||
'additionalFields.eanUi.eanValues',
|
||||
i,
|
||||
[],
|
||||
) as IAttributeNameUi[];
|
||||
|
||||
const body: { [key: string]: any } = {
|
||||
TableName: tableName,
|
||||
Key: {},
|
||||
Select: select,
|
||||
};
|
||||
|
||||
Object.assign(body, additionalFields);
|
||||
|
||||
const expressionAttributeName = adjustExpressionAttributeName(eanUi);
|
||||
|
||||
if (Object.keys(expressionAttributeName).length) {
|
||||
body.ExpressionAttributeNames = expressionAttributeName;
|
||||
}
|
||||
|
||||
if (additionalFields.readType) {
|
||||
body.ConsistentRead = additionalFields.readType === 'stronglyConsistentRead';
|
||||
}
|
||||
|
||||
if (additionalFields.projectionExpression) {
|
||||
body.ProjectionExpression = additionalFields.projectionExpression as string;
|
||||
}
|
||||
|
||||
const keyValues = this.getNodeParameter('keysUi.keyValues', i, []) as IDataObject[];
|
||||
|
||||
for (const item of keyValues) {
|
||||
let value = item.value as NodeParameterValue;
|
||||
// All data has to get send as string even numbers
|
||||
// @ts-ignore
|
||||
value = ![null, undefined].includes(value) ? value?.toString() : '';
|
||||
body.Key[item.key as string] = { [item.type as string]: value };
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'X-Amz-Target': 'DynamoDB_20120810.GetItem',
|
||||
'Content-Type': 'application/x-amz-json-1.0',
|
||||
};
|
||||
|
||||
responseData = await awsApiRequest.call(this, 'dynamodb', 'POST', '/', body, headers);
|
||||
|
||||
responseData = responseData.Item;
|
||||
|
||||
if (simple && responseData) {
|
||||
responseData = decodeItem(responseData as IAttributeValue);
|
||||
}
|
||||
} else if (operation === 'getAll') {
|
||||
// ----------------------------------
|
||||
// getAll
|
||||
// ----------------------------------
|
||||
|
||||
// https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html
|
||||
|
||||
const eavUi = this.getNodeParameter('eavUi.eavValues', i, []) as IAttributeValueUi[];
|
||||
const simple = this.getNodeParameter('simple', 0, false) as boolean;
|
||||
const select = this.getNodeParameter('select', 0) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||
const scan = this.getNodeParameter('scan', 0) as boolean;
|
||||
const eanUi = this.getNodeParameter(
|
||||
'options.eanUi.eanValues',
|
||||
i,
|
||||
[],
|
||||
) as IAttributeNameUi[];
|
||||
|
||||
const body: IRequestBody = {
|
||||
TableName: this.getNodeParameter('tableName', i) as string,
|
||||
};
|
||||
|
||||
if (scan) {
|
||||
const filterExpression = this.getNodeParameter('filterExpression', i) as string;
|
||||
if (filterExpression) {
|
||||
body.FilterExpression = filterExpression;
|
||||
}
|
||||
} else {
|
||||
body.KeyConditionExpression = this.getNodeParameter(
|
||||
'keyConditionExpression',
|
||||
i,
|
||||
) as string;
|
||||
}
|
||||
|
||||
const { indexName, projectionExpression, filterExpression } = this.getNodeParameter(
|
||||
'options',
|
||||
i,
|
||||
) as {
|
||||
indexName: string;
|
||||
projectionExpression: string;
|
||||
filterExpression: string;
|
||||
};
|
||||
|
||||
const expressionAttributeName = adjustExpressionAttributeName(eanUi);
|
||||
|
||||
if (Object.keys(expressionAttributeName).length) {
|
||||
body.ExpressionAttributeNames = expressionAttributeName;
|
||||
}
|
||||
|
||||
const expressionAttributeValues = adjustExpressionAttributeValues(eavUi);
|
||||
|
||||
if (Object.keys(expressionAttributeValues).length) {
|
||||
body.ExpressionAttributeValues = expressionAttributeValues;
|
||||
}
|
||||
|
||||
if (indexName) {
|
||||
body.IndexName = indexName;
|
||||
}
|
||||
|
||||
if (projectionExpression && select !== 'COUNT') {
|
||||
body.ProjectionExpression = projectionExpression;
|
||||
}
|
||||
|
||||
if (filterExpression) {
|
||||
body.FilterExpression = filterExpression;
|
||||
}
|
||||
|
||||
if (select) {
|
||||
body.Select = select;
|
||||
}
|
||||
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Amz-Target': scan ? 'DynamoDB_20120810.Scan' : 'DynamoDB_20120810.Query',
|
||||
};
|
||||
|
||||
if (returnAll && select !== 'COUNT') {
|
||||
responseData = await awsApiRequestAllItems.call(
|
||||
this,
|
||||
'dynamodb',
|
||||
'POST',
|
||||
'/',
|
||||
body,
|
||||
headers,
|
||||
);
|
||||
} else {
|
||||
body.Limit = this.getNodeParameter('limit', 0, 1);
|
||||
responseData = await awsApiRequest.call(this, 'dynamodb', 'POST', '/', body, headers);
|
||||
if (select !== 'COUNT') {
|
||||
responseData = responseData.Items;
|
||||
}
|
||||
}
|
||||
if (simple) {
|
||||
responseData = responseData.map(simplify);
|
||||
}
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IWebhookFunctions,
|
||||
IHttpRequestOptions,
|
||||
INodeExecutionData,
|
||||
IHttpRequestMethods,
|
||||
} from 'n8n-workflow';
|
||||
import { ApplicationError, deepCopy } from 'n8n-workflow';
|
||||
|
||||
import type { IRequestBody } from './types';
|
||||
import { getAwsCredentials } from '../GenericFunctions';
|
||||
|
||||
export async function awsApiRequest(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions,
|
||||
service: string,
|
||||
method: IHttpRequestMethods,
|
||||
path: string,
|
||||
body?: object | IRequestBody,
|
||||
headers?: object,
|
||||
): Promise<any> {
|
||||
const { credentials, credentialsType } = await getAwsCredentials(this);
|
||||
const requestOptions = {
|
||||
qs: {
|
||||
service,
|
||||
path,
|
||||
},
|
||||
method,
|
||||
body: JSON.stringify(body),
|
||||
url: '',
|
||||
headers,
|
||||
region: credentials?.region as string,
|
||||
} as IHttpRequestOptions;
|
||||
|
||||
try {
|
||||
return JSON.parse(
|
||||
(await this.helpers.requestWithAuthentication.call(
|
||||
this,
|
||||
credentialsType,
|
||||
requestOptions,
|
||||
)) as string,
|
||||
);
|
||||
} catch (error) {
|
||||
const statusCode = (error.statusCode || error.cause?.statusCode) as number;
|
||||
let errorMessage =
|
||||
error.response?.body?.message || error.response?.body?.Message || error.message;
|
||||
|
||||
if (statusCode === 403) {
|
||||
if (errorMessage === 'The security token included in the request is invalid.') {
|
||||
throw new ApplicationError('The AWS credentials are not valid!', { level: 'warning' });
|
||||
} else if (
|
||||
errorMessage.startsWith(
|
||||
'The request signature we calculated does not match the signature you provided',
|
||||
)
|
||||
) {
|
||||
throw new ApplicationError('The AWS credentials are not valid!', { level: 'warning' });
|
||||
}
|
||||
}
|
||||
|
||||
if (error.cause?.error) {
|
||||
try {
|
||||
errorMessage = JSON.parse(error.cause?.error).message;
|
||||
} catch (ex) {}
|
||||
}
|
||||
|
||||
throw new ApplicationError(`AWS error response [${statusCode}]: ${errorMessage}`, {
|
||||
level: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function awsApiRequestAllItems(
|
||||
this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions,
|
||||
service: string,
|
||||
method: IHttpRequestMethods,
|
||||
path: string,
|
||||
body?: IRequestBody,
|
||||
headers?: object,
|
||||
): Promise<any> {
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
|
||||
do {
|
||||
const originalHeaders = Object.assign({}, headers); //The awsapirequest function adds the hmac signature to the headers, if we pass the modified headers back in on the next call it will fail with invalid signature
|
||||
responseData = await awsApiRequest.call(this, service, method, path, body, originalHeaders);
|
||||
if (responseData.LastEvaluatedKey) {
|
||||
body!.ExclusiveStartKey = responseData.LastEvaluatedKey;
|
||||
}
|
||||
returnData.push(...(responseData.Items as IDataObject[]));
|
||||
} while (responseData.LastEvaluatedKey !== undefined);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export function copyInputItem(item: INodeExecutionData, properties: string[]): IDataObject {
|
||||
// Prepare the data to insert and copy it to be returned
|
||||
const newItem: IDataObject = {};
|
||||
for (const property of properties) {
|
||||
if (item.json[property] === undefined) {
|
||||
newItem[property] = null;
|
||||
} else {
|
||||
newItem[property] = deepCopy(item.json[property]);
|
||||
}
|
||||
}
|
||||
return newItem;
|
||||
}
|
||||
@@ -0,0 +1,888 @@
|
||||
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 or Update',
|
||||
value: 'upsert',
|
||||
description: 'Create a new record, or update the current one if it already exists (upsert)',
|
||||
action: 'Create or update an item',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete an item',
|
||||
action: 'Delete an item',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get an item',
|
||||
action: 'Get an item',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many items',
|
||||
action: 'Get many items',
|
||||
},
|
||||
],
|
||||
default: 'upsert',
|
||||
},
|
||||
];
|
||||
|
||||
export const itemFields: INodeProperties[] = [
|
||||
// ----------------------------------
|
||||
// all
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Table Name or ID',
|
||||
name: 'tableName',
|
||||
description:
|
||||
'Table to operate on. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
||||
type: 'options',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
},
|
||||
},
|
||||
default: [],
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getTables',
|
||||
},
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// upsert
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Data to Send',
|
||||
name: 'dataToSend',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Auto-Map Input Data to Columns',
|
||||
value: 'autoMapInputData',
|
||||
description: 'Use when node input properties match destination column names',
|
||||
},
|
||||
{
|
||||
name: 'Define Below for Each Column',
|
||||
value: 'defineBelow',
|
||||
description: 'Set the value for each destination column',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upsert'],
|
||||
},
|
||||
},
|
||||
default: 'defineBelow',
|
||||
description: 'Whether to insert the input data this node receives in the new row',
|
||||
},
|
||||
{
|
||||
displayName: 'Inputs to Ignore',
|
||||
name: 'inputsToIgnore',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upsert'],
|
||||
dataToSend: ['autoMapInputData'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'List of input properties to avoid sending, separated by commas. Leave empty to send all properties.',
|
||||
placeholder: 'Enter properties...',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields to Send',
|
||||
name: 'fieldsUi',
|
||||
placeholder: 'Add Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValueButtonText: 'Add Field to Send',
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['upsert'],
|
||||
dataToSend: ['defineBelow'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'fieldValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Field ID',
|
||||
name: 'fieldId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Field Value',
|
||||
name: 'fieldValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['upsert'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Expression Attribute Values',
|
||||
name: 'eavUi',
|
||||
description:
|
||||
'Substitution tokens for attribute names in an expression. Only needed when the parameter "condition expression" is set.',
|
||||
placeholder: 'Add Attribute Value',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
required: true,
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
minValue: 1,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'eavValues',
|
||||
displayName: 'Expression Attribute Vaue',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Attribute',
|
||||
name: 'attribute',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Number',
|
||||
value: 'N',
|
||||
},
|
||||
{
|
||||
name: 'String',
|
||||
value: 'S',
|
||||
},
|
||||
],
|
||||
default: 'S',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Condition Expression',
|
||||
name: 'conditionExpression',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'A condition that must be satisfied in order for a conditional upsert to succeed. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">View details</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Expression Attribute Names',
|
||||
name: 'eanUi',
|
||||
placeholder: 'Add Expression',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'eanValues',
|
||||
displayName: 'Expression',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
description:
|
||||
'One or more substitution tokens for attribute names in an expression. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">View details</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// delete
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Return',
|
||||
name: 'returnValues',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Attribute Values',
|
||||
value: 'ALL_OLD',
|
||||
description: 'The content of the old item is returned',
|
||||
},
|
||||
{
|
||||
name: 'Nothing',
|
||||
value: 'NONE',
|
||||
description: 'Nothing is returned',
|
||||
},
|
||||
],
|
||||
default: 'NONE',
|
||||
description:
|
||||
'Use ReturnValues if you want to get the item attributes as they appeared before they were deleted',
|
||||
},
|
||||
{
|
||||
displayName: 'Keys',
|
||||
name: 'keysUi',
|
||||
type: 'fixedCollection',
|
||||
placeholder: 'Add Key',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'keyValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Binary',
|
||||
value: 'B',
|
||||
},
|
||||
{
|
||||
name: 'Number',
|
||||
value: 'N',
|
||||
},
|
||||
{
|
||||
name: 'String',
|
||||
value: 'S',
|
||||
},
|
||||
],
|
||||
default: 'S',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
description:
|
||||
"Item's primary key. For example, with a simple primary key, you only need to provide a value for the partition key. For a composite primary key, you must provide values for both the partition key and the sort key.",
|
||||
},
|
||||
{
|
||||
displayName: 'Simplify',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['delete'],
|
||||
returnValues: ['ALL_OLD'],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'Whether to return a simplified version of the response instead of the raw data',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['delete'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Condition Expression',
|
||||
name: 'conditionExpression',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'A condition that must be satisfied in order for a conditional delete to succeed',
|
||||
},
|
||||
{
|
||||
displayName: 'Expression Attribute Names',
|
||||
name: 'eanUi',
|
||||
placeholder: 'Add Expression',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'eanValues',
|
||||
displayName: 'Expression',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
description:
|
||||
'One or more substitution tokens for attribute names in an expression. Check <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">Info</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Expression Attribute Values',
|
||||
name: 'expressionAttributeUi',
|
||||
description:
|
||||
'Substitution tokens for attribute names in an expression. Only needed when the parameter "condition expression" is set.',
|
||||
placeholder: 'Add Attribute Value',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
required: true,
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
minValue: 1,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'expressionAttributeValues',
|
||||
displayName: 'Expression Attribute Value',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Attribute',
|
||||
name: 'attribute',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Number',
|
||||
value: 'N',
|
||||
},
|
||||
{
|
||||
name: 'String',
|
||||
value: 'S',
|
||||
},
|
||||
],
|
||||
default: 'S',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// get
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Select',
|
||||
name: 'select',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'All Attributes',
|
||||
value: 'ALL_ATTRIBUTES',
|
||||
},
|
||||
{
|
||||
name: 'All Projected Attributes',
|
||||
value: 'ALL_PROJECTED_ATTRIBUTES',
|
||||
},
|
||||
{
|
||||
name: 'Specific Attributes',
|
||||
value: 'SPECIFIC_ATTRIBUTES',
|
||||
description: 'Select them in Attributes to Select under Additional Fields',
|
||||
},
|
||||
],
|
||||
default: 'ALL_ATTRIBUTES',
|
||||
},
|
||||
{
|
||||
displayName: 'Simplify',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['get'],
|
||||
select: ['ALL_PROJECTED_ATTRIBUTES', 'ALL_ATTRIBUTES'],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'Whether to return a simplified version of the response instead of the raw data',
|
||||
},
|
||||
{
|
||||
displayName: 'Keys',
|
||||
name: 'keysUi',
|
||||
type: 'fixedCollection',
|
||||
placeholder: 'Add Key',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'keyValues',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Binary',
|
||||
value: 'B',
|
||||
},
|
||||
{
|
||||
name: 'Number',
|
||||
value: 'N',
|
||||
},
|
||||
{
|
||||
name: 'String',
|
||||
value: 'S',
|
||||
},
|
||||
],
|
||||
default: 'S',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
description:
|
||||
"Item's primary key. For example, with a simple primary key, you only need to provide a value for the partition key. For a composite primary key, you must provide values for both the partition key and the sort key.",
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Attributes to Select',
|
||||
name: 'projectionExpression',
|
||||
type: 'string',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
|
||||
placeholder: 'id, name',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Expression Attribute Names',
|
||||
name: 'eanUi',
|
||||
placeholder: 'Add Expression',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'eanValues',
|
||||
displayName: 'Expression',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
description:
|
||||
'One or more substitution tokens for attribute names in an expression. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">View details</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Read Type',
|
||||
name: 'readType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Strongly Consistent Read',
|
||||
value: 'stronglyConsistentRead',
|
||||
},
|
||||
{
|
||||
name: 'Eventually Consistent Read',
|
||||
value: 'eventuallyConsistentRead',
|
||||
},
|
||||
],
|
||||
default: 'eventuallyConsistentRead',
|
||||
description:
|
||||
'Type of read to perform on the table. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html">View details</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
// Get All
|
||||
// ----------------------------------
|
||||
{
|
||||
displayName: 'Scan',
|
||||
name: 'scan',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description:
|
||||
'Whether to do an scan or query. Check <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-query-scan.html" >differences</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filter Expression',
|
||||
name: 'filterExpression',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
scan: [true],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'A filter expression determines which items within the Scan results should be returned to you. All of the other results are discarded. Empty value will return all Scan results.',
|
||||
},
|
||||
{
|
||||
displayName: 'Key Condition Expression',
|
||||
name: 'keyConditionExpression',
|
||||
description:
|
||||
'Condition to determine the items to be retrieved. The condition must perform an equality test on a single partition key value, in this format: <code>partitionKeyName = :partitionkeyval</code>',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-placeholder-miscased-id
|
||||
placeholder: 'id = :id',
|
||||
default: '',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['getAll'],
|
||||
scan: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Expression Attribute Values',
|
||||
name: 'eavUi',
|
||||
description: 'Substitution tokens for attribute names in an expression',
|
||||
placeholder: 'Add Attribute Value',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
required: true,
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
minValue: 1,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'eavValues',
|
||||
displayName: 'Expression Attribute Vaue',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Attribute',
|
||||
name: 'attribute',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Number',
|
||||
value: 'N',
|
||||
},
|
||||
{
|
||||
name: 'String',
|
||||
value: 'S',
|
||||
},
|
||||
],
|
||||
default: 'S',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Select',
|
||||
name: 'select',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'All Attributes',
|
||||
value: 'ALL_ATTRIBUTES',
|
||||
},
|
||||
{
|
||||
name: 'All Projected Attributes',
|
||||
value: 'ALL_PROJECTED_ATTRIBUTES',
|
||||
},
|
||||
{
|
||||
name: 'Count',
|
||||
value: 'COUNT',
|
||||
},
|
||||
{
|
||||
name: 'Specific Attributes',
|
||||
value: 'SPECIFIC_ATTRIBUTES',
|
||||
description: 'Select them in Attributes to Select under Additional Fields',
|
||||
},
|
||||
],
|
||||
default: 'ALL_ATTRIBUTES',
|
||||
},
|
||||
{
|
||||
displayName: 'Simplify',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['getAll'],
|
||||
select: ['ALL_PROJECTED_ATTRIBUTES', 'ALL_ATTRIBUTES', 'SPECIFIC_ATTRIBUTES'],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'Whether to return a simplified version of the response instead of the raw data',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['item'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Index Name',
|
||||
name: 'indexName',
|
||||
description:
|
||||
'Name of the index to query. It can be any secondary local or global index on the table.',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Attributes to Select',
|
||||
name: 'projectionExpression',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'Text that identifies one or more attributes to retrieve from the table. These attributes can include scalars, sets, or elements of a JSON document. The attributes in the expression must be separated by commas.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filter Expression',
|
||||
name: 'filterExpression',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/scan': [false],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description:
|
||||
'Text that contains conditions that DynamoDB applies after the Query operation, but before the data is returned. Items that do not satisfy the FilterExpression criteria are not returned.',
|
||||
},
|
||||
{
|
||||
displayName: 'Expression Attribute Names',
|
||||
name: 'eanUi',
|
||||
placeholder: 'Add Expression',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'eanValues',
|
||||
displayName: 'Expression',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
description:
|
||||
'One or more substitution tokens for attribute names in an expression. Check <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">Info</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 3
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../__tests__/credentials';
|
||||
|
||||
describe('AWS DynamoDB Node', () => {
|
||||
const dynamoDbNock = nock('https://dynamodb.eu-central-1.amazonaws.com');
|
||||
|
||||
beforeAll(() => {
|
||||
dynamoDbNock
|
||||
.post('/', {
|
||||
TableName: 'n8n-testing',
|
||||
KeyConditionExpression: 'id = :idVal',
|
||||
ExpressionAttributeValues: {
|
||||
':idVal': { S: 'foo' },
|
||||
},
|
||||
ProjectionExpression: '#time',
|
||||
ExpressionAttributeNames: {
|
||||
'#time': 'timestamp',
|
||||
},
|
||||
Limit: 50,
|
||||
Select: 'SPECIFIC_ATTRIBUTES',
|
||||
})
|
||||
.reply(200, {
|
||||
Items: [
|
||||
{
|
||||
timestamp: { S: '2025-01-01' },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
dynamoDbNock
|
||||
.post(
|
||||
'/',
|
||||
(body) =>
|
||||
body?.TableName === 'n8n-testing' &&
|
||||
body?.Key?.id?.S === 'foo' &&
|
||||
body?.Key?.timestamp?.S === '2025-01-01' &&
|
||||
body?.ExpressionAttributeNames?.['#time'] === 'timestamp' &&
|
||||
body?.ProjectionExpression === '#time',
|
||||
)
|
||||
.reply(200, {
|
||||
Item: {
|
||||
timestamp: { S: '2025-01-01' },
|
||||
},
|
||||
});
|
||||
|
||||
dynamoDbNock
|
||||
.post(
|
||||
'/',
|
||||
(body) =>
|
||||
body?.TableName === 'n8n-testing' &&
|
||||
body?.Item?.id?.S === 'foo' &&
|
||||
body?.Item?.timestamp?.S === '2025-01-01' &&
|
||||
body?.Item?.data?.S === 'payload' &&
|
||||
body?.ConditionExpression === '#d = :v' &&
|
||||
body?.ExpressionAttributeNames?.['#d'] === 'data' &&
|
||||
body?.ExpressionAttributeValues?.[':v']?.S === 'lorem ipsum',
|
||||
)
|
||||
.reply(200, {});
|
||||
|
||||
dynamoDbNock
|
||||
.post(
|
||||
'/',
|
||||
(body) =>
|
||||
body?.TableName === 'n8n-testing' &&
|
||||
body?.Key?.id?.S === 'foo' &&
|
||||
body?.Key?.timestamp?.S === '2025-01-01' &&
|
||||
body?.ConditionExpression === '#d = :v' &&
|
||||
body?.ExpressionAttributeNames?.['#d'] === 'data' &&
|
||||
body?.ExpressionAttributeValues?.[':v']?.S === 'payload',
|
||||
)
|
||||
.reply(200, {});
|
||||
});
|
||||
|
||||
afterAll(() => dynamoDbNock.done());
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,261 @@
|
||||
{
|
||||
"name": "AWS DynamoDB",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, -40],
|
||||
"id": "efd3f04e-8695-4f2c-8f8d-0b0166390d3f",
|
||||
"name": "When clicking ‘Execute workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"tableName": "n8n-testing",
|
||||
"keyConditionExpression": "id = :idVal",
|
||||
"eavUi": {
|
||||
"eavValues": [
|
||||
{
|
||||
"attribute": ":idVal",
|
||||
"value": "foo"
|
||||
}
|
||||
]
|
||||
},
|
||||
"select": "SPECIFIC_ATTRIBUTES",
|
||||
"options": {
|
||||
"projectionExpression": "#time",
|
||||
"eanUi": {
|
||||
"eanValues": [
|
||||
{
|
||||
"key": "#time",
|
||||
"value": "timestamp"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.awsDynamoDb",
|
||||
"typeVersion": 1,
|
||||
"position": [220, -340],
|
||||
"id": "fe8a7fbf-83af-45bb-ae58-77f143ce2f30",
|
||||
"name": "Get many items",
|
||||
"credentials": {
|
||||
"aws": {
|
||||
"id": "UamouRLgIHQY0AXg",
|
||||
"name": "AWS account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "get",
|
||||
"tableName": "n8n-testing",
|
||||
"keysUi": {
|
||||
"keyValues": [
|
||||
{
|
||||
"key": "id",
|
||||
"value": "foo"
|
||||
},
|
||||
{
|
||||
"key": "timestamp",
|
||||
"value": "2025-01-01"
|
||||
}
|
||||
]
|
||||
},
|
||||
"additionalFields": {
|
||||
"projectionExpression": "#time",
|
||||
"eanUi": {
|
||||
"eanValues": [
|
||||
{
|
||||
"key": "#time",
|
||||
"value": "timestamp"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.awsDynamoDb",
|
||||
"typeVersion": 1,
|
||||
"position": [220, -140],
|
||||
"id": "4cbddd91-c851-4ef6-b76a-d84d84f1d598",
|
||||
"name": "Get an item",
|
||||
"credentials": {
|
||||
"aws": {
|
||||
"id": "UamouRLgIHQY0AXg",
|
||||
"name": "AWS account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"tableName": "n8n-testing",
|
||||
"fieldsUi": {
|
||||
"fieldValues": [
|
||||
{
|
||||
"fieldId": "id",
|
||||
"fieldValue": "foo"
|
||||
},
|
||||
{
|
||||
"fieldId": "timestamp",
|
||||
"fieldValue": "2025-01-01"
|
||||
},
|
||||
{
|
||||
"fieldId": "data",
|
||||
"fieldValue": "payload"
|
||||
}
|
||||
]
|
||||
},
|
||||
"additionalFields": {
|
||||
"eavUi": {
|
||||
"eavValues": [
|
||||
{
|
||||
"attribute": ":v",
|
||||
"value": "lorem ipsum"
|
||||
}
|
||||
]
|
||||
},
|
||||
"conditionExpression": "#d = :v",
|
||||
"eanUi": {
|
||||
"eanValues": [
|
||||
{
|
||||
"key": "#d",
|
||||
"value": "data"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.awsDynamoDb",
|
||||
"typeVersion": 1,
|
||||
"position": [220, 60],
|
||||
"id": "17eee2d1-6547-4d99-b168-74d407cc252c",
|
||||
"name": "Create or update an item",
|
||||
"credentials": {
|
||||
"aws": {
|
||||
"id": "UamouRLgIHQY0AXg",
|
||||
"name": "AWS account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "delete",
|
||||
"tableName": "n8n-testing",
|
||||
"keysUi": {
|
||||
"keyValues": [
|
||||
{
|
||||
"key": "id",
|
||||
"value": "foo"
|
||||
},
|
||||
{
|
||||
"key": "timestamp",
|
||||
"value": "2025-01-01"
|
||||
}
|
||||
]
|
||||
},
|
||||
"additionalFields": {
|
||||
"conditionExpression": "#d = :v",
|
||||
"eanUi": {
|
||||
"eanValues": [
|
||||
{
|
||||
"key": "#d",
|
||||
"value": "data"
|
||||
}
|
||||
]
|
||||
},
|
||||
"expressionAttributeUi": {
|
||||
"expressionAttributeValues": [
|
||||
{
|
||||
"attribute": ":v",
|
||||
"value": "payload"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.awsDynamoDb",
|
||||
"typeVersion": 1,
|
||||
"position": [220, 260],
|
||||
"id": "6d266500-16b0-4ee9-a3b2-a4300bc23b1f",
|
||||
"name": "Delete an item",
|
||||
"credentials": {
|
||||
"aws": {
|
||||
"id": "UamouRLgIHQY0AXg",
|
||||
"name": "AWS account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Get many items": [
|
||||
{
|
||||
"json": {
|
||||
"timestamp": "2025-01-01"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Get an item": [
|
||||
{
|
||||
"json": {
|
||||
"timestamp": "2025-01-01"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Create or update an item": [
|
||||
{
|
||||
"json": {
|
||||
"id": "foo",
|
||||
"timestamp": "2025-01-01",
|
||||
"data": "payload"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Delete an item": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get many items",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Get an item",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Create or update an item",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Delete an item",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "ada45c19-f09e-43e5-8e5c-d66f54f78d4f",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "e115be144a6a5547dbfca93e774dfffa178aa94a181854c13e2ce5e14d195b2e"
|
||||
},
|
||||
"id": "Ffm7CA1AIhKGiOlK",
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" viewBox="-40 -35 340 340"><path fill="#5294CF" d="M165.258 288.501h3.508l57.261-28.634.953-1.347V29.964l-.953-1.354L168.766 0h-3.551z"/><path fill="#1F5B98" d="M90.741 288.501h-3.557l-57.212-28.634-1.161-1.997-.589-226.742 1.75-2.518L87.184 0h3.601z"/><path fill="#2D72B8" d="M87.285 0h81.426v288.501H87.285z"/><path fill="#1A476F" d="m256 137.769-1.935-.429-27.628-2.576-.41.204-57.312-2.292h-81.43l-57.313 2.292V91.264l-.06.032.06-.128 57.313-13.28h81.43l57.312 13.28 21.069 11.199v-7.2l8.904-.974-.922-1.798-28.192-20.159-.859.279-57.312-17.759h-81.43L29.972 72.515V28.61L0 63.723v30.666l.232-.168 8.672.946v7.348L0 107.28v30.513l.232-.024 8.672.128v12.807l-7.482.112L0 150.68v30.525l8.904 4.788v7.433l-8.531.942-.373-.28v30.661l29.972 35.118v-43.901l57.313 17.759h81.43l57.481-17.811.764.335 27.821-19.862 1.219-1.979-8.904-.982v-7.284l-1.167-.466-19.043 10.265-.69 1.44-57.481 13.203v.016h-81.43v-.016l-57.313-13.259v-43.864l57.313 2.284v.056h81.43l57.312-2.34 1.305.6 26.779-2.306 1.889-.923-8.904-.128v-12.807z"/><path fill="#2D72B8" d="M226.027 215.966v43.901L256 224.749v-30.461l-29.8 21.626zm0-18.545.173-.04 29.8-16.028v-30.649l-29.973 2.757zm.173-106.213-.173-.04v43.8L256 137.769v-30.634zm0-18.521L256 94.193V63.731L226.027 28.61v43.905l.173.06z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,90 @@
|
||||
export interface IRequestBody {
|
||||
[key: string]: string | IAttributeValue | undefined | boolean | object | number;
|
||||
TableName: string;
|
||||
Key?: object;
|
||||
IndexName?: string;
|
||||
ProjectionExpression?: string;
|
||||
KeyConditionExpression?: string;
|
||||
ExpressionAttributeValues?: IAttributeValue;
|
||||
ConsistentRead?: boolean;
|
||||
FilterExpression?: string;
|
||||
Limit?: number;
|
||||
ExclusiveStartKey?: IAttributeValue;
|
||||
}
|
||||
|
||||
export interface IAttributeValue {
|
||||
[attribute: string]: IAttributeValueValue;
|
||||
}
|
||||
|
||||
export interface IAttributeValueValue {
|
||||
[type: string]: string | string[] | IAttributeValue[];
|
||||
}
|
||||
|
||||
export interface IAttributeValueUi {
|
||||
attribute: string;
|
||||
type: AttributeValueType;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IAttributeNameUi {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type AttributeValueType =
|
||||
| 'B' // binary
|
||||
| 'BOOL' // boolean
|
||||
| 'BS' // binary set
|
||||
| 'L' // list
|
||||
| 'M' // map
|
||||
| 'N' // number
|
||||
| 'NULL'
|
||||
| 'NS' // number set
|
||||
| 'S' // string
|
||||
| 'SS'; // string set
|
||||
|
||||
export type PartitionKey = {
|
||||
details: {
|
||||
name: string;
|
||||
type: string;
|
||||
value: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const EAttributeValueTypes = {
|
||||
S: 'S',
|
||||
SS: 'SS',
|
||||
M: 'M',
|
||||
L: 'L',
|
||||
NS: 'NS',
|
||||
N: 'N',
|
||||
BOOL: 'BOOL',
|
||||
B: 'B',
|
||||
BS: 'BS',
|
||||
NULL: 'NULL',
|
||||
} as const;
|
||||
|
||||
export type EAttributeValueType = (typeof EAttributeValueTypes)[keyof typeof EAttributeValueTypes];
|
||||
|
||||
export interface IExpressionAttributeValue {
|
||||
attribute: string;
|
||||
type: EAttributeValueType;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type FieldsUiValues = Array<{
|
||||
fieldId: string;
|
||||
fieldValue: string;
|
||||
}>;
|
||||
|
||||
export type PutItemUi = {
|
||||
attribute: string;
|
||||
type: 'S' | 'N';
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type AdjustedPutItem = {
|
||||
[attribute: string]: {
|
||||
[type: string]: string;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,136 @@
|
||||
import type { IDataObject, INodeExecutionData } from 'n8n-workflow';
|
||||
import { deepCopy, assert, ApplicationError } from 'n8n-workflow';
|
||||
|
||||
import type {
|
||||
AdjustedPutItem,
|
||||
AttributeValueType,
|
||||
EAttributeValueType,
|
||||
IAttributeNameUi,
|
||||
IAttributeValue,
|
||||
IAttributeValueUi,
|
||||
IAttributeValueValue,
|
||||
PutItemUi,
|
||||
} from './types';
|
||||
|
||||
const addColon = (attribute: string) =>
|
||||
(attribute = attribute.charAt(0) === ':' ? attribute : `:${attribute}`);
|
||||
|
||||
const addPound = (key: string) => (key = key.charAt(0) === '#' ? key : `#${key}`);
|
||||
|
||||
export function adjustExpressionAttributeValues(eavUi: IAttributeValueUi[]) {
|
||||
const eav: IAttributeValue = {};
|
||||
|
||||
eavUi.forEach(({ attribute, type, value }) => {
|
||||
eav[addColon(attribute)] = { [type]: value } as IAttributeValueValue;
|
||||
});
|
||||
|
||||
return eav;
|
||||
}
|
||||
|
||||
export function adjustExpressionAttributeName(eanUi: IAttributeNameUi[]) {
|
||||
const ean: { [key: string]: string } = {};
|
||||
|
||||
eanUi.forEach(({ key, value }) => {
|
||||
ean[addPound(key)] = value;
|
||||
});
|
||||
|
||||
return ean;
|
||||
}
|
||||
|
||||
export function adjustPutItem(putItemUi: PutItemUi) {
|
||||
const adjustedPutItem: AdjustedPutItem = {};
|
||||
|
||||
Object.entries(putItemUi).forEach(([attribute, value]) => {
|
||||
let type: string;
|
||||
|
||||
if (typeof value === 'boolean') {
|
||||
type = 'BOOL';
|
||||
} else if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
|
||||
type = 'M';
|
||||
} else if (isNaN(Number(value))) {
|
||||
type = 'S';
|
||||
} else {
|
||||
type = 'N';
|
||||
}
|
||||
|
||||
adjustedPutItem[attribute] = { [type]: value.toString() };
|
||||
});
|
||||
|
||||
return adjustedPutItem;
|
||||
}
|
||||
|
||||
export function simplify(item: IAttributeValue): IDataObject {
|
||||
const output: IDataObject = {};
|
||||
|
||||
for (const [attribute, value] of Object.entries(item)) {
|
||||
const [type, content] = Object.entries(value)[0] as [AttributeValueType, string];
|
||||
//nedded as simplify is used in decodeItem
|
||||
|
||||
output[attribute] = decodeAttribute(type, content);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function decodeAttribute(type: AttributeValueType, attribute: string | IAttributeValue) {
|
||||
switch (type) {
|
||||
case 'BOOL':
|
||||
return Boolean(attribute);
|
||||
case 'N':
|
||||
return Number(attribute);
|
||||
case 'S':
|
||||
return String(attribute);
|
||||
case 'SS':
|
||||
case 'NS':
|
||||
return attribute;
|
||||
case 'M':
|
||||
assert(
|
||||
typeof attribute === 'object' && !Array.isArray(attribute) && attribute !== null,
|
||||
'Attribute must be an object',
|
||||
);
|
||||
return simplify(attribute);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function validateJSON(input: any): object {
|
||||
try {
|
||||
return JSON.parse(input as string);
|
||||
} catch (error) {
|
||||
throw new ApplicationError('Items must be a valid JSON', { level: 'warning' });
|
||||
}
|
||||
}
|
||||
|
||||
export function copyInputItem(item: INodeExecutionData, properties: string[]): IDataObject {
|
||||
// Prepare the data to insert and copy it to be returned
|
||||
const newItem: IDataObject = {};
|
||||
for (const property of properties) {
|
||||
if (item.json[property] === undefined) {
|
||||
newItem[property] = null;
|
||||
} else {
|
||||
newItem[property] = deepCopy(item.json[property]);
|
||||
}
|
||||
}
|
||||
return newItem;
|
||||
}
|
||||
|
||||
export function mapToAttributeValues(item: IDataObject): void {
|
||||
for (const key of Object.keys(item)) {
|
||||
if (!key.startsWith(':')) {
|
||||
item[`:${key}`] = item[key];
|
||||
delete item[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function decodeItem(item: IAttributeValue): IDataObject {
|
||||
const _item: IDataObject = {};
|
||||
for (const entry of Object.entries(item)) {
|
||||
const [attribute, value]: [string, object] = entry;
|
||||
const [type, content]: [string, object] = Object.entries(value)[0];
|
||||
_item[attribute] = decodeAttribute(type as EAttributeValueType, content as unknown as string);
|
||||
}
|
||||
|
||||
return _item;
|
||||
}
|
||||
Reference in New Issue
Block a user