UNPKG

@medusajs/utils

Version:

Medusa utilities functions shared by Medusa core and Modules

341 lines • 16.2 kB
"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _CSVNormalizer_instances, _CSVNormalizer_rows, _CSVNormalizer_products, _CSVNormalizer_getOrInitializeProductById, _CSVNormalizer_getOrInitializeProductByHandle, _CSVNormalizer_processRow; Object.defineProperty(exports, "__esModule", { value: true }); exports.CSVNormalizer = void 0; const common_1 = require("../common"); /** * Creates an error with the CSV row number */ function createError(rowNumber, message) { return new common_1.MedusaError(common_1.MedusaError.Types.INVALID_DATA, `Row ${rowNumber}: ${message}`); } /** * Parses different patterns to extract variant price iso * and the region name. The iso is converted to lowercase */ function parseVariantPriceColumn(columnName, rowNumber) { const normalizedValue = columnName; const potentialRegion = /\[(.*)\]/g.exec(normalizedValue)?.[1]; const iso = normalizedValue.split(" ").pop(); if (!iso) { throw createError(rowNumber, `Invalid price format used by "${columnName}". Expect column name to contain the ISO code as the last segment. For example: "Variant Price [Europe] EUR" or "Variant Price EUR"`); } return { iso: iso.toLowerCase(), region: potentialRegion, }; } /** * Processes a column value as a string */ function processAsString(inputKey, outputKey) { return (csvRow, _, __, output) => { const value = csvRow[inputKey]; if ((0, common_1.isPresent)(value)) { output[outputKey] = value; } }; } /** * Processes the column value as a boolean */ function processAsBoolean(inputKey, outputKey) { return (csvRow, _, __, output) => { const value = csvRow[inputKey]; if ((0, common_1.isPresent)(value)) { output[outputKey] = (0, common_1.tryConvertToBoolean)(value, value); } }; } /** * Processes the column value as a number */ function processAsNumber(inputKey, outputKey, options) { return (csvRow, _, rowNumber, output) => { const value = csvRow[inputKey]; if ((0, common_1.isPresent)(value)) { const numericValue = (0, common_1.tryConvertToNumber)(value); if (numericValue === undefined) { throw createError(rowNumber, `Invalid value provided for "${inputKey}". Expected value to be a number, received "${value}"`); } else { if (options?.asNumericString) { output[outputKey] = String(numericValue); } else { output[outputKey] = numericValue; } } } }; } /** * Processes the CSV column as a counter value. The counter values * are defined as "<Column Name> <1>". Duplicate values are not * added twice. */ function processAsCounterValue(inputMatcher, arrayItemKey, outputKey) { return (csvRow, rowColumns, _, output) => { output[outputKey] = output[outputKey] ?? []; const existingIds = output[outputKey].map((item) => item[arrayItemKey]); rowColumns .filter((rowKey) => inputMatcher.test(rowKey)) .forEach((rowKey) => { const value = csvRow[rowKey]; if (!existingIds.includes(value) && (0, common_1.isPresent)(value)) { output[outputKey].push({ [arrayItemKey]: value }); } }); }; } /** * Collection of static product columns whose values must be copied * as it is without any further processing. */ const productStaticColumns = { "product id": processAsString("product id", "id"), "product handle": processAsString("product handle", "handle"), "product title": processAsString("product title", "title"), "product status": processAsString("product status", "status"), "product description": processAsString("product description", "description"), "product subtitle": processAsString("product subtitle", "subtitle"), "product external id": processAsString("product external id", "external_id"), "product thumbnail": processAsString("product thumbnail", "thumbnail"), "product collection id": processAsString("product collection id", "collection_id"), "product type id": processAsString("product type id", "type_id"), "product discountable": processAsBoolean("product discountable", "discountable"), "product height": processAsNumber("product height", "height"), "product hs code": processAsString("product hs code", "hs_code"), "product length": processAsNumber("product length", "length"), "product material": processAsString("product material", "material"), "product mid code": processAsString("product mid code", "mid_code"), "product origin country": processAsString("product origin country", "origin_country"), "product weight": processAsNumber("product weight", "weight"), "product width": processAsNumber("product width", "width"), "product metadata": processAsString("product metadata", "metadata"), "shipping profile id": processAsString("shipping profile id", "shipping_profile_id"), }; /** * Collection of wildcard product columns whose values will be computed by * one or more columns from the CSV row. */ const productWildcardColumns = { "product category": processAsCounterValue(/product category \d/, "id", "categories"), "product image": processAsCounterValue(/product image \d/, "url", "images"), "product tag": processAsCounterValue(/product tag \d/, "id", "tags"), "product sales channel": processAsCounterValue(/product sales channel \d/, "id", "sales_channels"), }; /** * Collection of static variant columns whose values must be copied * as it is without any further processing. */ const variantStaticColumns = { "variant id": processAsString("variant id", "id"), "variant title": processAsString("variant title", "title"), "variant sku": processAsString("variant sku", "sku"), "variant upc": processAsString("variant upc", "upc"), "variant ean": processAsString("variant ean", "ean"), "variant hs code": processAsString("variant hs code", "hs_code"), "variant mid code": processAsString("variant mid code", "mid_code"), "variant manage inventory": processAsBoolean("variant manage inventory", "manage_inventory"), "variant allow backorder": processAsBoolean("variant allow backorder", "allow_backorder"), "variant barcode": processAsString("variant barcode", "barcode"), "variant height": processAsNumber("variant height", "height"), "variant length": processAsNumber("variant length", "length"), "variant material": processAsString("variant material", "material"), "variant metadata": processAsString("variant metadata", "metadata"), "variant origin country": processAsString("variant origin country", "origin_country"), "variant variant rank": processAsNumber("variant variant rank", "variant_rank"), "variant width": processAsNumber("variant width", "width"), "variant weight": processAsNumber("variant weight", "weight"), }; /** * Collection of wildcard variant columns whose values will be computed by * one or more columns from the CSV row. */ const variantWildcardColumns = { "variant price": (csvRow, rowColumns, rowNumber, output) => { const pricesColumns = rowColumns.filter((rowKey) => { return rowKey.startsWith("variant price ") && (0, common_1.isPresent)(csvRow[rowKey]); }); output["prices"] = output["prices"] ?? []; pricesColumns.forEach((columnName) => { const { iso } = parseVariantPriceColumn(columnName, rowNumber); const value = csvRow[columnName]; const numericValue = (0, common_1.tryConvertToNumber)(value); if (numericValue === undefined) { throw createError(rowNumber, `Invalid value provided for "${columnName}". Expected value to be a number, received "${value}"`); } else { output["prices"].push({ currency_code: iso, amount: numericValue, }); } }); }, }; /** * Options are processed separately and then defined on both the products and * the variants. */ const optionColumns = { "variant option": (csvRow, rowColumns, rowNumber, output) => { const matcher = /variant option \d+ name/; const optionNameColumns = rowColumns.filter((rowKey) => { return matcher.test(rowKey) && (0, common_1.isPresent)(csvRow[rowKey]); }); output["options"] = optionNameColumns.map((columnName) => { const [, , counter] = columnName.split(" "); const key = csvRow[columnName]; const value = csvRow[`variant option ${counter} value`]; if (!(0, common_1.isPresent)(value)) { throw createError(rowNumber, `Missing option value for "${columnName}"`); } return { key, value, }; }); }, }; /** * An array of known columns */ const knownStaticColumns = Object.keys(productStaticColumns).concat(Object.keys(variantStaticColumns)); const knownWildcardColumns = Object.keys(productWildcardColumns) .concat(Object.keys(variantWildcardColumns)) .concat(Object.keys(optionColumns)); /** * CSV normalizer processes all the allowed columns from a CSV file and remaps * them into a new object with properties matching the "AdminCreateProduct". * * However, further validations must be performed to validate the format and * the required fields in the normalized output. */ class CSVNormalizer { /** * Normalizes a row by converting all keys to lowercase and removing * the leading "\r" from the keys and the values. * * Also, it values the row to contain unknown columns and must contain * the "product id" or "product handle" columns. */ static preProcess(row, rowNumber) { const unknownColumns = []; const normalized = Object.keys(row).reduce((result, key) => { const lowerCaseKey = (0, common_1.normalizeCSVValue)(key).toLowerCase(); if (!knownStaticColumns.includes(lowerCaseKey) && !knownWildcardColumns.some((column) => lowerCaseKey.startsWith(column))) { unknownColumns.push(key); } result[lowerCaseKey] = (0, common_1.normalizeCSVValue)(row[key]); return result; }, {}); if (unknownColumns.length) { throw new common_1.MedusaError(common_1.MedusaError.Types.INVALID_DATA, `Invalid column name(s) "${unknownColumns.join('","')}"`); } const productId = normalized["product id"]; const productHandle = normalized["product handle"]; if (!(0, common_1.isPresent)(productId) && !(0, common_1.isPresent)(productHandle)) { throw createError(rowNumber, "Missing product id and handle. One of these columns are required to process the row"); } return normalized; } constructor(rows) { _CSVNormalizer_instances.add(this); _CSVNormalizer_rows.set(this, void 0); _CSVNormalizer_products.set(this, { toCreate: {}, toUpdate: {}, }); __classPrivateFieldSet(this, _CSVNormalizer_rows, rows, "f"); } /** * Process CSV rows. The return value is a tree of products */ proccess(resumingFromIndex = 0) { __classPrivateFieldGet(this, _CSVNormalizer_rows, "f").forEach((row, index) => __classPrivateFieldGet(this, _CSVNormalizer_instances, "m", _CSVNormalizer_processRow).call(this, row, resumingFromIndex + index + 1)); __classPrivateFieldSet(this, _CSVNormalizer_rows, [], "f"); return __classPrivateFieldGet(this, _CSVNormalizer_products, "f"); } } exports.CSVNormalizer = CSVNormalizer; _CSVNormalizer_rows = new WeakMap(), _CSVNormalizer_products = new WeakMap(), _CSVNormalizer_instances = new WeakSet(), _CSVNormalizer_getOrInitializeProductById = function _CSVNormalizer_getOrInitializeProductById(id) { if (!__classPrivateFieldGet(this, _CSVNormalizer_products, "f").toUpdate[id]) { __classPrivateFieldGet(this, _CSVNormalizer_products, "f").toUpdate[id] = {}; } return __classPrivateFieldGet(this, _CSVNormalizer_products, "f").toUpdate[id]; }, _CSVNormalizer_getOrInitializeProductByHandle = function _CSVNormalizer_getOrInitializeProductByHandle(handle) { if (!__classPrivateFieldGet(this, _CSVNormalizer_products, "f").toCreate[handle]) { __classPrivateFieldGet(this, _CSVNormalizer_products, "f").toCreate[handle] = {}; } return __classPrivateFieldGet(this, _CSVNormalizer_products, "f").toCreate[handle]; }, _CSVNormalizer_processRow = function _CSVNormalizer_processRow(row, rowNumber) { const rowColumns = Object.keys(row); const productId = row["product id"]; const productHandle = row["product handle"]; /** * Create representation of a product by its id or handle and process * its static + wildcard columns */ const product = productId ? __classPrivateFieldGet(this, _CSVNormalizer_instances, "m", _CSVNormalizer_getOrInitializeProductById).call(this, String(productId)) : __classPrivateFieldGet(this, _CSVNormalizer_instances, "m", _CSVNormalizer_getOrInitializeProductByHandle).call(this, String(productHandle)); Object.keys(productStaticColumns).forEach((column) => { productStaticColumns[column](row, rowColumns, rowNumber, product); }); Object.keys(productWildcardColumns).forEach((column) => { productWildcardColumns[column](row, rowColumns, rowNumber, product); }); /** * Create representation of a variant and process * its static + wildcard columns */ const variant = {}; Object.keys(variantStaticColumns).forEach((column) => { variantStaticColumns[column](row, rowColumns, rowNumber, variant); }); Object.keys(variantWildcardColumns).forEach((column) => { variantWildcardColumns[column](row, rowColumns, rowNumber, variant); }); /** * Process variant options as a standalone array */ const options = { options: [] }; Object.keys(optionColumns).forEach((column) => { optionColumns[column](row, rowColumns, rowNumber, options); }); /** * Specify options on both the variant and the product */ options.options.forEach(({ key, value }) => { variant.options = variant.options ?? {}; variant.options[key] = value; product.options = product.options ?? []; const matchingKey = product.options.find((option) => option.title === key); if (!matchingKey) { product.options.push({ title: key, values: [value] }); } else if (!matchingKey.values.includes(value)) { matchingKey.values.push(value); } }); /** * Assign variant to the product */ product.variants = product.variants ?? []; product.variants.push(variant); }; //# sourceMappingURL=csv-normalizer.js.map