UNPKG

@cafeasp/n8n-nodes-shopify-graphql

Version:
565 lines (564 loc) 20.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ShopifyGraphQl = void 0; // Import types from n8n-workflow for building the node logic // INodeType: Base interface that defines the node structure // INodeTypeDescription: Describes the node's UI (name, icon, input fields, operations) // IExecuteFunctions: Provides helper methods to access parameters, credentials, and make HTTP requests // INodeExecutionData: Defines the structure of data passed between nodes // NodeOperationError: Used to throw user-friendly errors when operations fail const n8n_workflow_1 = require("n8n-workflow"); class ShopifyGraphQl { constructor() { this.description = { displayName: 'Shopify GraphQL', name: 'shopifyGraphQl', icon: 'file:shopify.svg', group: ['transform'], version: 1, subtitle: '={{$parameter["operation"]}}', description: 'Execute GraphQL queries against Shopify Admin API', defaults: { name: 'Shopify GraphQL', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'shopifyGraphQlApi', required: true, }, ], properties: [ { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, options: [ { name: 'Execute Query', value: 'query', description: 'Execute a GraphQL query', action: 'Execute a GraphQL query', }, { name: 'Get Collection by Name', value: 'getCollectionByName', description: 'Get a collection by name and return product SKUs', action: 'Get a collection by name', }, { name: 'Get Product by SKU', value: 'getProductBySku', description: 'Get a product by variant SKU', action: 'Get a product by variant SKU', }, { name: 'Get Products', value: 'getProducts', description: 'Get a list of products', action: 'Get a list of products', }, { name: 'Get Orders', value: 'getOrders', description: 'Get a list of orders', action: 'Get a list of orders', }, ], default: 'query', }, // Custom Query { displayName: 'GraphQL Query', name: 'query', type: 'string', typeOptions: { rows: 10, }, displayOptions: { show: { operation: ['query'], }, }, default: '{\n shop {\n name\n email\n }\n}', description: 'The GraphQL query to execute', required: true, }, { displayName: 'Variables', name: 'variables', type: 'json', displayOptions: { show: { operation: ['query'], }, }, default: '{}', description: 'GraphQL query variables (as JSON)', }, // Get Collection by Name { displayName: 'Collection Name', name: 'collectionName', type: 'string', displayOptions: { show: { operation: ['getCollectionByName'], }, }, default: '', placeholder: 'Shop All LuMee', description: 'The exact title of the collection as it appears in Shopify', required: true, }, // Get Product by SKU { displayName: 'SKU', name: 'sku', type: 'string', displayOptions: { show: { operation: ['getProductBySku'], }, }, default: '', placeholder: 'ABC-123', description: 'The SKU of the product variant to search for', required: true, }, // Get Products Options { displayName: 'Return All', name: 'returnAll', type: 'boolean', displayOptions: { show: { operation: ['getProducts', 'getOrders'], }, }, default: false, description: 'Whether to return all results or only up to a given limit', }, { displayName: 'Limit', name: 'limit', type: 'number', displayOptions: { show: { operation: ['getProducts', 'getOrders'], returnAll: [false], }, }, typeOptions: { minValue: 1, maxValue: 250, }, default: 10, description: 'Max number of results to return', }, { displayName: 'Status', name: 'status', type: 'multiOptions', displayOptions: { show: { operation: ['getProducts'], }, }, options: [ { name: 'Active', value: 'ACTIVE', description: 'Products visible in online stores and sales channels', }, { name: 'Archived', value: 'ARCHIVED', description: 'Products no longer available for sale', }, { name: 'Draft', value: 'DRAFT', description: 'Products not yet published', }, ], default: ['ACTIVE'], description: 'Filter products by status. You can select multiple statuses.', }, ], }; } async execute() { const items = this.getInputData(); const returnData = []; const operation = this.getNodeParameter('operation', 0); // Get credentials const credentials = await this.getCredentials('shopifyGraphQlApi'); const shopName = credentials.shopName; const accessToken = credentials.accessToken; const apiVersion = credentials.apiVersion; const baseUrl = `https://${shopName}.myshopify.com/admin/api/${apiVersion}/graphql.json`; for (let i = 0; i < items.length; i++) { try { let query = ''; let variables = {}; if (operation === 'query') { // Custom query query = this.getNodeParameter('query', i); const variablesString = this.getNodeParameter('variables', i, '{}'); try { variables = JSON.parse(variablesString); } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Variables must be valid JSON', { itemIndex: i }); } } else if (operation === 'getCollectionByName') { // Get collection by name const collectionName = this.getNodeParameter('collectionName', i); query = ` query CollectionAndProducts($query: String!) { collections(first: 1, query: $query) { edges { node { id title handle descriptionHtml productsCount { count } products(first: 250) { edges { node { id title handle variants(first: 100) { edges { node { id sku title price } } } } } } } } } } `; variables = { query: `title:${collectionName}` }; } else if (operation === 'getProductBySku') { // Get product by SKU const sku = this.getNodeParameter('sku', i); query = ` query GetProductBySku($query: String!) { products(first: 1, query: $query) { edges { node { id title description handle status createdAt updatedAt variants(first: 100) { edges { node { id title price sku inventoryQuantity compareAtPrice barcode inventoryItem { id } } } } images(first: 5) { edges { node { id url altText } } } } } } } `; variables = { query: `sku:${sku}` }; } else if (operation === 'getProducts') { // Get products query const returnAll = this.getNodeParameter('returnAll', i, false); const statusFilter = this.getNodeParameter('status', i, ['ACTIVE']); // Build query string for status filter const queryString = `status:${statusFilter.join(',')}`; if (returnAll) { // Fetch all products using pagination let hasNextPage = true; let cursor = null; const allProducts = []; while (hasNextPage) { const paginationQuery = ` query GetProducts($query: String!, $cursor: String) { products(first: 250, query: $query, after: $cursor) { edges { cursor node { id title description handle status createdAt updatedAt variants(first: 10) { edges { node { id title price sku inventoryItem { id } } } } } } pageInfo { hasNextPage endCursor } } } `; const paginationVars = cursor ? { query: queryString, cursor } : { query: queryString }; const response = await this.helpers.httpRequest({ method: 'POST', url: baseUrl, headers: { 'X-Shopify-Access-Token': accessToken, 'Content-Type': 'application/json', }, body: { query: paginationQuery, variables: paginationVars, }, json: true, }); if (response.errors) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `GraphQL Error: ${JSON.stringify(response.errors)}`, { itemIndex: i }); } const products = response.data.products.edges.map((edge) => edge.node); allProducts.push(...products); hasNextPage = response.data.products.pageInfo.hasNextPage; cursor = response.data.products.pageInfo.endCursor; } returnData.push({ json: { products: allProducts }, pairedItem: { item: i }, }); continue; } else { // Fetch limited products const limit = this.getNodeParameter('limit', i); query = ` query GetProducts($limit: Int!, $query: String!) { products(first: $limit, query: $query) { edges { node { id title description handle status createdAt updatedAt variants(first: 10) { edges { node { id title price sku inventoryItem { id } } } } } } } } `; variables = { limit, query: queryString }; } } else if (operation === 'getOrders') { // Get orders query const returnAll = this.getNodeParameter('returnAll', i, false); if (returnAll) { // Fetch all orders using pagination let hasNextPage = true; let cursor = null; const allOrders = []; while (hasNextPage) { const paginationQuery = ` query GetOrders($cursor: String) { orders(first: 250, after: $cursor) { edges { cursor node { id name email createdAt totalPriceSet { shopMoney { amount currencyCode } } lineItems(first: 10) { edges { node { id title quantity } } } } } pageInfo { hasNextPage endCursor } } } `; const paginationVars = cursor ? { cursor } : {}; const response = await this.helpers.httpRequest({ method: 'POST', url: baseUrl, headers: { 'X-Shopify-Access-Token': accessToken, 'Content-Type': 'application/json', }, body: { query: paginationQuery, variables: paginationVars, }, json: true, }); if (response.errors) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `GraphQL Error: ${JSON.stringify(response.errors)}`, { itemIndex: i }); } const orders = response.data.orders.edges.map((edge) => edge.node); allOrders.push(...orders); hasNextPage = response.data.orders.pageInfo.hasNextPage; cursor = response.data.orders.pageInfo.endCursor; } returnData.push({ json: { orders: allOrders }, pairedItem: { item: i }, }); continue; } else { // Fetch limited orders const limit = this.getNodeParameter('limit', i); query = ` query GetOrders($limit: Int!) { orders(first: $limit) { edges { node { id name email createdAt totalPriceSet { shopMoney { amount currencyCode } } lineItems(first: 10) { edges { node { id title quantity } } } } } } } `; variables = { limit }; } } // Make the GraphQL request const response = await this.helpers.httpRequest({ method: 'POST', url: baseUrl, headers: { 'X-Shopify-Access-Token': accessToken, 'Content-Type': 'application/json', }, body: { query, variables, }, json: true, }); // Check for GraphQL errors if (response.errors) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `GraphQL Error: ${JSON.stringify(response.errors)}`, { itemIndex: i }); } returnData.push({ json: response.data, pairedItem: { item: i }, }); } catch (error) { if (this.continueOnFail()) { returnData.push({ json: { error: error.message, }, pairedItem: { item: i }, }); continue; } throw error; } } return [returnData]; } } exports.ShopifyGraphQl = ShopifyGraphQl;