"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { SpreadORM: () => SpreadORM }); module.exports = __toCommonJS(src_exports); // src/classes/SpreadORM.ts var import_papaparse = __toESM(require("papaparse"), 1); // src/errors/SpreadORMError.ts var SpreadORMError = class extends Error { constructor(message) { super(message); this.name = "SpreadORMError"; } }; var FetchError = class extends SpreadORMError { constructor(message, statusCode) { super(`Failed to fetch spreadsheet: ${message}`); this.statusCode = statusCode; this.name = "FetchError"; } }; var ValidationError = class extends SpreadORMError { constructor(message) { super(message); this.name = "ValidationError"; } }; // src/utils/index.ts function isWhereOperator(value) { if (typeof value !== "object" || value === null) return false; const validKeys = [ "eq", "ne", "gt", "gte", "lt", "lte", "contains", "startsWith", "endsWith", "in", "notIn" ]; return Object.keys(value).every((key) => validKeys.includes(key)); } function applyWhere(data, where) { if (!where) return data; return data.filter( (row) => Object.entries(where).every(([key, condition]) => { const rowValue = row[key]; if (rowValue === null) { if (condition === null) return true; return false; } if (isWhereOperator(condition)) { const { eq, ne, gt, gte, lt, lte, contains, startsWith, endsWith, in: inArray, notIn } = condition; if (eq !== void 0) return rowValue === eq; if (ne !== void 0) return rowValue !== ne; if (typeof rowValue === "number") { if (gt !== void 0 && typeof gt === "number") return rowValue > gt; if (gte !== void 0 && typeof gte === "number") return rowValue >= gte; if (lt !== void 0 && typeof lt === "number") return rowValue < lt; if (lte !== void 0 && typeof lte === "number") return rowValue <= lte; } if (typeof rowValue === "string") { if (contains !== void 0 && typeof contains === "string") { return rowValue.includes(contains); } if (startsWith !== void 0 && typeof startsWith === "string") { return rowValue.startsWith(startsWith); } if (endsWith !== void 0 && typeof endsWith === "string") { return rowValue.endsWith(endsWith); } } if (inArray !== void 0 && Array.isArray(inArray)) { return inArray.includes(rowValue); } if (notIn !== void 0 && Array.isArray(notIn)) { return !notIn.includes(rowValue); } return true; } return rowValue === condition; }) ); } function compareValues(a, b, order) { if (a === void 0 || a === null) return order === "asc" ? 1 : -1; if (b === void 0 || b === null) return order === "asc" ? -1 : 1; if (typeof a !== typeof b) { return String(a).localeCompare(String(b)) * (order === "asc" ? 1 : -1); } if (typeof a === "string" && typeof b === "string") { return a.localeCompare(b) * (order === "asc" ? 1 : -1); } if (typeof a === "number" && typeof b === "number") { return (a - b) * (order === "asc" ? 1 : -1); } return String(a).localeCompare(String(b)) * (order === "asc" ? 1 : -1); } function applyOrderBy(data, orderBy) { if (!orderBy) return data; const orderByArray = Array.isArray(orderBy) ? orderBy : [orderBy]; const filteredData = data.filter( (row) => Object.values(row).every((value) => value !== null) ); return [...filteredData].sort((a, b) => { for (const { key, order } of orderByArray) { const aValue = a[key]; const bValue = b[key]; const comparison = compareValues(aValue, bValue, order); if (comparison !== 0) return comparison; } return 0; }); } function isValidKey(data, key) { return Object.keys(data[0]).includes(key.toString()); } function applySelectLimitOffset(data, options) { let result = data; if (options?.offset !== void 0) { if (typeof options.offset !== "number" || options.offset < 0) { throw new ValidationError("Offset must be a non-negative number"); } result = result.slice(options.offset); } if (options?.limit !== void 0) { if (typeof options.limit !== "number" || options.limit < 0) { throw new ValidationError("Limit must be a non-negative number"); } result = result.slice(0, options.limit); } if (options?.select) { if (!Array.isArray(options.select)) { throw new ValidationError("Select must be an array of keys"); } const invalidKeys = options.select.filter((key) => !isValidKey(data, key)); if (invalidKeys.length > 0) { throw new ValidationError(`Invalid select keys: ${invalidKeys.join(", ")}`); } return result.map((row) => { const newRow = {}; if (!options?.select) return newRow; for (const key of options.select) { newRow[key] = row[key]; } return newRow; }); } return result; } // src/classes/SpreadORM.ts var { parse } = import_papaparse.default; var SpreadORM = class { sheetId; data = null; lastFetchTime = 0; cacheEnabled = true; cacheDuration = 5 * 60 * 1e3; // 5 minutes parseOptions; /** * Creates a new SpreadORM instance. * @param {string} sheetId - The ID of the Google Sheet. * @param {Object} options - Configuration options * @param {CacheOptions} [options.cache] - Cache configuration options * - enabled: Whether to enable caching (default: true) * - duration: Cache duration in milliseconds (default: 5 minutes) * @param {ParseOptions} [options.parseOptions] - CSV parsing options * - skipEmptyLines: Whether to skip empty lines (default: true) * - transformHeader: Function to transform the header (default: trim whitespace) * - delimiter: CSV delimiter (default: ',') */ constructor(sheetId, options) { if (!sheetId) throw new ValidationError("Sheet ID is required"); if (typeof sheetId !== "string") throw new ValidationError("Sheet ID must be a string"); this.sheetId = sheetId; if (options?.cache) { if (options.cache.enabled !== void 0) { this.cacheEnabled = options.cache.enabled; } if (options.cache.duration !== void 0) { if (typeof options.cache.duration !== "number" || options.cache.duration < 0) { throw new ValidationError("Cache duration must be a positive number"); } this.cacheDuration = options.cache.duration; } } this.parseOptions = { skipEmptyLines: true, transformHeader: (header) => header.trim(), ...options?.parseOptions }; } isCacheValid() { if (!this.cacheEnabled || !this.data) return false; const now = Date.now(); return now - this.lastFetchTime < this.cacheDuration; } async fetchData() { if (this.isCacheValid()) return; if (this.data !== null) { this.lastFetchTime = Date.now(); return; } const url = `https://docs.google.com/spreadsheets/d/${this.sheetId}/gviz/tq?tqx=out:csv`; try { const response = await fetch(url); if (!response.ok) { throw new FetchError(response.statusText, response.status); } const text = await response.text(); const { data, errors } = parse(text, { header: true, dynamicTyping: true, skipEmptyLines: this.parseOptions.skipEmptyLines, transformHeader: this.parseOptions.transformHeader, delimiter: this.parseOptions.delimiter }); if (errors.length > 0) { throw new ValidationError( `CSV parsing errors: ${errors.map((e) => e.message).join(", ")}` ); } const cleanData = data.map((row) => { const cleanRow = {}; Object.entries(row).forEach(([key, value]) => { if (key.trim() !== "") { cleanRow[key] = value; } }); return cleanRow; }); if (cleanData.length === 0) { console.warn("No data found in spreadsheet"); this.data = null; return; } this.data = cleanData; this.lastFetchTime = Date.now(); } catch (error) { this.data = null; if (error instanceof SpreadORMError) { throw error; } throw new FetchError(error instanceof Error ? error.message : "Unknown error occurred"); } } /** * Finds multiple rows in the sheet based on the provided options. * @param {SheetOptions} [options] - Options for filtering and selecting the rows. * @returns {Promise} A promise that resolves to an array of rows. */ async findMany(options) { try { await this.fetchData(); if (!this.data) return []; let result = this.data; result = applyWhere(result, options?.where); result = applyOrderBy(result, options?.orderBy); result = applySelectLimitOffset(result, options); return result; } catch (error) { console.error("Error in findMany:", error); throw error; } } /** * Finds a unique row in the sheet based on the provided options. * @param {SheetOptions} [options] - Options for filtering and selecting the unique row. * @returns {Promise | null>} A promise that resolves to the unique row or null if not found. * @throws {Error} If multiple results are found. */ async findUnique(options) { const results = await this.findMany(options); if (results.length > 1) { throw new ValidationError("findUnique found multiple results"); } return results[0] || null; } /** * Finds the first row in the sheet that matches the provided options. * @param {SheetOptions} [options] - Options for filtering and selecting the first row. * @returns {Promise | null>} A promise that resolves to the first matching row or null if not found. */ async findFirst(options) { const results = await this.findMany(options); return results[0] || null; } /** * Finds the last row in the sheet that matches the provided options. * @param {SheetOptions} [options] - Options for filtering and selecting the last row. * @returns {Promise | null>} A promise that resolves to the last matching row or null if not found. */ async findLast(options) { const results = await this.findMany(options); return results[results.length - 1] || null; } /** * Counts the number of rows that match the provided options. * @param {SheetOptions} [options] - Options for filtering the rows to be counted. * @returns {Promise} A promise that resolves to the count of matching rows. */ async count(options) { const results = await this.findMany(options); return results.length; } /** * Gets the current cache status */ getCacheStatus() { return { enabled: this.cacheEnabled, valid: this.isCacheValid(), lastFetchTime: this.lastFetchTime || null }; } /** * Updates cache settings */ configureCaching(options) { if (options.enabled !== void 0) { this.cacheEnabled = options.enabled; } if (options.duration !== void 0) { if (typeof options.duration !== "number" || options.duration < 0) { throw new ValidationError("Cache duration must be a positive number"); } this.cacheDuration = options.duration; } } /** * Resets the internal data cache, forcing a fresh fetch on the next operation. */ async reset() { this.data = null; this.lastFetchTime = 0; } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { SpreadORM }); //# sourceMappingURL=index.cjs.map