diff --git a/marketplace/plugins/pinecone/.gitignore b/marketplace/plugins/pinecone/.gitignore new file mode 100644 index 0000000000..23e6609462 --- /dev/null +++ b/marketplace/plugins/pinecone/.gitignore @@ -0,0 +1,5 @@ +node_modules +lib/*.d.* +lib/*.js +lib/*.js.map +dist/* \ No newline at end of file diff --git a/marketplace/plugins/pinecone/README.md b/marketplace/plugins/pinecone/README.md new file mode 100644 index 0000000000..0feecc5e21 --- /dev/null +++ b/marketplace/plugins/pinecone/README.md @@ -0,0 +1,4 @@ + +# Pinecone + +Documentation on: https://docs.tooljet.com/docs/data-sources/pinecone \ No newline at end of file diff --git a/marketplace/plugins/pinecone/__tests__/index.js b/marketplace/plugins/pinecone/__tests__/index.js new file mode 100644 index 0000000000..056582a50e --- /dev/null +++ b/marketplace/plugins/pinecone/__tests__/index.js @@ -0,0 +1,7 @@ +'use strict'; + +const pinecone = require('../lib'); + +describe('pinecone', () => { + it.todo('needs tests'); +}); diff --git a/marketplace/plugins/pinecone/lib/icon.svg b/marketplace/plugins/pinecone/lib/icon.svg new file mode 100644 index 0000000000..2bddce89fd --- /dev/null +++ b/marketplace/plugins/pinecone/lib/icon.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/marketplace/plugins/pinecone/lib/index.ts b/marketplace/plugins/pinecone/lib/index.ts new file mode 100644 index 0000000000..a3eaa07e0b --- /dev/null +++ b/marketplace/plugins/pinecone/lib/index.ts @@ -0,0 +1,91 @@ +import { QueryError, QueryResult, QueryService, ConnectionTestResult } from '@tooljet-marketplace/common'; +import { SourceOptions, QueryOptions, Operation } from './types'; +import { + getIndexStats, + listVectorIds, + fetchVectors, + upsertVectors, + updateVector, + deleteVectors, + quertVectors, +} from './query_operations'; +import { Pinecone } from '@pinecone-database/pinecone'; + +export default class PineconeService implements QueryService { + // Function to run the specified operation + async run(sourceOptions: SourceOptions, queryOptions: QueryOptions, dataSourceId: string): Promise { + const operation = queryOptions.operation; + const pinecone = await this.getConnection(sourceOptions); + let result = {}; + + try { + switch (operation) { + case Operation.GetIndexStats: + result = await getIndexStats(pinecone, queryOptions); + break; + case Operation.ListVectorIds: + result = await listVectorIds(pinecone, queryOptions); + break; + case Operation.FetchVectors: + result = await fetchVectors(pinecone, queryOptions); + break; + case Operation.UpsertVectors: + result = await upsertVectors(pinecone, queryOptions); + break; + case Operation.UpdateVector: + result = await updateVector(pinecone, queryOptions); + break; + case Operation.DeleteVectors: + result = await deleteVectors(pinecone, queryOptions); + break; + case Operation.QueryVectors: + result = await quertVectors(pinecone, queryOptions); + break; + default: + throw new QueryError('Query could not be completed', 'Invalid operation', {}); + } + } catch (error) { + throw new QueryError('Query could not be completed', error?.message, {}); + } + + return { + status: 'ok', + data: result, + }; + } + + // Function to test the Pinecone connection + async testConnection(sourceOptions: SourceOptions): Promise { + const pinecone = await this.getConnection(sourceOptions); + + try { + const indexes = await pinecone.listIndexes(); + console.log('Indexes fetched:', indexes); + + if (indexes.indexes.length > 0) { + console.log('Connection successful, indexes found'); + return { status: 'ok' }; + } else { + console.error('No indexes found'); + throw new QueryError('No indexes found', 'The index list is empty', {}); + } + } catch (error) { + console.error('Connection could not be established:', error.message); + throw new QueryError('Connection could not be established', error?.message, {}); + } + } + + async getConnection(sourceOptions: SourceOptions): Promise { + const { apiKey } = sourceOptions; + + if (!apiKey) { + throw new QueryError('API key missing', 'No API key provided in source options', {}); + } + + const pinecone = new Pinecone({ + apiKey: apiKey, + }); + + return pinecone; + } +} diff --git a/marketplace/plugins/pinecone/lib/manifest.json b/marketplace/plugins/pinecone/lib/manifest.json new file mode 100644 index 0000000000..0f87759578 --- /dev/null +++ b/marketplace/plugins/pinecone/lib/manifest.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/manifest.schema.json", + "title": "Pinecone Plugin", + "description": "A schema defining Pinecone datasource", + "type": "api", + "source": { + "name": "Pinecone", + "kind": "pinecone", + "exposedVariables": { + "isLoading": false, + "data": {}, + "rawData": {} + }, + "options": { + "apiKey": { + "type": "string", + "encrypted": true + } + } + }, + "defaults": {}, + "properties": { + "apiKey": { + "label": "API Key", + "key": "apiKey", + "type": "password", + "description": "Enter your Pinecone API Key", + "helpText": "For generating API Key, visit: Pinecone Console" + } + }, + "required": [ + "apiKey" + ] +} diff --git a/marketplace/plugins/pinecone/lib/operations.json b/marketplace/plugins/pinecone/lib/operations.json new file mode 100644 index 0000000000..b5d3a4e3c9 --- /dev/null +++ b/marketplace/plugins/pinecone/lib/operations.json @@ -0,0 +1,294 @@ +{ + "$schema": "https://raw.githubusercontent.com/ToolJet/ToolJet/develop/plugins/schemas/operations.schema.json", + "title": "Pinecone Datasource", + "description": "A schema defining Pinecone datasource", + "type": "database", + "defaults": {}, + "properties": { + "index": { + "label": "Index", + "key": "index", + "type": "text", + "description": "Enter the index name (e.g., example-index)", + "placeholder": "example-index", + "height": "36px" + }, + "operation": { + "label": "Operation", + "key": "operation", + "type": "dropdown-component-flip", + "description": "Select an operation", + "list": [ + { "value": "get_index_stats", "name": "Get Index Stats" }, + { "value": "list_vector_ids", "name": "List Vector IDs" }, + { "value": "fetch_vectors", "name": "Fetch Vectors" }, + { "value": "upsert_vectors", "name": "Upsert Vectors" }, + { "value": "update_vector", "name": "Update a Vector" }, + { "value": "delete_vectors", "name": "Delete Vectors" }, + { "value": "query_vector", "name": "Query Vectors" } + ] + }, + "get_index_stats": { + "index": { + "label": "Index", + "key": "index", + "type": "codehinter", + "description": "Enter the index name (e.g., example-index)", + "placeholder": "example-index", + "height": "36px" + } + }, + "list_vector_ids": { + "index": { + "label": "Index", + "key": "index", + "type": "codehinter", + "description": "Enter the index name (e.g., example-index)", + "placeholder": "example-index", + "height": "36px" + }, + "prefix": { + "label": "Prefix", + "key": "prefix", + "type": "codehinter", + "description": "Enter a prefix to filter vector IDs", + "placeholder": "document1#", + "height": "36px" + }, + "limit": { + "label": "Limit", + "key": "limit", + "type": "codehinter", + "description": "Enter the maximum number of vector IDs to return", + "placeholder": "100", + "height": "36px" + }, + "paginationToken": { + "label": "Pagination Token", + "key": "paginationToken", + "type": "codehinter", + "description": "Enter next token for next set of vector IDs", + "placeholder": "Tm90aGluzYB0byBZzWUGaGVyZQo=", + "height": "36px" + }, + "namespace": { + "label": "Namespace", + "key": "namespace", + "type": "codehinter", + "description": "Enter the namespace (optional)", + "placeholder": "example-namespace", + "height": "36px" + } + }, + "fetch_vectors": { + "index": { + "label": "Index", + "key": "index", + "type": "codehinter", + "description": "Enter the index name (e.g., example-index)", + "placeholder": "example-index", + "height": "36px" + }, + "ids": { + "label": "IDs", + "key": "ids", + "type": "codehinter", + "description": "Enter vector IDs as JSON array", + "placeholder": "[\"id-1\", \"id-2\"]", + "height": "36px" + }, + "namespace": { + "label": "Namespace", + "key": "namespace", + "type": "codehinter", + "description": "Enter the namespace (optional)", + "placeholder": "example-namespace", + "height": "36px" + } + }, + "upsert_vectors": { + "index": { + "label": "Index", + "key": "index", + "type": "codehinter", + "description": "Enter the index name (e.g., example-index)", + "placeholder": "example-index", + "height": "36px" + }, + "vectors": { + "label": "Vectors", + "key": "vectors", + "type": "codehinter", + "description": "Enter vectors as JSON array", + "placeholder": "[{\"id\": \"vec1\", \"values\": [0.1, 0.2, 0.3]}]", + "height": "36px" + }, + "namespace": { + "label": "Namespace", + "key": "namespace", + "type": "codehinter", + "description": "Enter the namespace (optional)", + "placeholder": "example-namespace", + "height": "36px" + } + }, + "update_vector": { + "index": { + "label": "Index", + "key": "index", + "type": "codehinter", + "description": "Enter the index name (e.g., example-index)", + "placeholder": "example-index", + "height": "36px" + }, + "id": { + "label": "ID", + "key": "id", + "type": "codehinter", + "description": "Enter vector ID to update", + "placeholder": "id-3", + "height": "36px" + }, + "values": { + "label": "Values", + "key": "values", + "type": "codehinter", + "description": "Enter updated vector values as JSON array", + "placeholder": "[4.0, 2.0]", + "height": "36px" + }, + "sparse_vector": { + "label": "Sparse Vector", + "key": "sparse_vector", + "type": "codehinter", + "description": "Enter sparse vector values", + "placeholder": "{\"indices\": [1, 5], \"values\": [0.5, 0.5]}", + "height": "36px" + }, + "metadata": { + "label": "Metadata", + "key": "metadata", + "type": "codehinter", + "description": "Enter metadata", + "placeholder": "{\"genre\": \"comedy\"}", + "height": "36px" + }, + "namespace": { + "label": "Namespace", + "key": "namespace", + "type": "codehinter", + "description": "Enter the namespace (optional)", + "placeholder": "example-namespace", + "height": "36px" + } + }, + "delete_vectors": { + "index": { + "label": "Index", + "key": "index", + "type": "codehinter", + "description": "Enter the index name (e.g., example-index)", + "placeholder": "example-index", + "height": "36px" + }, + "ids": { + "label": "ID", + "key": "id", + "type": "codehinter", + "description": "Enter vector IDs as JSON array", + "placeholder": "[\"id-1\", \"id-2\"]", + "height": "36px" + }, + "delete_all": { + "label": "Delete All", + "key": "delete_all", + "type": "codehinter", + "description": "Set true to delete all vectors", + "placeholder": "true (false by default)", + "height":"36px" + }, + "namespace": { + "label": "Namespace", + "key": "namespace", + "type": "codehinter", + "description": "Enter the namespace (optional)", + "placeholder": "example-namespace", + "height": "36px" + }, + "filter": { + "label": "Filter", + "key": "filter", + "type": "codehinter", + "description": "Enter a filter query in JSON format", + "placeholder": "{\"genre\": {\"$in\": [\"documentary\", \"action\"]}}", + "height": "150px" + } + }, + "query_vector": { + "index": { + "label": "Index", + "key": "index", + "type": "codehinter", + "description": "Enter the index name (e.g., example-index)", + "placeholder": "example-index", + "height": "36px" + }, + "namespace": { + "label": "Namespace", + "key": "namespace", + "type": "codehinter", + "description": "Enter the namespace (optional)", + "placeholder": "example-namespace", + "height": "36px" + }, + "top_k": { + "label": "Top K", + "key": "top_k", + "type": "codehinter", + "description": "Enter the number", + "placeholder": "3", + "height": "36px" + }, + "filter": { + "label": "Filter", + "key": "filter", + "type": "codehinter", + "description": "Enter a filter query in JSON format", + "placeholder": "{\"genre\": {\"$in\": [\"documentary\", \"action\"]}}", + "height": "150px" + }, + "include_values": { + "label": "Include values", + "key": "include_values", + "type": "codehinter", + "description": "Enter boolean values", + "placeholder": "true (false by default)", + "height": "36px" + }, + "include_metadata": { + "label": "Include metadata", + "key": "include_metadata", + "type": "codehinter", + "description": "Enter boolean values", + "placeholder": "true (false by default)", + "height": "36px" + }, + "vectors": { + "label": "Vector", + "key": "vectors", + "type": "codehinter", + "description": "Enter vector IDs as JSON array", + "placeholder": "[\"0.3\", \"0.3\", \"0.3\", \"0.3\", \"0.3\"]", + "height": "36px" + }, + "sparse_vector": { + "label": "Sparse Vector", + "key": "sparse_vector", + "type": "codehinter", + "description": "Enter sparse vector values", + "placeholder": "{\"indices\": [1, 5], \"values\": [0.5, 0.5]}", + "height": "36px" + } + } + } +} diff --git a/marketplace/plugins/pinecone/lib/query_operations.ts b/marketplace/plugins/pinecone/lib/query_operations.ts new file mode 100644 index 0000000000..1e12733f21 --- /dev/null +++ b/marketplace/plugins/pinecone/lib/query_operations.ts @@ -0,0 +1,181 @@ +import { QueryOptions } from './types'; +import { Pinecone } from '@pinecone-database/pinecone'; + +export async function getIndexStats(pinecone: Pinecone, options: QueryOptions): Promise { + const { index } = options; + + if (!index) { + throw new Error('Index name is required'); + } + + try { + const indexClient = pinecone.index(index); + const stats = await indexClient.describeIndexStats(); + + return stats; + } catch (error) { + console.error('Error fetching index stats:', error); + throw new Error(error?.message || 'An unexpected error occurred'); + } +} + +export async function listVectorIds(pinecone: Pinecone, options: QueryOptions): Promise { + const { index, prefix, limit, paginationToken, namespace } = options; + + if (!index) { + throw new Error('Index name is required'); + } + + try { + const indexClient = pinecone.index(index); + + const listOptions = { + prefix: prefix, + limit: limit || 10, + paginationToken: paginationToken, + }; + + const client = namespace ? indexClient.namespace(namespace) : indexClient; + + const vectors = await client.listPaginated(listOptions); + + return vectors; + } catch (error) { + console.error('Error listing vector IDs:', error); + throw new Error(error?.message || 'An unexpected error occurred'); + } +} + +export async function fetchVectors(pinecone: Pinecone, options: QueryOptions): Promise { + const { index, ids, namespace } = options; + + if (!index || !ids) { + throw new Error('Index name and vector IDs are required'); + } + + const vectorIds = typeof ids === 'string' ? JSON.parse(ids) : ids; + + try { + const indexClient = pinecone.index(index); + const client = namespace ? await indexClient.namespace(namespace) : indexClient; + const vectors = await client.fetch(vectorIds); + + return vectors; + } catch (error) { + throw new Error(error?.message || 'An unexpected error occurred'); + } +} + +export async function upsertVectors(pinecone: Pinecone, options: QueryOptions): Promise { + const { index, vectors, namespace } = options; + const parsedVectors = typeof vectors === 'string' ? JSON.parse(vectors) : vectors; + + if (!index || !vectors) { + throw new Error('Index name and vectors are required'); + } + + parsedVectors.forEach((vector) => { + if (!vector.id || !Array.isArray(vector.values)) { + throw new Error('Each vector must have an id and a values array'); + } + }); + + try { + const indexClient = pinecone.index(index); + const client = namespace ? await indexClient.namespace(namespace) : indexClient; + const upsertResponse = await client.upsert(parsedVectors); + if (upsertResponse === undefined) { + return 'Upsert Successful'; + } else { + throw new Error('Upsert failed'); + } + } catch (error) { + throw new Error(error?.message || 'An unexpected error occurred'); + } +} + +export async function updateVector(pinecone: Pinecone, options: QueryOptions): Promise { + const { index, id, values, sparse_vector, metadata, namespace } = options; + + if (!index || !id || (!values && !sparse_vector)) { + throw new Error('Index name, vector ID, and either values or sparse vector are required'); + } + + const valuesArray = typeof values === 'string' ? JSON.parse(values) : values; + + try { + const indexClient = pinecone.index(index); + const client = namespace ? await indexClient.namespace(namespace) : indexClient; + const updateResponse = await client.update({ + id, + ...(valuesArray && { values: valuesArray }), + ...(metadata && { metadata: JSON.parse(metadata) }), + ...(sparse_vector && { sparseValues: JSON.parse(sparse_vector) }), + }); + + if (updateResponse === undefined) { + return 'Update Successful'; + } else { + throw new Error('Update failed'); + } + } catch (error) { + throw new Error(error?.message || 'An unexpected error occurred'); + } +} + +export async function deleteVectors(pinecone: Pinecone, options: QueryOptions): Promise { + const { index, id, delete_all, namespace, filter } = options; + + if (!index) { + throw new Error('Index name is required'); + } + + try { + const indexClient = pinecone.index(index); + const client = namespace ? await indexClient.namespace(namespace) : indexClient; + let deleteResponse; + if (delete_all && delete_all.toLowerCase() === 'true') { + deleteResponse = await client.deleteAll(); + } else if (filter) { + deleteResponse = await client.deleteMany({ + filter: JSON.parse(filter), + }); + } else { + deleteResponse = await client.deleteMany(JSON.parse(id)); + } + + if (deleteResponse === undefined) { + return 'Delete Successful'; + } else { + throw new Error('Delete failed'); + } + } catch (error) { + throw new Error(error?.message || 'An unexpected error occurred'); + } +} + +export async function quertVectors(pinecone: Pinecone, options: QueryOptions): Promise { + const { index, namespace, top_k, filter, include_values, include_metadata, vectors, sparse_vector } = options; + + if (!index) { + throw new Error('Index is required'); + } + + const pineconeQueryOptions = { + topK: Number(top_k), + vector: JSON.parse(vectors), + ...(filter && { filter: JSON.parse(filter) }), + ...(include_values && { includeValues: include_values.toLowerCase() === 'true' }), + ...(include_metadata && { includeMetadata: include_metadata.toLowerCase() === 'true' }), + ...(sparse_vector && { sparseVector: JSON.parse(sparse_vector) }), + }; + + try { + const indexClient = pinecone.index(index); + const client = namespace ? await indexClient.namespace(namespace) : indexClient; + const queryResponse = await client.query(pineconeQueryOptions); + return queryResponse; + } catch (error) { + throw new Error(error.message); + } +} diff --git a/marketplace/plugins/pinecone/lib/types.ts b/marketplace/plugins/pinecone/lib/types.ts new file mode 100644 index 0000000000..44a670264a --- /dev/null +++ b/marketplace/plugins/pinecone/lib/types.ts @@ -0,0 +1,44 @@ +export type SourceOptions = { + apiKey: string; +}; + +// Define the query options based on the available operations in the operations.json file. +export type QueryOptions = { + operation: Operation; + index: string; + ids?: string[]; + vectors?: string; + string?: string[]; + id?: string; + values?: number[]; + sparseValues?: SparseValues; + setmetadata?: object; + filter?: string; + prefix?: string; + limit?: number; + paginationToken?: string; + namespace?: string; + delete_all?: string; + metadata?: string; + sparse_vector?: string; + top_k?: string; + include_metadata?: string; + include_values?: string; +}; + +// Define a type for sparse vectors used in the "update_vector" operation. +export type SparseValues = { + indices: number[]; + values: number[]; +}; + +// Enum for different operations. +export enum Operation { + GetIndexStats = 'get_index_stats', + ListVectorIds = 'list_vector_ids', + FetchVectors = 'fetch_vectors', + UpsertVectors = 'upsert_vectors', + UpdateVector = 'update_vector', + DeleteVectors = 'delete_vectors', + QueryVectors = 'query_vector', +} diff --git a/marketplace/plugins/pinecone/package.json b/marketplace/plugins/pinecone/package.json new file mode 100644 index 0000000000..aa65c9a95a --- /dev/null +++ b/marketplace/plugins/pinecone/package.json @@ -0,0 +1,28 @@ +{ + "name": "@tooljet-marketplace/pinecone", + "version": "1.0.0", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "directories": { + "lib": "lib", + "test": "__tests__" + }, + "files": [ + "lib" + ], + "scripts": { + "test": "echo \"Error: run tests from root\" && exit 1", + "build": "ncc build lib/index.ts -o dist", + "watch": "ncc build lib/index.ts -o dist --watch" + }, + "homepage": "https://github.com/tooljet/tooljet#readme", + "dependencies": { + "@grpc/grpc-js": "^1.12.2", + "@pinecone-database/pinecone": "^3.0.3", + "@tooljet-marketplace/common": "^1.0.0" + }, + "devDependencies": { + "@vercel/ncc": "^0.34.0", + "typescript": "^4.7.4" + } +} diff --git a/marketplace/plugins/pinecone/tsconfig.json b/marketplace/plugins/pinecone/tsconfig.json new file mode 100644 index 0000000000..a18a801b14 --- /dev/null +++ b/marketplace/plugins/pinecone/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "lib" + }, + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/server/src/assets/marketplace/plugins.json b/server/src/assets/marketplace/plugins.json index 9554a8ca34..e231792836 100644 --- a/server/src/assets/marketplace/plugins.json +++ b/server/src/assets/marketplace/plugins.json @@ -1,84 +1,84 @@ [ - { - "name": "plivo", - "description": "Plugin for Plivo APIs", - "version": "1.0.0", - "id": "plivo", - "author": "Tooljet", - "timestamp": "Thu, 02 Mar 2023 10:40:06 GMT" - }, - { - "name": "GitHub", - "description": "Plugin for GitHub APIs", - "version": "1.0.0", - "id": "github", - "author": "Tooljet", - "timestamp": "Thu, 02 Mar 2023 11:52:32 GMT" - }, - { - "name": "OpenAI", - "description": "Plugin for OpenAI APIs", - "version": "1.0.0", - "id": "openai", - "author": "Tooljet", - "timestamp": "Mon, 10 Apr 2023 06:33:21 GMT" - }, - { - "name": "AWS Textract", - "description": "Plugin for AWS Textract machine-learning service", - "version": "1.0.0", - "id": "textract", - "author": "Tooljet", - "timestamp": "Wed, 19 Apr 2023 11:36:56 GMT" - }, - { - "name": "HarperDB", - "repo": "", - "description": "Plugin for HarperDB data source", - "version": "1.0.0", - "id": "harperdb", - "author": "Tooljet", - "timestamp": "Thu, 08 Jun 2023 09:50:05 GMT" - }, - { - "name": "AWS Redshift", - "description": "Plugin for Amazon Redshift data warehouse", - "version": "1.0.0", - "id": "awsredshift", - "author": "Tooljet", - "timestamp": "Wed, 17 Jan 2024 20:05:16 GMT" - }, - { - "name": "aws-lambda", - "description": "Plugin for aws-lambda", - "version": "1.0.0", - "id": "aws-lambda", - "author": "Tooljet", - "timestamp": "Tue, 05 Dec 2023 00:27:16 GMT" - }, - { - "name": "PocketBase", - "description": "API plugin from pocketbase", - "version": "1.0.0", - "id": "pocketbase", - "author": "Tooljet", - "timestamp": "Mon, 18 Mar 2024 14:39:34 GMT" - }, - { - "name": "Supabase", - "description": "Plugin for Supabase", - "version": "1.0.0", - "id": "supabase", - "author": "Tooljet", - "timestamp": "Mon, 25 Mar 2024 20:05:16 GMT" - }, - { - "name": "Engagespot", - "description": "Plugin for engagespot APIs", - "version": "1.0.0", - "id": "engagespot", - "author": "Tooljet", - "timestamp": "Thu, 29 Feb 2024 09:46:21 GMT" + { + "name": "plivo", + "description": "Plugin for Plivo APIs", + "version": "1.0.0", + "id": "plivo", + "author": "Tooljet", + "timestamp": "Thu, 02 Mar 2023 10:40:06 GMT" + }, + { + "name": "GitHub", + "description": "Plugin for GitHub APIs", + "version": "1.0.0", + "id": "github", + "author": "Tooljet", + "timestamp": "Thu, 02 Mar 2023 11:52:32 GMT" + }, + { + "name": "OpenAI", + "description": "Plugin for OpenAI APIs", + "version": "1.0.0", + "id": "openai", + "author": "Tooljet", + "timestamp": "Mon, 10 Apr 2023 06:33:21 GMT" + }, + { + "name": "AWS Textract", + "description": "Plugin for AWS Textract machine-learning service", + "version": "1.0.0", + "id": "textract", + "author": "Tooljet", + "timestamp": "Wed, 19 Apr 2023 11:36:56 GMT" + }, + { + "name": "HarperDB", + "repo": "", + "description": "Plugin for HarperDB data source", + "version": "1.0.0", + "id": "harperdb", + "author": "Tooljet", + "timestamp": "Thu, 08 Jun 2023 09:50:05 GMT" + }, + { + "name": "AWS Redshift", + "description": "Plugin for Amazon Redshift data warehouse", + "version": "1.0.0", + "id": "awsredshift", + "author": "Tooljet", + "timestamp": "Wed, 17 Jan 2024 20:05:16 GMT" + }, + { + "name": "aws-lambda", + "description": "Plugin for aws-lambda", + "version": "1.0.0", + "id": "aws-lambda", + "author": "Tooljet", + "timestamp": "Tue, 05 Dec 2023 00:27:16 GMT" + }, + { + "name": "PocketBase", + "description": "API plugin from pocketbase", + "version": "1.0.0", + "id": "pocketbase", + "author": "Tooljet", + "timestamp": "Mon, 18 Mar 2024 14:39:34 GMT" + }, + { + "name": "Supabase", + "description": "Plugin for Supabase", + "version": "1.0.0", + "id": "supabase", + "author": "Tooljet", + "timestamp": "Mon, 25 Mar 2024 20:05:16 GMT" + }, + { + "name": "Engagespot", + "description": "Plugin for engagespot APIs", + "version": "1.0.0", + "id": "engagespot", + "author": "Tooljet", + "timestamp": "Thu, 29 Feb 2024 09:46:21 GMT" }, { "name": "salesforce", @@ -95,5 +95,13 @@ "id": "portkey", "author": "Portkey", "timestamp": "Sat, 29 Jun 2024 09:40:13 GMT" + }, + { + "name": "pinecone", + "description": "api plugin from pinecone", + "version": "1.0.0", + "id": "pinecone", + "author": "Tooljet", + "timestamp": "Mon, 28 Oct 2024 08:08:28 GMT" } ] \ No newline at end of file