@teleporthq/teleport-plugin-next-data-source 0.44.11 → 0.44.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/browser-row-policy.test.ts +6 -6
- package/__tests__/fetchers.test.ts +1 -1
- package/__tests__/items-path.test.ts +3 -2
- package/__tests__/search-relevance.test.ts +407 -0
- package/dist/cjs/fetchers/csv-file.d.ts.map +1 -1
- package/dist/cjs/fetchers/csv-file.js +4 -3
- package/dist/cjs/fetchers/csv-file.js.map +1 -1
- package/dist/cjs/fetchers/google-sheets.d.ts.map +1 -1
- package/dist/cjs/fetchers/google-sheets.js +2 -1
- package/dist/cjs/fetchers/google-sheets.js.map +1 -1
- package/dist/cjs/fetchers/javascript.d.ts.map +1 -1
- package/dist/cjs/fetchers/javascript.js +2 -1
- package/dist/cjs/fetchers/javascript.js.map +1 -1
- package/dist/cjs/fetchers/mariadb.d.ts.map +1 -1
- package/dist/cjs/fetchers/mariadb.js +4 -3
- package/dist/cjs/fetchers/mariadb.js.map +1 -1
- package/dist/cjs/fetchers/mysql.d.ts.map +1 -1
- package/dist/cjs/fetchers/mysql.js +3 -2
- package/dist/cjs/fetchers/mysql.js.map +1 -1
- package/dist/cjs/fetchers/postgresql.d.ts.map +1 -1
- package/dist/cjs/fetchers/postgresql.js +4 -3
- package/dist/cjs/fetchers/postgresql.js.map +1 -1
- package/dist/cjs/fetchers/redshift.d.ts.map +1 -1
- package/dist/cjs/fetchers/redshift.js +3 -2
- package/dist/cjs/fetchers/redshift.js.map +1 -1
- package/dist/cjs/fetchers/rest-api.d.ts.map +1 -1
- package/dist/cjs/fetchers/rest-api.js +3 -2
- package/dist/cjs/fetchers/rest-api.js.map +1 -1
- package/dist/cjs/fetchers/supabase.d.ts.map +1 -1
- package/dist/cjs/fetchers/supabase.js +3 -2
- package/dist/cjs/fetchers/supabase.js.map +1 -1
- package/dist/cjs/fetchers/teleport.d.ts.map +1 -1
- package/dist/cjs/fetchers/teleport.js +5 -4
- package/dist/cjs/fetchers/teleport.js.map +1 -1
- package/dist/cjs/fetchers/turso.d.ts.map +1 -1
- package/dist/cjs/fetchers/turso.js +2 -1
- package/dist/cjs/fetchers/turso.js.map +1 -1
- package/dist/cjs/search-relevance.d.ts +81 -0
- package/dist/cjs/search-relevance.d.ts.map +1 -0
- package/dist/cjs/search-relevance.js +119 -0
- package/dist/cjs/search-relevance.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/fetchers/csv-file.d.ts.map +1 -1
- package/dist/esm/fetchers/csv-file.js +4 -3
- package/dist/esm/fetchers/csv-file.js.map +1 -1
- package/dist/esm/fetchers/google-sheets.d.ts.map +1 -1
- package/dist/esm/fetchers/google-sheets.js +2 -1
- package/dist/esm/fetchers/google-sheets.js.map +1 -1
- package/dist/esm/fetchers/javascript.d.ts.map +1 -1
- package/dist/esm/fetchers/javascript.js +2 -1
- package/dist/esm/fetchers/javascript.js.map +1 -1
- package/dist/esm/fetchers/mariadb.d.ts.map +1 -1
- package/dist/esm/fetchers/mariadb.js +4 -3
- package/dist/esm/fetchers/mariadb.js.map +1 -1
- package/dist/esm/fetchers/mysql.d.ts.map +1 -1
- package/dist/esm/fetchers/mysql.js +3 -2
- package/dist/esm/fetchers/mysql.js.map +1 -1
- package/dist/esm/fetchers/postgresql.d.ts.map +1 -1
- package/dist/esm/fetchers/postgresql.js +4 -3
- package/dist/esm/fetchers/postgresql.js.map +1 -1
- package/dist/esm/fetchers/redshift.d.ts.map +1 -1
- package/dist/esm/fetchers/redshift.js +3 -2
- package/dist/esm/fetchers/redshift.js.map +1 -1
- package/dist/esm/fetchers/rest-api.d.ts.map +1 -1
- package/dist/esm/fetchers/rest-api.js +3 -2
- package/dist/esm/fetchers/rest-api.js.map +1 -1
- package/dist/esm/fetchers/supabase.d.ts.map +1 -1
- package/dist/esm/fetchers/supabase.js +3 -2
- package/dist/esm/fetchers/supabase.js.map +1 -1
- package/dist/esm/fetchers/teleport.d.ts.map +1 -1
- package/dist/esm/fetchers/teleport.js +5 -4
- package/dist/esm/fetchers/teleport.js.map +1 -1
- package/dist/esm/fetchers/turso.d.ts.map +1 -1
- package/dist/esm/fetchers/turso.js +2 -1
- package/dist/esm/fetchers/turso.js.map +1 -1
- package/dist/esm/search-relevance.d.ts +81 -0
- package/dist/esm/search-relevance.d.ts.map +1 -0
- package/dist/esm/search-relevance.js +113 -0
- package/dist/esm/search-relevance.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/fetchers/csv-file.ts +17 -11
- package/src/fetchers/google-sheets.ts +15 -12
- package/src/fetchers/javascript.ts +12 -12
- package/src/fetchers/mariadb.ts +21 -26
- package/src/fetchers/mysql.ts +21 -26
- package/src/fetchers/postgresql.ts +31 -26
- package/src/fetchers/redshift.ts +18 -17
- package/src/fetchers/rest-api.ts +11 -11
- package/src/fetchers/supabase.ts +27 -20
- package/src/fetchers/teleport.ts +31 -26
- package/src/fetchers/turso.ts +18 -17
- package/src/search-relevance.ts +230 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateSupabaseCountFetcher = exports.generateSupabaseFetcher = exports.validateSupabaseConfig = void 0;
|
|
4
4
|
var utils_1 = require("../utils");
|
|
5
|
+
var search_relevance_1 = require("../search-relevance");
|
|
5
6
|
var validateSupabaseConfig = function (config) {
|
|
6
7
|
if (!config || typeof config !== 'object') {
|
|
7
8
|
return { isValid: false, error: 'Config must be a valid object' };
|
|
@@ -91,12 +92,12 @@ var generateSupabaseFetcher = function (config, tableName) {
|
|
|
91
92
|
END $$;
|
|
92
93
|
|
|
93
94
|
*/
|
|
94
|
-
return "import { createClient } from '@supabase/supabase-js'\n\nlet client = null\n\nconst getClient = () => {\n if (client) return client\n \n client = createClient(\n ".concat(JSON.stringify(supabaseUrl), ",\n ").concat((0, utils_1.replaceSecretReference)(apiKey), "\n )\n \n return client\n}\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateSearchEscapeHelpersCode)(), "\n\n// Helper function to process filter values\nconst processFilterValue = (value) => {\n if (typeof value === 'string' && !isNaN(Number(value))) {\n return Number(value)\n }\n return value\n}\n\n// Helper function to apply filters to a query\nconst applyFilters = (queryRef, filters) => {\n if (!filters) return queryRef\n \n const parsedFilters = safeJSONParse(filters)\n \n if (Array.isArray(parsedFilters)) {\n const filterTree = normalizeFilterTree(parsedFilters)\n \n const applyCondition = (condition) => {\n const field = condition.source\n const value = condition.destination\n const operand = condition.operand\n \n if (Array.isArray(value)) {\n const processedValues = value.map(processFilterValue)\n if (operand === '!=') {\n queryRef = queryRef.not(field, 'in', processedValues)\n } else {\n queryRef = queryRef.in(field, processedValues)\n }\n return\n }\n \n const processedValue = processFilterValue(value)\n \n // Handle null values\n if (processedValue === null) {\n if (operand === '=') {\n queryRef = queryRef.is(field, null)\n } else if (operand === '!=') {\n queryRef = queryRef.not(field, 'is', null)\n }\n return\n }\n \n // Map operand to Supabase methods\n switch (operand) {\n case '!=':\n queryRef = queryRef.neq(field, processedValue)\n break\n case '>':\n queryRef = queryRef.gt(field, processedValue)\n break\n case '>=':\n queryRef = queryRef.gte(field, processedValue)\n break\n case '<':\n queryRef = queryRef.lt(field, processedValue)\n break\n case '<=':\n queryRef = queryRef.lte(field, processedValue)\n break\n default:\n queryRef = queryRef.eq(field, processedValue)\n }\n }\n \n // An OR group cannot be expressed by chaining (chained filters are ANDed),\n // so it is emitted as one PostgREST or=(...) expression instead.\n const POSTGREST_OPERANDS = { '=': 'eq', '!=': 'neq', '>': 'gt', '>=': 'gte', '<': 'lt', '<=': 'lte' }\n \n const toExprValue = (value) => {\n if (value === null || value === undefined) return 'null'\n const asString = String(value)\n return /[,.:()\"\\s]/.test(asString) ? '\"' + asString.replace(/\"/g, '\\\\\"') + '\"' : asString\n }\n \n const conditionToExpr = (condition) => {\n const field = condition.source\n const value = condition.destination\n const operand = condition.operand\n \n if (Array.isArray(value)) {\n const list = '(' + value.map((entry) => toExprValue(processFilterValue(entry))).join(',') + ')'\n return operand === '!=' ? field + '.not.in.' + list : field + '.in.' + list\n }\n \n const processedValue = processFilterValue(value)\n if (processedValue === null) {\n return operand === '!=' ? field + '.not.is.null' : field + '.is.null'\n }\n \n return field + '.' + (POSTGREST_OPERANDS[operand] || 'eq') + '.' + toExprValue(processedValue)\n }\n \n const nodeToExpr = (node) => {\n if (node.type === 'group') {\n const parts = node.children.map(nodeToExpr).filter(Boolean)\n if (parts.length === 0) return null\n if (parts.length === 1) return parts[0]\n return (node.operator === 'or' ? 'or' : 'and') + '(' + parts.join(',') + ')'\n }\n return conditionToExpr(node)\n }\n \n const applyNode = (node) => {\n if (node.type !== 'group') {\n applyCondition(node)\n return\n }\n if (node.operator === 'and') {\n node.children.forEach(applyNode)\n return\n }\n if (node.children.length === 1) {\n applyNode(node.children[0])\n return\n }\n const parts = node.children.map(nodeToExpr).filter(Boolean)\n if (parts.length > 0) {\n queryRef = queryRef.or(parts.join(','))\n }\n }\n \n if (filterTree) {\n applyNode(filterTree)\n }\n } else {\n // Old format: object with key-value pairs (backward compatibility)\n Object.entries(parsedFilters).forEach(([key, value]) => {\n if (Array.isArray(value)) {\n const processedValues = value.map(processFilterValue)\n queryRef = queryRef.in(key, processedValues)\n } else if (typeof value === 'object' && value !== null) {\n const operator = Object.keys(value)[0]\n let operatorValue = value[operator]\n if (typeof operatorValue === 'string' && !isNaN(Number(operatorValue))) {\n operatorValue = Number(operatorValue)\n }\n switch (operator) {\n case 'eq': queryRef = queryRef.eq(key, operatorValue); break\n case 'neq': queryRef = queryRef.neq(key, operatorValue); break\n case 'gt': queryRef = queryRef.gt(key, operatorValue); break\n case 'gte': queryRef = queryRef.gte(key, operatorValue); break\n case 'lt': queryRef = queryRef.lt(key, operatorValue); break\n case 'lte': queryRef = queryRef.lte(key, operatorValue); break\n case 'like': queryRef = queryRef.like(key, operatorValue); break\n case 'ilike': queryRef = queryRef.ilike(key, operatorValue); break\n case 'in': queryRef = queryRef.in(key, operatorValue); break\n default: queryRef = queryRef.eq(key, operatorValue)\n }\n } else {\n let processedValue = value\n if (typeof value === 'string' && !isNaN(Number(value))) {\n processedValue = Number(value)\n }\n queryRef = queryRef.eq(key, processedValue)\n }\n })\n }\n \n return queryRef\n}\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\nexport default async function handler(req, res) {\n try {\n const client = getClient()\n const { query, queryColumns, select, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query\n \n let queryRef = client.from('").concat(tableName, "').select(select || '*')\n \n if (query) {\n let columns = []\n \n if (queryColumns) {\n // Use specified columns. Wrap non-arrays so a single column\n // passed as a bare string doesn't get iterated as chars.\n const parsed = safeJSONParse(queryColumns)\n columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])\n } else {\n // Fallback: Get text-searchable columns from a sample row\n try {\n const { data: sampleData, error: sampleError } = await client.from('").concat(tableName, "').select('*').limit(1).single()\n if (sampleError) {\n throw sampleError\n }\n if (sampleData) {\n // Filter out columns that are likely non-text types\n // Note: This is heuristic-based since we don't have schema info\n columns = Object.keys(sampleData).filter(col => {\n const value = sampleData[col]\n const colLower = col.toLowerCase()\n \n // Exclude common timestamp/date column names\n if (colLower.includes('_at') || colLower.includes('date') || colLower === 'timestamp') {\n return false\n }\n \n // Exclude if value is a number, boolean, null, or object (non-string)\n if (value === null || value === undefined) {\n return true // Include null columns, let the query handle it\n }\n \n const type = typeof value\n return type === 'string' // Only include string values\n })\n }\n } catch (schemaError) {\n console.warn('Failed to fetch sample row for column names:', schemaError.message)\n // Continue without search if we can't get columns\n }\n }\n \n if (columns.length > 0) {\n // PostgREST .or() DSL separates conditions with comma and uses\n // dot to split field / operator / value, so we wrap the pattern\n // in double quotes and backslash-escape any embedded quotes or\n // backslashes in the user's search term. Columns go through the\n // shared identifier sanitizer to reject injection. Note:\n // PostgREST .ilike. does not expose a LIKE ESCAPE clause, so\n // raw % / _ in the user input still act as wildcards in this\n // backend; the canvas preview path escapes them explicitly.\n const rawPattern = \"%\" + String(query) + \"%\"\n const escapedForPostgrest = rawPattern\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"')\n const searchPattern = '\"' + escapedForPostgrest + '\"'\n const orConditions = columns\n .map((col) => sanitizeSearchIdentifier(col) + \".ilike.\" + searchPattern)\n .join(\",\")\n queryRef = queryRef.or(orConditions)\n }\n }\n\n // Apply filters using helper function\n queryRef = applyFilters(queryRef, filters)\n \n // Handle sorts - new array format\n if (sorts) {\n const parsedSorts = safeJSONParse(sorts)\n if (Array.isArray(parsedSorts)) {\n parsedSorts.forEach((sort) => {\n if (sort.field) {\n queryRef = queryRef.order(sort.field, {\n ascending: !(sort.order || '').toLowerCase().startsWith('desc')\n })\n }\n })\n }\n } else if (sortBy) {\n queryRef = queryRef.order(sortBy, { ascending: !(sortOrder || '').toLowerCase().startsWith('desc') })\n }\n \n const limitValue = limit || perPage\n const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)\n \n if (offsetValue !== undefined && limitValue) {\n queryRef = queryRef.range(offsetValue, offsetValue + parseInt(limitValue) - 1)\n } else if (limitValue) {\n queryRef = queryRef.limit(parseInt(limitValue))\n }\n \n const { data, error } = await queryRef\n \n if (error) {\n return res.status(500).json({\n success: false,\n error: error.message,\n timestamp: Date.now()\n })\n }\n \n const safeData = JSON.parse(JSON.stringify(data, dateReplacer))\n \n return res.status(200).json({\n success: true,\n data: safeData,\n timestamp: Date.now()\n })\n } catch (error) {\n console.error('Supabase fetch error:', error)\n return res.status(500).json({\n success: false,\n error: error.message || 'Failed to fetch data',\n timestamp: Date.now()\n })\n }\n}\n");
|
|
95
|
+
return "import { createClient } from '@supabase/supabase-js'\n\nlet client = null\n\nconst getClient = () => {\n if (client) return client\n \n client = createClient(\n ".concat(JSON.stringify(supabaseUrl), ",\n ").concat((0, utils_1.replaceSecretReference)(apiKey), "\n )\n \n return client\n}\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateSearchEscapeHelpersCode)(), "\n").concat((0, search_relevance_1.generateSearchTermsCode)(false), "\n\n// Helper function to process filter values\nconst processFilterValue = (value) => {\n if (typeof value === 'string' && !isNaN(Number(value))) {\n return Number(value)\n }\n return value\n}\n\n// Helper function to apply filters to a query\nconst applyFilters = (queryRef, filters) => {\n if (!filters) return queryRef\n \n const parsedFilters = safeJSONParse(filters)\n \n if (Array.isArray(parsedFilters)) {\n const filterTree = normalizeFilterTree(parsedFilters)\n \n const applyCondition = (condition) => {\n const field = condition.source\n const value = condition.destination\n const operand = condition.operand\n \n if (Array.isArray(value)) {\n const processedValues = value.map(processFilterValue)\n if (operand === '!=') {\n queryRef = queryRef.not(field, 'in', processedValues)\n } else {\n queryRef = queryRef.in(field, processedValues)\n }\n return\n }\n \n const processedValue = processFilterValue(value)\n \n // Handle null values\n if (processedValue === null) {\n if (operand === '=') {\n queryRef = queryRef.is(field, null)\n } else if (operand === '!=') {\n queryRef = queryRef.not(field, 'is', null)\n }\n return\n }\n \n // Map operand to Supabase methods\n switch (operand) {\n case '!=':\n queryRef = queryRef.neq(field, processedValue)\n break\n case '>':\n queryRef = queryRef.gt(field, processedValue)\n break\n case '>=':\n queryRef = queryRef.gte(field, processedValue)\n break\n case '<':\n queryRef = queryRef.lt(field, processedValue)\n break\n case '<=':\n queryRef = queryRef.lte(field, processedValue)\n break\n default:\n queryRef = queryRef.eq(field, processedValue)\n }\n }\n \n // An OR group cannot be expressed by chaining (chained filters are ANDed),\n // so it is emitted as one PostgREST or=(...) expression instead.\n const POSTGREST_OPERANDS = { '=': 'eq', '!=': 'neq', '>': 'gt', '>=': 'gte', '<': 'lt', '<=': 'lte' }\n \n const toExprValue = (value) => {\n if (value === null || value === undefined) return 'null'\n const asString = String(value)\n return /[,.:()\"\\s]/.test(asString) ? '\"' + asString.replace(/\"/g, '\\\\\"') + '\"' : asString\n }\n \n const conditionToExpr = (condition) => {\n const field = condition.source\n const value = condition.destination\n const operand = condition.operand\n \n if (Array.isArray(value)) {\n const list = '(' + value.map((entry) => toExprValue(processFilterValue(entry))).join(',') + ')'\n return operand === '!=' ? field + '.not.in.' + list : field + '.in.' + list\n }\n \n const processedValue = processFilterValue(value)\n if (processedValue === null) {\n return operand === '!=' ? field + '.not.is.null' : field + '.is.null'\n }\n \n return field + '.' + (POSTGREST_OPERANDS[operand] || 'eq') + '.' + toExprValue(processedValue)\n }\n \n const nodeToExpr = (node) => {\n if (node.type === 'group') {\n const parts = node.children.map(nodeToExpr).filter(Boolean)\n if (parts.length === 0) return null\n if (parts.length === 1) return parts[0]\n return (node.operator === 'or' ? 'or' : 'and') + '(' + parts.join(',') + ')'\n }\n return conditionToExpr(node)\n }\n \n const applyNode = (node) => {\n if (node.type !== 'group') {\n applyCondition(node)\n return\n }\n if (node.operator === 'and') {\n node.children.forEach(applyNode)\n return\n }\n if (node.children.length === 1) {\n applyNode(node.children[0])\n return\n }\n const parts = node.children.map(nodeToExpr).filter(Boolean)\n if (parts.length > 0) {\n queryRef = queryRef.or(parts.join(','))\n }\n }\n \n if (filterTree) {\n applyNode(filterTree)\n }\n } else {\n // Old format: object with key-value pairs (backward compatibility)\n Object.entries(parsedFilters).forEach(([key, value]) => {\n if (Array.isArray(value)) {\n const processedValues = value.map(processFilterValue)\n queryRef = queryRef.in(key, processedValues)\n } else if (typeof value === 'object' && value !== null) {\n const operator = Object.keys(value)[0]\n let operatorValue = value[operator]\n if (typeof operatorValue === 'string' && !isNaN(Number(operatorValue))) {\n operatorValue = Number(operatorValue)\n }\n switch (operator) {\n case 'eq': queryRef = queryRef.eq(key, operatorValue); break\n case 'neq': queryRef = queryRef.neq(key, operatorValue); break\n case 'gt': queryRef = queryRef.gt(key, operatorValue); break\n case 'gte': queryRef = queryRef.gte(key, operatorValue); break\n case 'lt': queryRef = queryRef.lt(key, operatorValue); break\n case 'lte': queryRef = queryRef.lte(key, operatorValue); break\n case 'like': queryRef = queryRef.like(key, operatorValue); break\n case 'ilike': queryRef = queryRef.ilike(key, operatorValue); break\n case 'in': queryRef = queryRef.in(key, operatorValue); break\n default: queryRef = queryRef.eq(key, operatorValue)\n }\n } else {\n let processedValue = value\n if (typeof value === 'string' && !isNaN(Number(value))) {\n processedValue = Number(value)\n }\n queryRef = queryRef.eq(key, processedValue)\n }\n })\n }\n \n return queryRef\n}\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\nexport default async function handler(req, res) {\n try {\n const client = getClient()\n const { query, queryColumns, select, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query\n \n let queryRef = client.from('").concat(tableName, "').select(select || '*')\n \n if (query) {\n let columns = []\n \n if (queryColumns) {\n // Use specified columns. Wrap non-arrays so a single column\n // passed as a bare string doesn't get iterated as chars.\n const parsed = safeJSONParse(queryColumns)\n columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])\n } else {\n // Fallback: Get text-searchable columns from a sample row\n try {\n const { data: sampleData, error: sampleError } = await client.from('").concat(tableName, "').select('*').limit(1).single()\n if (sampleError) {\n throw sampleError\n }\n if (sampleData) {\n // Filter out columns that are likely non-text types\n // Note: This is heuristic-based since we don't have schema info\n columns = Object.keys(sampleData).filter(col => {\n const value = sampleData[col]\n const colLower = col.toLowerCase()\n \n // Exclude common timestamp/date column names\n if (colLower.includes('_at') || colLower.includes('date') || colLower === 'timestamp') {\n return false\n }\n \n // Exclude if value is a number, boolean, null, or object (non-string)\n if (value === null || value === undefined) {\n return true // Include null columns, let the query handle it\n }\n \n const type = typeof value\n return type === 'string' // Only include string values\n })\n }\n } catch (schemaError) {\n console.warn('Failed to fetch sample row for column names:', schemaError.message)\n // Continue without search if we can't get columns\n }\n }\n \n if (columns.length > 0) {\n // PostgREST .or() DSL separates conditions with comma and uses\n // dot to split field / operator / value, so we wrap the pattern\n // in double quotes and backslash-escape any embedded quotes or\n // backslashes in the user's search term. Columns go through the\n // shared identifier sanitizer to reject injection. Note:\n // PostgREST .ilike. does not expose a LIKE ESCAPE clause, so\n // raw % / _ in the user input still act as wildcards in this\n // backend; the canvas preview path escapes them explicitly. Each\n // typed word gets its own .or(), and PostgREST ANDs them.\n searchTerms(query).forEach((word) => {\n const rawPattern = \"%\" + word + \"%\"\n const escapedForPostgrest = rawPattern\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"')\n const searchPattern = '\"' + escapedForPostgrest + '\"'\n const orConditions = columns\n .map((col) => sanitizeSearchIdentifier(col) + \".ilike.\" + searchPattern)\n .join(\",\")\n queryRef = queryRef.or(orConditions)\n })\n }\n }\n\n // Apply filters using helper function\n queryRef = applyFilters(queryRef, filters)\n \n // Handle sorts - new array format\n if (sorts) {\n const parsedSorts = safeJSONParse(sorts)\n if (Array.isArray(parsedSorts)) {\n parsedSorts.forEach((sort) => {\n if (sort.field) {\n queryRef = queryRef.order(sort.field, {\n ascending: !(sort.order || '').toLowerCase().startsWith('desc')\n })\n }\n })\n }\n } else if (sortBy) {\n queryRef = queryRef.order(sortBy, { ascending: !(sortOrder || '').toLowerCase().startsWith('desc') })\n }\n \n const limitValue = limit || perPage\n const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)\n \n if (offsetValue !== undefined && limitValue) {\n queryRef = queryRef.range(offsetValue, offsetValue + parseInt(limitValue) - 1)\n } else if (limitValue) {\n queryRef = queryRef.limit(parseInt(limitValue))\n }\n \n const { data, error } = await queryRef\n \n if (error) {\n return res.status(500).json({\n success: false,\n error: error.message,\n timestamp: Date.now()\n })\n }\n \n const safeData = JSON.parse(JSON.stringify(data, dateReplacer))\n \n return res.status(200).json({\n success: true,\n data: safeData,\n timestamp: Date.now()\n })\n } catch (error) {\n console.error('Supabase fetch error:', error)\n return res.status(500).json({\n success: false,\n error: error.message || 'Failed to fetch data',\n timestamp: Date.now()\n })\n }\n}\n");
|
|
95
96
|
};
|
|
96
97
|
exports.generateSupabaseFetcher = generateSupabaseFetcher;
|
|
97
98
|
// tslint:disable-next-line:variable-name
|
|
98
99
|
var generateSupabaseCountFetcher = function (_config, tableName) {
|
|
99
|
-
return "\nasync function getCount(req, res) {\n const supabase = getClient()\n\n try {\n const { query, queryColumns, filters } = req.query\n let countQuery = supabase.from('".concat(tableName, "').select('*', { count: 'exact', head: true })\n\n if (query) {\n let columns = []\n \n if (queryColumns) {\n // Use specified columns\n const parsed = safeJSONParse(queryColumns)\n columns = Array.isArray(parsed) ? parsed : [parsed]\n } else {\n // Fallback: Get text-searchable columns from a sample row\n try {\n const { data: sampleData, error: sampleError } = await supabase.from('").concat(tableName, "').select('*').limit(1).single()\n if (sampleError) {\n throw sampleError\n }\n if (sampleData) {\n // Filter out columns that are likely non-text types\n // Note: This is heuristic-based since we don't have schema info\n columns = Object.keys(sampleData).filter(col => {\n const value = sampleData[col]\n const colLower = col.toLowerCase()\n \n // Exclude common timestamp/date column names\n if (colLower.includes('_at') || colLower.includes('date') || colLower === 'timestamp') {\n return false\n }\n \n // Exclude if value is a number, boolean, null, or object (non-string)\n if (value === null || value === undefined) {\n return true // Include null columns, let the query handle it\n }\n \n const type = typeof value\n return type === 'string' // Only include string values\n })\n }\n } catch (schemaError) {\n console.warn('Failed to fetch sample row for column names:', schemaError.message)\n // Continue without search if we can't get columns\n }\n }\n \n if (columns.length > 0) {\n // Mirror the fetch handler: sanitize identifiers, wrap the\n // pattern in double quotes for PostgREST .or() DSL, and escape\n // backslashes / quotes in the user's search term.\n const rawPattern = \"%\" +
|
|
100
|
+
return "\nasync function getCount(req, res) {\n const supabase = getClient()\n\n try {\n const { query, queryColumns, filters } = req.query\n let countQuery = supabase.from('".concat(tableName, "').select('*', { count: 'exact', head: true })\n\n if (query) {\n let columns = []\n \n if (queryColumns) {\n // Use specified columns\n const parsed = safeJSONParse(queryColumns)\n columns = Array.isArray(parsed) ? parsed : [parsed]\n } else {\n // Fallback: Get text-searchable columns from a sample row\n try {\n const { data: sampleData, error: sampleError } = await supabase.from('").concat(tableName, "').select('*').limit(1).single()\n if (sampleError) {\n throw sampleError\n }\n if (sampleData) {\n // Filter out columns that are likely non-text types\n // Note: This is heuristic-based since we don't have schema info\n columns = Object.keys(sampleData).filter(col => {\n const value = sampleData[col]\n const colLower = col.toLowerCase()\n \n // Exclude common timestamp/date column names\n if (colLower.includes('_at') || colLower.includes('date') || colLower === 'timestamp') {\n return false\n }\n \n // Exclude if value is a number, boolean, null, or object (non-string)\n if (value === null || value === undefined) {\n return true // Include null columns, let the query handle it\n }\n \n const type = typeof value\n return type === 'string' // Only include string values\n })\n }\n } catch (schemaError) {\n console.warn('Failed to fetch sample row for column names:', schemaError.message)\n // Continue without search if we can't get columns\n }\n }\n \n if (columns.length > 0) {\n // Mirror the fetch handler: sanitize identifiers, wrap the\n // pattern in double quotes for PostgREST .or() DSL, and escape\n // backslashes / quotes in the user's search term, one .or() per word.\n searchTerms(query).forEach((word) => {\n const rawPattern = \"%\" + word + \"%\"\n const escapedForPostgrest = rawPattern\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"')\n const searchPattern = '\"' + escapedForPostgrest + '\"'\n const orConditions = columns\n .map((col) => sanitizeSearchIdentifier(col) + \".ilike.\" + searchPattern)\n .join(\",\")\n countQuery = countQuery.or(orConditions)\n })\n }\n }\n\n // Apply filters using helper function\n countQuery = applyFilters(countQuery, filters)\n\n const { count, error } = await countQuery\n \n if (error) throw error\n\n return res.status(200).json({\n success: true,\n count: count || 0,\n timestamp: Date.now()\n })\n } catch (error) {\n console.error('Error getting count:', error)\n return res.status(500).json({\n success: false,\n error: error.message || 'Failed to get count',\n timestamp: Date.now()\n })\n }\n}\n");
|
|
100
101
|
};
|
|
101
102
|
exports.generateSupabaseCountFetcher = generateSupabaseCountFetcher;
|
|
102
103
|
//# sourceMappingURL=supabase.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supabase.js","sourceRoot":"","sources":["../../../src/fetchers/supabase.ts"],"names":[],"mappings":";;;AAAA,kCAMiB;
|
|
1
|
+
{"version":3,"file":"supabase.js","sourceRoot":"","sources":["../../../src/fetchers/supabase.ts"],"names":[],"mappings":";;;AAAA,kCAMiB;AACjB,wDAA6D;AAEtD,IAAM,sBAAsB,GAAG,UACpC,MAA+B;IAE/B,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;QACzC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAA;KAClE;IAED,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,EAAE;QACjE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAA;KAC7D;IAED,IAAI;QACF,IAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;QACvC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE;YACpF,OAAO,CAAC,IAAI,CAAC,oEAAoE,CAAC,CAAA;SACnF;KACF;IAAC,WAAM;QACN,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,6BAA6B,EAAE,CAAA;KAChE;IAED,IAAI,CAAC,MAAM,CAAC,cAAc,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE;QAClD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,+DAA+D;SACvE,CAAA;KACF;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAC1B,CAAC,CAAA;AA5BY,QAAA,sBAAsB,0BA4BlC;AAUM,IAAM,uBAAuB,GAAG,UACrC,MAA+B,EAC/B,SAAiB;IAEjB,IAAM,cAAc,GAAG,MAAwB,CAAA;IAC/C,IAAM,WAAW,GAAG,cAAc,CAAC,WAAW,CAAA;IAC9C,IAAM,MAAM,GAAG,cAAc,CAAC,cAAc,IAAI,cAAc,CAAC,YAAY,CAAA;IAC3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA2DA;IACA,OAAO,iLAQH,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,oBAC3B,IAAA,8BAAsB,EAAC,MAAM,CAAC,8CAMlC,IAAA,iCAAyB,GAAE,iBAE3B,IAAA,qCAA6B,GAAE,iBAE/B,IAAA,uCAA+B,GAAE,eACjC,IAAA,0CAAuB,EAAC,KAAK,CAAC,irLAqK9B,IAAA,iCAAyB,GAAE,2QAOK,SAAS,qjBAaqC,SAAS,ynIA6GxF,CAAA;AACD,CAAC,CAAA;AA9XY,QAAA,uBAAuB,2BA8XnC;AAED,yCAAyC;AAClC,IAAM,4BAA4B,GAAG,UAAC,OAAY,EAAE,SAAiB;IAC1E,OAAO,wLAM6B,SAAS,kdAYmC,SAAS,6jFAsE1F,CAAA;AACD,CAAC,CAAA;AA1FY,QAAA,4BAA4B,gCA0FxC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"teleport.d.ts","sourceRoot":"","sources":["../../../src/fetchers/teleport.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"teleport.d.ts","sourceRoot":"","sources":["../../../src/fetchers/teleport.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,sBAAsB,EAC5B,MAAM,oBAAoB,CAAA;AA2C3B,eAAO,MAAM,sBAAsB,WACzB,OAAO,MAAM,EAAE,OAAO,CAAC,KAC9B;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAMpC,CAAA;AAED,eAAO,MAAM,uBAAuB,WAC1B,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,qBACC,sBAAsB,KACvC,MAycF,CAAA;AAED,eAAO,MAAM,4BAA4B,WAC/B,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,KAChB,MA0FF,CAAA"}
|
|
@@ -12,6 +12,7 @@ var request_locale_1 = require("../request-locale");
|
|
|
12
12
|
var table_access_guard_1 = require("./utils/table-access-guard");
|
|
13
13
|
var request_sql_guards_1 = require("./utils/request-sql-guards");
|
|
14
14
|
var array_overlap_sql_1 = require("../array-overlap-sql");
|
|
15
|
+
var search_relevance_1 = require("../search-relevance");
|
|
15
16
|
var pg_client_import_1 = require("./pg-client-import");
|
|
16
17
|
var DEFAULT_ENV_KEYS = {
|
|
17
18
|
host: 'TELEPORT_DB_HOST',
|
|
@@ -59,9 +60,9 @@ var generateTeleportFetcher = function (config, tableName, transformOptions) {
|
|
|
59
60
|
? "key: ".concat(resolveEnvReference(dbConfig.sslConfig.key, 'TELEPORT_DB_SSL_KEY'), ",")
|
|
60
61
|
: '', "\n rejectUnauthorized: false\n }")
|
|
61
62
|
: "(process.env.".concat(DEFAULT_ENV_KEYS.ssl, " === 'false' ? false : process.env.").concat(DEFAULT_ENV_KEYS.ssl, " === 'true' ? { rejectUnauthorized: false } : undefined)");
|
|
62
|
-
return "".concat(pg_client_import_1.PG_CLIENT_IMPORT, "\n\n").concat((0, table_access_guard_1.generateTableAccessPreamble)(transformOptions.trustedReaderRoles || []), "\nfunction normalizePostgresConnectionString(connectionString) {\n if (!connectionString || typeof connectionString !== 'string') return connectionString;\n if (/^postgresql:\\/(?!\\/)/i.test(connectionString)) {\n return connectionString.replace(/^postgresql:\\//i, 'postgresql://');\n }\n return connectionString;\n}\n\nfunction stripSslQueryParamsFromConnectionString(connectionString) {\n if (!connectionString || typeof connectionString !== 'string') return connectionString;\n try {\n var u = new URL(connectionString.replace(/^postgresql:/i, 'postgres:'));\n u.searchParams.delete('sslmode');\n u.searchParams.delete('ssl');\n u.searchParams.delete('sslrootcert');\n u.searchParams.delete('sslcert');\n u.searchParams.delete('sslkey');\n return u.toString().replace(/^postgres:/i, 'postgresql:');\n } catch (e) {\n return connectionString;\n }\n}\n\nconst getClient = () => {\n var ssl = ").concat(sslCode, ";\n var connStr = process.env.TELEPORT_DB_CONNECTION_STRING;\n if (connStr) {\n connStr = normalizePostgresConnectionString(connStr);\n }\n if (ssl === false && connStr) {\n connStr = stripSslQueryParamsFromConnectionString(connStr);\n }\n if (connStr) {\n return new Client(Object.assign(\n { connectionString: connStr },\n ssl !== undefined ? { ssl: ssl } : {}\n ));\n }\n return new Client(Object.assign(\n {\n host: ").concat(hostRef, ",\n port: parseInt(").concat(portRef, " || '5432', 10),\n user: ").concat(userRef, ",\n password: ").concat(passwordRef, ",\n database: ").concat(databaseRef, ",\n },\n ssl !== undefined ? { ssl: ssl } : {}\n ));\n}\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateSearchEscapeHelpersCode)(), "\n").concat((0, array_overlap_sql_1.generateArrayOverlapSqlCode)(), "\n").concat((0, transformations_1.getTransformationCode)(tableName, transformOptions), "\n").concat((0, transformations_1.getTransformWrapperCode)(tableName, transformOptions), "\n").concat((0, request_sql_guards_1.generateRequestSqlGuardsCode)(), "\nconst processFilters = (filters, conditions, queryParams, paramIndex) => {\n if (!filters) return paramIndex\n \n const filterTree = normalizeFilterTree(safeJSONParse(filters))\n if (!filterTree) return paramIndex\n \n const buildCondition = (condition) => {\n const field = condition.source\n const normalizedCondition = normalizeInOperand(condition.operand, condition.destination)\n if (normalizedCondition === null) return null\n const value = normalizedCondition.value\n const operand = normalizedCondition.operand\n \n // The products table's virtual fields (effective price, rating bucket,\n // on-sale flag, variant axes) are whole clauses, not columns - so they are\n // answered before any branch below can interpolate the field name raw.\n const productClause = productFilterClause(field, operand, value, (param) => {\n queryParams.push(param)\n return '$' + paramIndex++\n })\n if (productClause !== null) return productClause\n\n // A translatable column is compared against the value in every language,\n // so a localized slug resolves under any locale - see localized-columns.ts.\n const localizedClause = localizedEqualityClause(field, operand, value, (param) => {\n queryParams.push(param)\n return '$' + paramIndex++\n })\n if (localizedClause !== null) return localizedClause\n assertRequestColumn(field, 'filter field')\n \n if (Array.isArray(value)) {\n if (value.length === 0) return null\n if (operand === 'array_overlap') {\n // The row's list (see array-overlap-sql.ts) shares any value with the set.\n const clause = arrayOverlapSql((localizedColumnList(field) || [field]), '$' + paramIndex)\n queryParams.push(value.map((entry) => String(entry)))\n paramIndex++\n return clause\n }\n const placeholders = value.map(() => `$${paramIndex++}`)\n queryParams.push(...value)\n return operand === '!='\n ? `${field} NOT IN (${placeholders.join(', ')})`\n : `${field} IN (${placeholders.join(', ')})`\n }\n \n if (operand === 'array_overlap') {\n if (value === '' || value === null || value === undefined) return null\n // A single comma-joined string (the multi-select Category Filter's\n // ?categoryFilter=a,b,c) expands to multiple ids; one id stays one.\n const overlapValues = String(value).split(',').map((entry) => entry.trim()).filter(Boolean)\n if (overlapValues.length === 0) return null\n const clause = arrayOverlapSql((localizedColumnList(field) || [field]), '$' + paramIndex)\n queryParams.push(overlapValues)\n paramIndex++\n return clause\n }\n \n if (value === null) {\n if (operand === '=') return `${field} IS NULL`\n if (operand === '!=') return `${field} IS NOT NULL`\n return null\n }\n \n const validOps = ['=', '!=', '>', '<', '>=', '<=']\n const sqlOperator = validOps.includes(operand) ? operand : '='\n const clause = `${field} ${sqlOperator} $${paramIndex}`\n queryParams.push(value)\n paramIndex++\n return clause\n }\n \n const clause = buildFilterTreeClause(filterTree, buildCondition)\n if (clause) conditions.push(clause)\n \n return paramIndex\n}\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\n").concat((0, product_price_sort_1.generateSortFieldSqlHelper)(tableName), "\n\n").concat((0, product_price_sort_1.generateSortFallbackFieldHelper)(tableName), "\n\n").concat((0, row_order_1.generateRowOrderTiebreakHelper)((0, row_order_1.rowIdentityColumn)(config, tableName)), "\n\n").concat((0, product_filter_fields_1.generateProductFilterClauseHelper)(tableName), "\n\n").concat((0, localized_columns_1.generateLocalizedColumnsHelper)(localizedColumns), "\n\n// Matches DDL / dangerous statements the raw-query branch should refuse.\n// Keep this list conservative \u2014 anything destructive or schema-changing is\n// out of scope for a client-triggered fetch. SELECT and CTEs (WITH ...) are\n// the only shapes consumers legitimately need.\nconst BLOCKED_RAW_QUERY_PATTERNS = [\n /\\bcreate\\s+(?:temporary\\s+|temp\\s+|unlogged\\s+|global\\s+|local\\s+)?table\\b/i,\n /\\bcreate\\s+(?:unique\\s+)?index\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?(?:materialized\\s+)?view\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?trigger\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?(?:aggregate\\s+)?function\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?procedure\\b/i,\n /\\bcreate\\s+(?:database|schema|sequence|extension|type|role|user)\\b/i,\n /\\bdrop\\s+(?:table|view|index|schema|database|sequence|trigger|function|procedure|role|user|extension|type|materialized)\\b/i,\n /\\balter\\s+(?:table|view|index|schema|database|sequence|role|user|system)\\b/i,\n /\\btruncate\\b/i,\n /\\bgrant\\b/i,\n /\\brevoke\\b/i,\n /\\binsert\\b/i,\n /\\bupdate\\b/i,\n /\\bdelete\\b/i,\n /\\bcopy\\b/i,\n /\\bvacuum\\b/i,\n /\\breindex\\b/i,\n /\\bcluster\\b/i,\n]\n\nfunction assertRawQuerySafe(rawQuery) {\n if (typeof rawQuery !== 'string' || rawQuery.length === 0) {\n throw new Error('rawQuery must be a non-empty string')\n }\n // Reject multi-statement payloads \u2014 only single SELECT / WITH statements.\n // A trailing semicolon is tolerated but any content after it fails.\n var trimmed = rawQuery.trim().replace(/;\\s*$/, '')\n if (trimmed.indexOf(';') !== -1) {\n throw new Error('rawQuery must contain exactly one statement')\n }\n for (var i = 0; i < BLOCKED_RAW_QUERY_PATTERNS.length; i++) {\n if (BLOCKED_RAW_QUERY_PATTERNS[i].test(trimmed)) {\n throw new Error('rawQuery contains a blocked statement')\n }\n }\n}\n\nexport default async function handler(req, res) {\n").concat((0, table_access_guard_1.generateReadGuardCall)(tableName, 'req.query && req.query.rawQuery'), "\n const client = getClient()\n\n try {\n await client.connect()\n ").concat(schema ? "await client.query('SET search_path TO ".concat(schema, "')") : '', "\n\n // If the caller supplied a rawQuery, run it verbatim (after a safety\n // guard). The schema-driven branch below builds `SELECT * FROM ").concat(tableName, "`\n // with optional filters \u2014 it's the default read path and ignores\n // rawQuery. Page-load workflows and other consumers that need a JOIN\n // or a user-scoped filter pass their fully-rendered SQL here via\n // `fetchData({ rawQuery })` and expect it to execute verbatim.\n if (req.query && typeof req.query.rawQuery === 'string' && req.query.rawQuery.length > 0) {\n assertRawQuerySafe(req.query.rawQuery)\n // A page's own statement, read for a visitor, runs over the rows a\n // visitor is served of every narrowed table it names.\n const rawSql = __rowAccess.narrowRaw ? __brpNarrowRawQuery(req.query.rawQuery) : req.query.rawQuery\n const rawResult = await client.query(rawSql)\n const rawRows = Array.isArray(rawResult?.rows) ? rawResult.rows : []\n const rawPlain = rawRows.map((row) =>\n row && typeof row.toJSON === 'function' ? row.toJSON() : row\n )\n const rawSafe = JSON.parse(JSON.stringify(rawPlain, dateReplacer))\n return res.status(200).json({\n success: true,\n // Every row here ends up in a browser (a page's props or a client\n // refetch), so no credential column survives, whatever joined it.\n data: __taWithoutCredentials(\n __rowAccess.narrowRaw ? __brpStripHiddenOfRaw(req.query.rawQuery, rawSafe) : rawSafe,\n null\n ),\n timestamp: Date.now()\n })\n }\n\n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query\n // A visitor never filters, sorts or searches by a column it is not served.\n if (__rowPolicy) __brpAssertVisibleFields(__rowPolicy, req.query)\n // The language the page is in - what a translatable sort orders by.\n const requestLocale = typeof req.query.").concat(request_locale_1.REQUEST_LOCALE_PARAM, " === 'string' ? req.query.").concat(request_locale_1.REQUEST_LOCALE_PARAM, " : null\n \n const conditions = []\n const queryParams = []\n let paramIndex = 1\n \n if (query) {\n let columns = []\n \n if (queryColumns) {\n const parsed = safeJSONParse(queryColumns)\n columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])\n } else {\n try {\n const schemaQuery = `\n SELECT column_name \n FROM information_schema.columns \n WHERE table_name = $1\n ").concat(schema ? "AND table_schema = $2" : '', "\n ORDER BY ordinal_position\n `\n const schemaParams = ").concat(schema
|
|
63
|
+
return "".concat(pg_client_import_1.PG_CLIENT_IMPORT, "\n\n").concat((0, table_access_guard_1.generateTableAccessPreamble)(transformOptions.trustedReaderRoles || []), "\nfunction normalizePostgresConnectionString(connectionString) {\n if (!connectionString || typeof connectionString !== 'string') return connectionString;\n if (/^postgresql:\\/(?!\\/)/i.test(connectionString)) {\n return connectionString.replace(/^postgresql:\\//i, 'postgresql://');\n }\n return connectionString;\n}\n\nfunction stripSslQueryParamsFromConnectionString(connectionString) {\n if (!connectionString || typeof connectionString !== 'string') return connectionString;\n try {\n var u = new URL(connectionString.replace(/^postgresql:/i, 'postgres:'));\n u.searchParams.delete('sslmode');\n u.searchParams.delete('ssl');\n u.searchParams.delete('sslrootcert');\n u.searchParams.delete('sslcert');\n u.searchParams.delete('sslkey');\n return u.toString().replace(/^postgres:/i, 'postgresql:');\n } catch (e) {\n return connectionString;\n }\n}\n\nconst getClient = () => {\n var ssl = ").concat(sslCode, ";\n var connStr = process.env.TELEPORT_DB_CONNECTION_STRING;\n if (connStr) {\n connStr = normalizePostgresConnectionString(connStr);\n }\n if (ssl === false && connStr) {\n connStr = stripSslQueryParamsFromConnectionString(connStr);\n }\n if (connStr) {\n return new Client(Object.assign(\n { connectionString: connStr },\n ssl !== undefined ? { ssl: ssl } : {}\n ));\n }\n return new Client(Object.assign(\n {\n host: ").concat(hostRef, ",\n port: parseInt(").concat(portRef, " || '5432', 10),\n user: ").concat(userRef, ",\n password: ").concat(passwordRef, ",\n database: ").concat(databaseRef, ",\n },\n ssl !== undefined ? { ssl: ssl } : {}\n ));\n}\n\n").concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateSearchEscapeHelpersCode)(), "\n").concat((0, search_relevance_1.generateSearchSqlHelpersCode)(search_relevance_1.PG_SEARCH_SQL), "\n").concat((0, array_overlap_sql_1.generateArrayOverlapSqlCode)(), "\n").concat((0, transformations_1.getTransformationCode)(tableName, transformOptions), "\n").concat((0, transformations_1.getTransformWrapperCode)(tableName, transformOptions), "\n").concat((0, request_sql_guards_1.generateRequestSqlGuardsCode)(), "\nconst processFilters = (filters, conditions, queryParams, paramIndex) => {\n if (!filters) return paramIndex\n \n const filterTree = normalizeFilterTree(safeJSONParse(filters))\n if (!filterTree) return paramIndex\n \n const buildCondition = (condition) => {\n const field = condition.source\n const normalizedCondition = normalizeInOperand(condition.operand, condition.destination)\n if (normalizedCondition === null) return null\n const value = normalizedCondition.value\n const operand = normalizedCondition.operand\n \n // The products table's virtual fields (effective price, rating bucket,\n // on-sale flag, variant axes) are whole clauses, not columns - so they are\n // answered before any branch below can interpolate the field name raw.\n const productClause = productFilterClause(field, operand, value, (param) => {\n queryParams.push(param)\n return '$' + paramIndex++\n })\n if (productClause !== null) return productClause\n\n // A translatable column is compared against the value in every language,\n // so a localized slug resolves under any locale - see localized-columns.ts.\n const localizedClause = localizedEqualityClause(field, operand, value, (param) => {\n queryParams.push(param)\n return '$' + paramIndex++\n })\n if (localizedClause !== null) return localizedClause\n assertRequestColumn(field, 'filter field')\n \n if (Array.isArray(value)) {\n if (value.length === 0) return null\n if (operand === 'array_overlap') {\n // The row's list (see array-overlap-sql.ts) shares any value with the set.\n const clause = arrayOverlapSql((localizedColumnList(field) || [field]), '$' + paramIndex)\n queryParams.push(value.map((entry) => String(entry)))\n paramIndex++\n return clause\n }\n const placeholders = value.map(() => `$${paramIndex++}`)\n queryParams.push(...value)\n return operand === '!='\n ? `${field} NOT IN (${placeholders.join(', ')})`\n : `${field} IN (${placeholders.join(', ')})`\n }\n \n if (operand === 'array_overlap') {\n if (value === '' || value === null || value === undefined) return null\n // A single comma-joined string (the multi-select Category Filter's\n // ?categoryFilter=a,b,c) expands to multiple ids; one id stays one.\n const overlapValues = String(value).split(',').map((entry) => entry.trim()).filter(Boolean)\n if (overlapValues.length === 0) return null\n const clause = arrayOverlapSql((localizedColumnList(field) || [field]), '$' + paramIndex)\n queryParams.push(overlapValues)\n paramIndex++\n return clause\n }\n \n if (value === null) {\n if (operand === '=') return `${field} IS NULL`\n if (operand === '!=') return `${field} IS NOT NULL`\n return null\n }\n \n const validOps = ['=', '!=', '>', '<', '>=', '<=']\n const sqlOperator = validOps.includes(operand) ? operand : '='\n const clause = `${field} ${sqlOperator} $${paramIndex}`\n queryParams.push(value)\n paramIndex++\n return clause\n }\n \n const clause = buildFilterTreeClause(filterTree, buildCondition)\n if (clause) conditions.push(clause)\n \n return paramIndex\n}\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\n").concat((0, product_price_sort_1.generateSortFieldSqlHelper)(tableName), "\n\n").concat((0, product_price_sort_1.generateSortFallbackFieldHelper)(tableName), "\n\n").concat((0, row_order_1.generateRowOrderTiebreakHelper)((0, row_order_1.rowIdentityColumn)(config, tableName)), "\n\n").concat((0, product_filter_fields_1.generateProductFilterClauseHelper)(tableName), "\n\n").concat((0, localized_columns_1.generateLocalizedColumnsHelper)(localizedColumns), "\n\n// Matches DDL / dangerous statements the raw-query branch should refuse.\n// Keep this list conservative \u2014 anything destructive or schema-changing is\n// out of scope for a client-triggered fetch. SELECT and CTEs (WITH ...) are\n// the only shapes consumers legitimately need.\nconst BLOCKED_RAW_QUERY_PATTERNS = [\n /\\bcreate\\s+(?:temporary\\s+|temp\\s+|unlogged\\s+|global\\s+|local\\s+)?table\\b/i,\n /\\bcreate\\s+(?:unique\\s+)?index\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?(?:materialized\\s+)?view\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?trigger\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?(?:aggregate\\s+)?function\\b/i,\n /\\bcreate\\s+(?:or\\s+replace\\s+)?procedure\\b/i,\n /\\bcreate\\s+(?:database|schema|sequence|extension|type|role|user)\\b/i,\n /\\bdrop\\s+(?:table|view|index|schema|database|sequence|trigger|function|procedure|role|user|extension|type|materialized)\\b/i,\n /\\balter\\s+(?:table|view|index|schema|database|sequence|role|user|system)\\b/i,\n /\\btruncate\\b/i,\n /\\bgrant\\b/i,\n /\\brevoke\\b/i,\n /\\binsert\\b/i,\n /\\bupdate\\b/i,\n /\\bdelete\\b/i,\n /\\bcopy\\b/i,\n /\\bvacuum\\b/i,\n /\\breindex\\b/i,\n /\\bcluster\\b/i,\n]\n\nfunction assertRawQuerySafe(rawQuery) {\n if (typeof rawQuery !== 'string' || rawQuery.length === 0) {\n throw new Error('rawQuery must be a non-empty string')\n }\n // Reject multi-statement payloads \u2014 only single SELECT / WITH statements.\n // A trailing semicolon is tolerated but any content after it fails.\n var trimmed = rawQuery.trim().replace(/;\\s*$/, '')\n if (trimmed.indexOf(';') !== -1) {\n throw new Error('rawQuery must contain exactly one statement')\n }\n for (var i = 0; i < BLOCKED_RAW_QUERY_PATTERNS.length; i++) {\n if (BLOCKED_RAW_QUERY_PATTERNS[i].test(trimmed)) {\n throw new Error('rawQuery contains a blocked statement')\n }\n }\n}\n\nexport default async function handler(req, res) {\n").concat((0, table_access_guard_1.generateReadGuardCall)(tableName, 'req.query && req.query.rawQuery'), "\n const client = getClient()\n\n try {\n await client.connect()\n ").concat(schema ? "await client.query('SET search_path TO ".concat(schema, "')") : '', "\n\n // If the caller supplied a rawQuery, run it verbatim (after a safety\n // guard). The schema-driven branch below builds `SELECT * FROM ").concat(tableName, "`\n // with optional filters \u2014 it's the default read path and ignores\n // rawQuery. Page-load workflows and other consumers that need a JOIN\n // or a user-scoped filter pass their fully-rendered SQL here via\n // `fetchData({ rawQuery })` and expect it to execute verbatim.\n if (req.query && typeof req.query.rawQuery === 'string' && req.query.rawQuery.length > 0) {\n assertRawQuerySafe(req.query.rawQuery)\n // A page's own statement, read for a visitor, runs over the rows a\n // visitor is served of every narrowed table it names.\n const rawSql = __rowAccess.narrowRaw ? __brpNarrowRawQuery(req.query.rawQuery) : req.query.rawQuery\n const rawResult = await client.query(rawSql)\n const rawRows = Array.isArray(rawResult?.rows) ? rawResult.rows : []\n const rawPlain = rawRows.map((row) =>\n row && typeof row.toJSON === 'function' ? row.toJSON() : row\n )\n const rawSafe = JSON.parse(JSON.stringify(rawPlain, dateReplacer))\n return res.status(200).json({\n success: true,\n // Every row here ends up in a browser (a page's props or a client\n // refetch), so no credential column survives, whatever joined it.\n data: __taWithoutCredentials(\n __rowAccess.narrowRaw ? __brpStripHiddenOfRaw(req.query.rawQuery, rawSafe) : rawSafe,\n null\n ),\n timestamp: Date.now()\n })\n }\n\n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query\n // A visitor never filters, sorts or searches by a column it is not served.\n if (__rowPolicy) __brpAssertVisibleFields(__rowPolicy, req.query)\n // The language the page is in - what a translatable sort orders by.\n const requestLocale = typeof req.query.").concat(request_locale_1.REQUEST_LOCALE_PARAM, " === 'string' ? req.query.").concat(request_locale_1.REQUEST_LOCALE_PARAM, " : null\n \n const conditions = []\n const queryParams = []\n let paramIndex = 1\n let searchRank = ''\n let searchFrom = ''\n \n if (query) {\n let columns = []\n \n if (queryColumns) {\n const parsed = safeJSONParse(queryColumns)\n columns = Array.isArray(parsed) ? parsed : (parsed ? [parsed] : [])\n } else {\n try {\n const schemaQuery = `\n SELECT column_name \n FROM information_schema.columns \n WHERE table_name = $1\n ").concat(schema ? "AND table_schema = $2" : '', "\n ORDER BY ordinal_position\n `\n const schemaParams = ").concat(schema
|
|
63
64
|
? "[".concat(JSON.stringify(tableName), ", ").concat(JSON.stringify(schema), "]")
|
|
64
|
-
: "[".concat(JSON.stringify(tableName), "]"), "\n \n const schemaResult = await client.query(schemaQuery, schemaParams)\n columns = schemaResult.rows.map(row => row.column_name)\n } catch (schemaError) {\n console.warn('Failed to fetch column names from information_schema:', schemaError.message)\n }\n }\n if (__rowPolicy) columns = __brpVisibleColumns(__rowPolicy, columns)\n \n
|
|
65
|
+
: "[".concat(JSON.stringify(tableName), "]"), "\n \n const schemaResult = await client.query(schemaQuery, schemaParams)\n columns = schemaResult.rows.map(row => row.column_name)\n } catch (schemaError) {\n console.warn('Failed to fetch column names from information_schema:', schemaError.message)\n }\n }\n if (__rowPolicy) columns = __brpVisibleColumns(__rowPolicy, columns)\n \n const search = buildSearchSql(query, columns, paramIndex)\n if (search) {\n conditions.push(search.where)\n queryParams.push(...search.params)\n paramIndex = search.nextIndex\n searchRank = search.rank\n searchFrom = search.from\n }\n }\n \n paramIndex = processFilters(filters, conditions, queryParams, paramIndex)\n if (__rowPolicy) conditions.push('(' + __rowPolicy.predicate + ')')\n \n // A search's worked-out texts stay out of the rows: only the table's own columns.\n let sql = `SELECT ${searchFrom ? '").concat(tableName, ".*' : '*'} FROM ").concat(tableName, "${searchFrom}`\n \n if (conditions.length > 0) {\n sql += ` WHERE ${conditions.join(' AND ')}`\n }\n \n // Handle sorts - new array format. Two ORDER BY clauses are built: the one\n // actually used (a translatable column in the request's language, the\n // products table's price rewrites, rows without a value last), and a\n // plain-column twin kept only as the fallback below.\n // A search orders its matches by where the words are first; the sort\n // orders the rows within each of those groups.\n let orderBySql = ''\n let plainOrderBySql = ''\n const searchOrder = searchRank ? searchRank + ', ' : ''\n if (sorts) {\n const parsedSorts = safeJSONParse(sorts)\n if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {\n const valid = parsedSorts.filter((sort) => sort && sort.field)\n valid.forEach((sort) => assertRequestColumn(sort.field, 'sort field'))\n const orderOf = (sort) => (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'\n const orderClauses = valid.map((sort) => `${localizedSortFieldSql(sort.field, requestLocale) || sortFieldSql(sort.field)} ${orderOf(sort)}").concat(list_sort_order_1.SORT_NULLS_LAST_SQL, "`)\n const plainClauses = valid.map((sort) => `${sortFallbackField(sort.field)} ${orderOf(sort)}`)\n\n if (orderClauses.length > 0) {\n // The row's own key last, so equal rows can't repeat across pages\n // or vanish between them.\n const tiebreak = rowOrderTiebreak(valid.map((sort) => sort.field), orderOf(valid[valid.length - 1]))\n orderBySql = ` ORDER BY ${searchOrder}${orderClauses.join(', ')}${tiebreak}`\n plainOrderBySql = ` ORDER BY ${searchOrder}${plainClauses.join(', ')}${tiebreak}`\n }\n }\n } else if (sortBy) {\n assertRequestColumn(sortBy, 'sort field')\n const sortByDirection = (sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'\n const tiebreak = rowOrderTiebreak([sortBy], sortByDirection)\n orderBySql = ` ORDER BY ${searchOrder}${localizedSortFieldSql(sortBy, requestLocale) || sortBy} ${sortByDirection}").concat(list_sort_order_1.SORT_NULLS_LAST_SQL, "${tiebreak}`\n plainOrderBySql = ` ORDER BY ${searchOrder}${sortBy} ${sortByDirection}${tiebreak}`\n }\n if (!orderBySql && searchRank) {\n orderBySql = plainOrderBySql = ' ORDER BY ' + searchRank + rowOrderTiebreak([], 'ASC')\n }\n const usedRewrittenSort = orderBySql !== plainOrderBySql\n\n const limitValue = requestRowCount(limit || perPage)\n const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)\n\n let sqlTail = ''\n if (limitValue !== undefined) {\n sqlTail += ` LIMIT ${limitValue}`\n }\n \n if (offsetValue !== undefined) {\n sqlTail += ` OFFSET ${offsetValue}`\n }\n\n const baseSql = sql\n sql = baseSql + orderBySql + sqlTail\n\n // The discount-aware price ordering is an inline sub-select over a JSON\n // column. It is written to be unraisable, but it runs inside ORDER BY for\n // the whole table - so if a database ever rejects it, fall back to ordering\n // by the stored list price rather than serving an empty products page. The\n // translatable-column ordering takes the same exit: a per-language column\n // the schema had at generation time may have been dropped since. So does\n // the empty-values-last ordering.\n let result\n try {\n result = await client.query(sql, queryParams)\n } catch (sortError) {\n if (!usedRewrittenSort) throw sortError\n console.warn(\n 'Rewritten sort failed; falling back to the plain column:',\n sortError && sortError.message\n )\n result = await client.query(baseSql + plainOrderBySql + sqlTail, queryParams)\n }\n const rows = Array.isArray(result?.rows) ? result.rows : []\n const plainRows = rows.map((row) =>\n row && typeof row.toJSON === 'function' ? row.toJSON() : row\n )\n const safeData = JSON.parse(JSON.stringify(plainRows, dateReplacer))\n ").concat((0, transformations_1.getTransformExpression)(tableName)
|
|
65
66
|
? "const transformedData = ".concat((0, transformations_1.getTransformExpression)(tableName))
|
|
66
67
|
: '', "\n\n // Stamp each row with a globally-correct 1-based `__rowNumber` (the page\n // offset is applied server-side above), so list index columns keep counting\n // across pages instead of restarting at 1. Renderer-internal field \u2014 it is\n // filtered from ordinary field bindings and only read by the index column.\n const __finalData = ").concat((0, transformations_1.getTransformExpression)(tableName) ? 'transformedData' : 'safeData', "\n const __rowNumberStart = (typeof offsetValue === 'number' && !isNaN(offsetValue)) ? offsetValue : 0\n const __numberedData = Array.isArray(__finalData)\n ? __finalData.map((__row, __i) =>\n __row && typeof __row === 'object' && !Array.isArray(__row)\n ? Object.assign({}, __row, { __rowNumber: __rowNumberStart + __i + 1 })\n : __row)\n : __finalData\n\n return res.status(200).json({\n success: true,\n data: __taWithoutCredentials(\n __rowPolicy ? __brpStripHidden(__rowPolicy, __numberedData) : __numberedData,\n ").concat(JSON.stringify(tableName), "\n ),\n timestamp: Date.now()\n })\n } catch (error) {\n console.error('Teleport DB fetch error:', error)\n // A database error names tables, columns and values: it stays in the server\n // log. Only an error this route raised for the caller (it carries a status)\n // is repeated to it.\n return res.status(error.status || 500).json({\n success: false,\n error: error.status ? error.message : 'Failed to fetch data',\n timestamp: Date.now()\n })\n } finally {\n if (client) {\n try {\n await client.end()\n } catch (error) {\n console.error('Error closing database client:', error)\n }\n }\n }\n}\n");
|
|
67
68
|
};
|
|
@@ -70,9 +71,9 @@ var generateTeleportCountFetcher = function (config, tableName) {
|
|
|
70
71
|
var _a;
|
|
71
72
|
var dbConfig = config;
|
|
72
73
|
var hasSchema = !!((_a = dbConfig.options) === null || _a === void 0 ? void 0 : _a.schema);
|
|
73
|
-
return "\nasync function getCount(req, res) {\n".concat((0, table_access_guard_1.generateReadGuardCall)(tableName, null), "\n const client = getClient()\n\n try {\n await client.connect()\n const { query, queryColumns, filters } = req.query\n if (__rowPolicy) __brpAssertVisibleFields(__rowPolicy, req.query)\n const conditions = []\n const queryParams = []\n let paramIndex = 1\n\n if (query) {\n let columns = []\n \n if (queryColumns) {\n const parsed = safeJSONParse(queryColumns)\n columns = Array.isArray(parsed) ? parsed : [parsed]\n } else {\n try {\n const schemaQuery = `\n SELECT column_name \n FROM information_schema.columns \n WHERE table_name = $1\n ").concat(hasSchema ? "AND table_schema = $2" : '', "\n ORDER BY ordinal_position\n `\n const schemaParams = ").concat(hasSchema
|
|
74
|
+
return "\nasync function getCount(req, res) {\n".concat((0, table_access_guard_1.generateReadGuardCall)(tableName, null), "\n const client = getClient()\n\n try {\n await client.connect()\n const { query, queryColumns, filters } = req.query\n if (__rowPolicy) __brpAssertVisibleFields(__rowPolicy, req.query)\n const conditions = []\n const queryParams = []\n let paramIndex = 1\n let searchFrom = ''\n\n if (query) {\n let columns = []\n \n if (queryColumns) {\n const parsed = safeJSONParse(queryColumns)\n columns = Array.isArray(parsed) ? parsed : [parsed]\n } else {\n try {\n const schemaQuery = `\n SELECT column_name \n FROM information_schema.columns \n WHERE table_name = $1\n ").concat(hasSchema ? "AND table_schema = $2" : '', "\n ORDER BY ordinal_position\n `\n const schemaParams = ").concat(hasSchema
|
|
74
75
|
? "[".concat(JSON.stringify(tableName), ", ").concat(JSON.stringify(dbConfig.options.schema), "]")
|
|
75
|
-
: "[".concat(JSON.stringify(tableName), "]"), "\n \n const schemaResult = await client.query(schemaQuery, schemaParams)\n columns = schemaResult.rows.map(row => row.column_name)\n } catch (schemaError) {\n console.warn('Failed to fetch column names from information_schema:', schemaError.message)\n }\n }\n if (__rowPolicy) columns = __brpVisibleColumns(__rowPolicy, columns)\n \n
|
|
76
|
+
: "[".concat(JSON.stringify(tableName), "]"), "\n \n const schemaResult = await client.query(schemaQuery, schemaParams)\n columns = schemaResult.rows.map(row => row.column_name)\n } catch (schemaError) {\n console.warn('Failed to fetch column names from information_schema:', schemaError.message)\n }\n }\n if (__rowPolicy) columns = __brpVisibleColumns(__rowPolicy, columns)\n \n const search = buildSearchSql(query, columns, paramIndex)\n if (search) {\n conditions.push(search.where)\n queryParams.push(...search.params)\n paramIndex = search.nextIndex\n searchFrom = search.from\n }\n }\n\n paramIndex = processFilters(filters, conditions, queryParams, paramIndex)\n if (__rowPolicy) conditions.push('(' + __rowPolicy.predicate + ')')\n\n let countSql = `SELECT COUNT(*) FROM ").concat(tableName, "${searchFrom}`\n if (conditions.length > 0) {\n countSql += ` WHERE ${conditions.join(' AND ')}`\n }\n\n const result = await client.query(countSql, queryParams)\n const count = parseInt(result.rows[0].count, 10)\n\n return res.status(200).json({\n success: true,\n count: count,\n timestamp: Date.now()\n })\n } catch (error) {\n console.error('Error getting count:', error)\n return res.status(error.status || 500).json({\n success: false,\n error: error.status ? error.message : 'Failed to get count',\n timestamp: Date.now()\n })\n } finally {\n if (client) {\n try {\n await client.end()\n } catch (error) {\n console.error('Error closing database client:', error)\n }\n }\n }\n}\n");
|
|
76
77
|
};
|
|
77
78
|
exports.generateTeleportCountFetcher = generateTeleportCountFetcher;
|
|
78
79
|
//# sourceMappingURL=teleport.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"teleport.js","sourceRoot":"","sources":["../../../src/fetchers/teleport.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"teleport.js","sourceRoot":"","sources":["../../../src/fetchers/teleport.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turso.d.ts","sourceRoot":"","sources":["../../../src/fetchers/turso.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"turso.d.ts","sourceRoot":"","sources":["../../../src/fetchers/turso.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,mBAAmB,WACtB,OAAO,MAAM,EAAE,OAAO,CAAC,KAC9B;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAcpC,CAAA;AASD,eAAO,MAAM,oBAAoB,WACvB,OAAO,MAAM,EAAE,OAAO,CAAC,aACpB,MAAM,KAChB,MAmMF,CAAA"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateTursoFetcher = exports.validateTursoConfig = void 0;
|
|
4
4
|
var utils_1 = require("../utils");
|
|
5
|
+
var search_relevance_1 = require("../search-relevance");
|
|
5
6
|
var validateTursoConfig = function (config) {
|
|
6
7
|
if (!config || typeof config !== 'object') {
|
|
7
8
|
return { isValid: false, error: 'Config must be a valid object' };
|
|
@@ -19,7 +20,7 @@ var generateTursoFetcher = function (config, tableName) {
|
|
|
19
20
|
var tursoConfig = config;
|
|
20
21
|
var databaseUrl = tursoConfig.databaseUrl;
|
|
21
22
|
var token = tursoConfig.token;
|
|
22
|
-
return "import { createClient } from '@libsql/client'\n\n".concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateSearchEscapeHelpersCode)(), "\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\nexport default async function handler(req, res) {\n let client = null\n try {\n client = createClient({\n url: ").concat(JSON.stringify(databaseUrl), ",\n authToken: ").concat((0, utils_1.replaceSecretReference)(token), "\n })\n \n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query\n \n let sql = `SELECT * FROM ").concat(tableName, "`\n const whereClauses = []\n const queryParams = []\n let searchQueryColumns = null\n \n if (query) {\n if (queryColumns) {\n const parsed = safeJSONParse(queryColumns)\n const columns = Array.isArray(parsed) ? parsed : [parsed]\n // Cast columns to TEXT and LOWER both sides so the match is\n // case-insensitive regardless of SQLite collation.\n
|
|
23
|
+
return "import { createClient } from '@libsql/client'\n\n".concat((0, utils_1.generateSafeJSONParseCode)(), "\n\n").concat((0, utils_1.generateFilterTreeHelpersCode)(), "\n\n").concat((0, utils_1.generateSearchEscapeHelpersCode)(), "\n").concat((0, search_relevance_1.generateSearchSqlHelpersCode)(search_relevance_1.SQLITE_SEARCH_SQL), "\n\n").concat((0, utils_1.generateDateFormatterCode)(), "\n\nexport default async function handler(req, res) {\n let client = null\n try {\n client = createClient({\n url: ").concat(JSON.stringify(databaseUrl), ",\n authToken: ").concat((0, utils_1.replaceSecretReference)(token), "\n })\n \n const { query, queryColumns, limit, page, perPage, sortBy, sortOrder, filters, sorts, offset } = req.query\n \n let sql = `SELECT * FROM ").concat(tableName, "`\n const whereClauses = []\n const queryParams = []\n let searchQueryColumns = null\n let search = null\n \n if (query) {\n if (queryColumns) {\n const parsed = safeJSONParse(queryColumns)\n const columns = Array.isArray(parsed) ? parsed : [parsed]\n // Cast columns to TEXT and LOWER both sides so the match is\n // case-insensitive regardless of SQLite collation.\n search = buildSearchSql(query, columns)\n if (search) {\n whereClauses.push(search.where)\n queryParams.push(...search.params)\n }\n } else {\n // Store query for post-filtering if columns not specified\n searchQueryColumns = query\n }\n }\n \n // Helper to sanitize identifier (prevent SQL injection in column names)\n const sanitizeIdentifier = (name) => {\n // Only allow alphanumeric and underscore\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {\n throw new Error(`Invalid identifier: ${name}`)\n }\n return `\"${name}\"`\n }\n \n if (filters) {\n const filterTree = normalizeFilterTree(safeJSONParse(filters))\n \n if (filterTree) {\n const buildCondition = (condition) => {\n const field = sanitizeIdentifier(condition.source)\n const normalizedCondition = normalizeInOperand(condition.operand, condition.destination)\n if (normalizedCondition === null) return null\n const value = normalizedCondition.value\n const operand = normalizedCondition.operand\n \n if (Array.isArray(value)) {\n if (value.length === 0) return null\n const placeholders = value.map(() => '?').join(', ')\n queryParams.push(...value)\n return operand === '!='\n ? `${field} NOT IN (${placeholders})`\n : `${field} IN (${placeholders})`\n }\n \n if (value === null) {\n if (operand === '=') return `${field} IS NULL`\n if (operand === '!=') return `${field} IS NOT NULL`\n return null\n }\n \n const validOps = ['=', '!=', '>', '<', '>=', '<=']\n const sqlOperator = validOps.includes(operand) ? operand : '='\n queryParams.push(value)\n return `${field} ${sqlOperator} ?`\n }\n \n const clause = buildFilterTreeClause(filterTree, buildCondition)\n if (clause) whereClauses.push(clause)\n }\n }\n \n if (whereClauses.length > 0) {\n sql += ` WHERE ${whereClauses.join(' AND ')}`\n }\n \n // Handle sorts - new array format. A search orders its matches by where\n // the words are first; the sort orders the rows within each of those groups.\n const orderClauses = search && search.rank ? [search.rank] : []\n if (sorts) {\n const parsedSorts = safeJSONParse(sorts)\n if (Array.isArray(parsedSorts) && parsedSorts.length > 0) {\n orderClauses.push(...parsedSorts.map((sort) => {\n if (!sort.field) return null\n const order = (sort.order || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'\n return `${sanitizeIdentifier(sort.field)} ${order}`\n }).filter(Boolean))\n }\n } else if (sortBy) {\n const sortOrderValue = (sortOrder || '').toUpperCase().startsWith('DESC') ? 'DESC' : 'ASC'\n orderClauses.push(`${sanitizeIdentifier(sortBy)} ${sortOrderValue}`)\n }\n if (orderClauses.length > 0) {\n sql += ` ORDER BY ${orderClauses.join(', ')}`\n if (search && search.rank) queryParams.push(...search.rankParams)\n }\n \n const limitValue = limit || perPage\n const offsetValue = offset !== undefined ? parseInt(offset) : (page && perPage ? (parseInt(page) - 1) * parseInt(perPage) : undefined)\n \n // Only apply SQL pagination if we're not doing post-filtering\n if (!searchQueryColumns) {\n if (limitValue) {\n sql += ` LIMIT ?`\n queryParams.push(parseInt(limitValue))\n }\n \n if (offsetValue !== undefined) {\n sql += ` OFFSET ?`\n queryParams.push(offsetValue)\n }\n }\n \n const result = await client.execute({\n sql,\n args: queryParams\n })\n \n let data = result.rows.map((row) => {\n const obj = {}\n result.columns.forEach((col, idx) => {\n obj[col] = row[col]\n })\n return obj\n })\n \n // Apply post-filtering for search without queryColumns\n if (searchQueryColumns) {\n const searchTerms = searchQueryColumns.toLowerCase().split(' ').filter(Boolean)\n data = data.filter((item) => {\n try {\n const stringified = JSON.stringify(item).toLowerCase()\n return searchTerms.every((term) => stringified.includes(term))\n } catch {\n return false\n }\n })\n \n // Apply pagination after filtering\n if (limitValue) {\n const start = offsetValue || 0\n data = data.slice(start, start + parseInt(limitValue))\n } else if (offsetValue) {\n data = data.slice(offsetValue)\n }\n }\n \n const safeData = JSON.parse(JSON.stringify(data, dateReplacer))\n \n return res.status(200).json({\n success: true,\n data: safeData,\n timestamp: Date.now()\n })\n } catch (error) {\n console.error('Turso fetch error:', error)\n return res.status(500).json({\n success: false,\n error: error.message || 'Failed to fetch data',\n timestamp: Date.now()\n })\n } finally {\n if (client) {\n try {\n await client.close()\n } catch (error) {\n console.error('Error closing Turso client:', error)\n }\n }\n }\n}\n");
|
|
23
24
|
};
|
|
24
25
|
exports.generateTursoFetcher = generateTursoFetcher;
|
|
25
26
|
//# sourceMappingURL=turso.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turso.js","sourceRoot":"","sources":["../../../src/fetchers/turso.ts"],"names":[],"mappings":";;;AAAA,kCAMiB;
|
|
1
|
+
{"version":3,"file":"turso.js","sourceRoot":"","sources":["../../../src/fetchers/turso.ts"],"names":[],"mappings":";;;AAAA,kCAMiB;AACjB,wDAAqF;AAE9E,IAAM,mBAAmB,GAAG,UACjC,MAA+B;IAE/B,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;QACzC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAA;KAClE;IAED,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,EAAE;QACjE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAA;KACnE;IAED,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE;QACrD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,wCAAwC,EAAE,CAAA;KAC3E;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAC1B,CAAC,CAAA;AAhBY,QAAA,mBAAmB,uBAgB/B;AASM,IAAM,oBAAoB,GAAG,UAClC,MAA+B,EAC/B,SAAiB;IAEjB,IAAM,WAAW,GAAG,MAAqB,CAAA;IACzC,IAAM,WAAW,GAAG,WAAW,CAAC,WAAW,CAAA;IAC3C,IAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAA;IAE/B,OAAO,2DAEP,IAAA,iCAAyB,GAAE,iBAE3B,IAAA,qCAA6B,GAAE,iBAE/B,IAAA,uCAA+B,GAAE,eACjC,IAAA,+CAA4B,EAAC,oCAAiB,CAAC,iBAE/C,IAAA,iCAAyB,GAAE,0IAMhB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,iCACrB,IAAA,8BAAsB,EAAC,KAAK,CAAC,gLAKhB,SAAS,spLAwKxC,CAAA;AACD,CAAC,CAAA;AAtMY,QAAA,oBAAoB,wBAsMhC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a list's search box narrows and orders the rows of a generated route.
|
|
3
|
+
*
|
|
4
|
+
* The typed text is split into terms at white space, letter case aside, and
|
|
5
|
+
* the punctuation around a term is dropped ("form?" looks for "form",
|
|
6
|
+
* "\"create" for "create"). Every term must appear in one of the searched
|
|
7
|
+
* columns, so "create form" finds "How to create a form".
|
|
8
|
+
*
|
|
9
|
+
* Where the stored text is read the same way as the typed text (the
|
|
10
|
+
* TeleportHQ database and other Postgres sources, and the routes that search
|
|
11
|
+
* rows in memory), punctuation inside a term counts as a space on both sides
|
|
12
|
+
* ("e-commerce" finds "E-Commerce", "what's" finds "what’s"), and HTML tags
|
|
13
|
+
* and entities count as spaces too, so a word that is only in a rich-text
|
|
14
|
+
* body's markup ("strong", "href", "amp") never matches.
|
|
15
|
+
*
|
|
16
|
+
* The matches are ordered column by column, in the order the list searches
|
|
17
|
+
* them: by how much of the typed text the first column (a title) holds, then
|
|
18
|
+
* the second (a summary), and so on. A term counts 3 as a whole word, 2 at
|
|
19
|
+
* the start of a word ("form" in "forms") and 1 inside one ("form" in
|
|
20
|
+
* "platform"). The list's own sort orders the rows that score alike.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Characters a search reads as a space: punctuation, and line breaks and
|
|
24
|
+
* tabs, so a word at the end of a line is still a whole word.
|
|
25
|
+
*/
|
|
26
|
+
export declare const SEARCH_PUNCTUATION: string;
|
|
27
|
+
/**
|
|
28
|
+
* `searchTerms(query)`: the typed terms, lowercased, each once. `normalised`
|
|
29
|
+
* also reads the punctuation inside a term as a space, for the routes whose
|
|
30
|
+
* stored text is read the same way; otherwise only the punctuation around a
|
|
31
|
+
* term is dropped, so "e-commerce" still finds the raw text "e-commerce".
|
|
32
|
+
*/
|
|
33
|
+
export declare const generateSearchTermsCode: (normalised: boolean) => string;
|
|
34
|
+
/**
|
|
35
|
+
* How one SQL dialect reads a column for a search. `textSql` is the source
|
|
36
|
+
* of `(column) => sql`, the column's text as the search reads it: lower case,
|
|
37
|
+
* with a space at each end so a whole word and a word start can be told
|
|
38
|
+
* apart. `likeSql` is the source of `(text, placeholder, before, after) =>
|
|
39
|
+
* sql`, testing `text LIKE before + term + after` with the term bound to the
|
|
40
|
+
* placeholder. `normalised` dialects also read tags, entities and punctuation
|
|
41
|
+
* in the stored text as spaces; `lateral` ones work each column's text out
|
|
42
|
+
* once per row, in a LATERAL subquery the route joins after its table.
|
|
43
|
+
*/
|
|
44
|
+
export interface SearchSqlDialect {
|
|
45
|
+
textSql: string;
|
|
46
|
+
likeSql: string;
|
|
47
|
+
normalised: boolean;
|
|
48
|
+
lateral: boolean;
|
|
49
|
+
}
|
|
50
|
+
export declare const PG_SEARCH_SQL: SearchSqlDialect;
|
|
51
|
+
export declare const REDSHIFT_SEARCH_SQL: SearchSqlDialect;
|
|
52
|
+
export declare const MYSQL_SEARCH_SQL: SearchSqlDialect;
|
|
53
|
+
export declare const MARIADB_SEARCH_SQL: SearchSqlDialect;
|
|
54
|
+
export declare const SQLITE_SEARCH_SQL: SearchSqlDialect;
|
|
55
|
+
/**
|
|
56
|
+
* `buildSearchSql(query, columns, firstIndex)` for the SQL routes:
|
|
57
|
+
* `{ where, params, from, rank, rankParams, nextIndex }`, or null when there
|
|
58
|
+
* is nothing to search. `from` is the LATERAL join a `lateral` dialect's
|
|
59
|
+
* route appends after its table (selecting `table.*`, so the worked-out
|
|
60
|
+
* texts never reach a row), '' otherwise. `rank` is the ORDER BY keys, one
|
|
61
|
+
* per searched column, best first.
|
|
62
|
+
*
|
|
63
|
+
* With `firstIndex` the placeholders are numbered (`$n`) and each term is
|
|
64
|
+
* bound once, the rank reusing it; without it they are positional (`?`),
|
|
65
|
+
* every occurrence binds its own copy, and `rankParams` go after the
|
|
66
|
+
* parameters of the whole WHERE, as the ORDER BY comes after it.
|
|
67
|
+
*
|
|
68
|
+
* Uses `escapeLikePattern` and `sanitizeSearchIdentifier`
|
|
69
|
+
* (`generateSearchEscapeHelpersCode`).
|
|
70
|
+
*/
|
|
71
|
+
export declare const generateSearchSqlHelpersCode: (dialect: SearchSqlDialect) => string;
|
|
72
|
+
/**
|
|
73
|
+
* For the routes that search rows in memory: `matchesSearch(row, query,
|
|
74
|
+
* columns, valueOf)`, true when every term is in one of the columns, and
|
|
75
|
+
* `rankBySearch(rows, query, columns, valueOf)`, the same order as the SQL
|
|
76
|
+
* routes' rank, applied after the list's sort (a stable sort keeps it among
|
|
77
|
+
* rows that score alike) and before the page is cut. Stored text is read
|
|
78
|
+
* the normalised way: tags, entities and punctuation as spaces.
|
|
79
|
+
*/
|
|
80
|
+
export declare const generateSearchRankHelperCode: () => string;
|
|
81
|
+
//# sourceMappingURL=search-relevance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-relevance.d.ts","sourceRoot":"","sources":["../../src/search-relevance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAE2E,CAAA;AAwB1G;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,eACtB,OAAO,KAClB,MAmBD,CAAA;AAEF;;;;;;;;;GASG;AACH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,OAAO,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;CACjB;AAID,eAAO,MAAM,aAAa,EAAE,gBAK3B,CAAA;AAED,eAAO,MAAM,mBAAmB,EAAE,gBAKjC,CAAA;AAID,eAAO,MAAM,gBAAgB,EAAE,gBAK9B,CAAA;AAED,eAAO,MAAM,kBAAkB,EAAE,gBAKhC,CAAA;AAED,eAAO,MAAM,iBAAiB,EAAE,gBAK/B,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,YAC9B,gBAAgB,KACxB,MAuCD,CAAA;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,QAAO,MAyB9C,CAAA"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* How a list's search box narrows and orders the rows of a generated route.
|
|
4
|
+
*
|
|
5
|
+
* The typed text is split into terms at white space, letter case aside, and
|
|
6
|
+
* the punctuation around a term is dropped ("form?" looks for "form",
|
|
7
|
+
* "\"create" for "create"). Every term must appear in one of the searched
|
|
8
|
+
* columns, so "create form" finds "How to create a form".
|
|
9
|
+
*
|
|
10
|
+
* Where the stored text is read the same way as the typed text (the
|
|
11
|
+
* TeleportHQ database and other Postgres sources, and the routes that search
|
|
12
|
+
* rows in memory), punctuation inside a term counts as a space on both sides
|
|
13
|
+
* ("e-commerce" finds "E-Commerce", "what's" finds "what’s"), and HTML tags
|
|
14
|
+
* and entities count as spaces too, so a word that is only in a rich-text
|
|
15
|
+
* body's markup ("strong", "href", "amp") never matches.
|
|
16
|
+
*
|
|
17
|
+
* The matches are ordered column by column, in the order the list searches
|
|
18
|
+
* them: by how much of the typed text the first column (a title) holds, then
|
|
19
|
+
* the second (a summary), and so on. A term counts 3 as a whole word, 2 at
|
|
20
|
+
* the start of a word ("form" in "forms") and 1 inside one ("form" in
|
|
21
|
+
* "platform"). The list's own sort orders the rows that score alike.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.generateSearchRankHelperCode = exports.generateSearchSqlHelpersCode = exports.SQLITE_SEARCH_SQL = exports.MARIADB_SEARCH_SQL = exports.MYSQL_SEARCH_SQL = exports.REDSHIFT_SEARCH_SQL = exports.PG_SEARCH_SQL = exports.generateSearchTermsCode = exports.SEARCH_PUNCTUATION = void 0;
|
|
25
|
+
/**
|
|
26
|
+
* Characters a search reads as a space: punctuation, and line breaks and
|
|
27
|
+
* tabs, so a word at the end of a line is still a whole word.
|
|
28
|
+
*/
|
|
29
|
+
exports.SEARCH_PUNCTUATION = '\t\n\v\f\r!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~\u00a0\u00a1\u00ab\u00b7\u00bb\u00bf' +
|
|
30
|
+
'\u2010\u2011\u2012\u2013\u2014\u2015\u2018\u2019\u201a\u201b\u201c\u201d\u201e\u2022\u2026\u2039\u203a';
|
|
31
|
+
/** A JavaScript string literal that survives being pasted into any generated file. */
|
|
32
|
+
var jsString = function (value) {
|
|
33
|
+
return JSON.stringify(value).replace(/[\u007f-\uffff`$]/g, function (char) { return '\\u' + ('0000' + char.charCodeAt(0).toString(16)).slice(-4); });
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* One Postgres regular expression, as a string literal, for everything a
|
|
37
|
+
* normalised search reads as a space: a tag, an entity, or one of
|
|
38
|
+
* SEARCH_PUNCTUATION. The bracket expression puts `]` first and `-` last,
|
|
39
|
+
* escapes the backslash, and puts `[` before `-` so it never opens a class.
|
|
40
|
+
* One pass: `translate()` is several times slower over long multibyte text.
|
|
41
|
+
*/
|
|
42
|
+
var PG_SPACES_REGEX_SQL = "'".concat(('<[^>]*>|&[a-z0-9#]+;|[]' +
|
|
43
|
+
exports.SEARCH_PUNCTUATION.split('')
|
|
44
|
+
.filter(function (char) { return !']-\\['.includes(char); })
|
|
45
|
+
.join('') +
|
|
46
|
+
'\\\\[-]').replace(/'/g, "''"), "'");
|
|
47
|
+
/**
|
|
48
|
+
* `searchTerms(query)`: the typed terms, lowercased, each once. `normalised`
|
|
49
|
+
* also reads the punctuation inside a term as a space, for the routes whose
|
|
50
|
+
* stored text is read the same way; otherwise only the punctuation around a
|
|
51
|
+
* term is dropped, so "e-commerce" still finds the raw text "e-commerce".
|
|
52
|
+
*/
|
|
53
|
+
var generateSearchTermsCode = function (normalised) { return "const SEARCH_PUNCTUATION = ".concat(jsString(exports.SEARCH_PUNCTUATION), "\nconst searchPunctuationToSpaces = (text) => {\n let out = ''\n for (const char of text) out += SEARCH_PUNCTUATION.indexOf(char) === -1 ? char : ' '\n return out\n}\nconst searchTerms = (query) => {\n const terms = []\n String(query == null ? '' : query).toLowerCase().split(/\\s+/).forEach((token) => {\n const spaced = searchPunctuationToSpaces(token)\n").concat(normalised
|
|
54
|
+
? " const term = spaced.trim().replace(/\\s+/g, ' ')"
|
|
55
|
+
: " const start = spaced.length - spaced.trimStart().length\n const term = token.slice(start, start + spaced.trim().length)", "\n if (term && terms.indexOf(term) === -1) terms.push(term)\n })\n return terms\n}"); };
|
|
56
|
+
exports.generateSearchTermsCode = generateSearchTermsCode;
|
|
57
|
+
var PG_LIKE_SQL = "(text, placeholder, before, after) => text + \" LIKE ('\" + before + \"' || \" + placeholder + \"::text || '\" + after + \"') ESCAPE '|'\"";
|
|
58
|
+
exports.PG_SEARCH_SQL = {
|
|
59
|
+
textSql: "(column) => \"(' ' || regexp_replace(lower(\\\"\" + column + \"\\\"::text), \" + SEARCH_SPACES_REGEX_SQL + \", ' ', 'g') || ' ')\"",
|
|
60
|
+
likeSql: PG_LIKE_SQL,
|
|
61
|
+
normalised: true,
|
|
62
|
+
lateral: true,
|
|
63
|
+
};
|
|
64
|
+
exports.REDSHIFT_SEARCH_SQL = {
|
|
65
|
+
textSql: "(column) => \"(' ' || lower(\\\"\" + column + \"\\\"::text) || ' ')\"",
|
|
66
|
+
likeSql: PG_LIKE_SQL,
|
|
67
|
+
normalised: false,
|
|
68
|
+
lateral: false,
|
|
69
|
+
};
|
|
70
|
+
var CONCAT_LIKE_SQL = "(text, placeholder, before, after) => text + \" LIKE CONCAT('\" + before + \"', \" + placeholder + \", '\" + after + \"') ESCAPE '|'\"";
|
|
71
|
+
exports.MYSQL_SEARCH_SQL = {
|
|
72
|
+
textSql: "(column) => \"CONCAT(' ', LOWER(CAST(\" + mysql.escapeId(column) + \" AS CHAR)), ' ')\"",
|
|
73
|
+
likeSql: CONCAT_LIKE_SQL,
|
|
74
|
+
normalised: false,
|
|
75
|
+
lateral: false,
|
|
76
|
+
};
|
|
77
|
+
exports.MARIADB_SEARCH_SQL = {
|
|
78
|
+
textSql: "(column) => \"CONCAT(' ', LOWER(CAST(\\`\" + column + \"\\` AS CHAR)), ' ')\"",
|
|
79
|
+
likeSql: CONCAT_LIKE_SQL,
|
|
80
|
+
normalised: false,
|
|
81
|
+
lateral: false,
|
|
82
|
+
};
|
|
83
|
+
exports.SQLITE_SEARCH_SQL = {
|
|
84
|
+
textSql: "(column) => \"(' ' || LOWER(CAST(\\\"\" + column + \"\\\" AS TEXT)) || ' ')\"",
|
|
85
|
+
likeSql: "(text, placeholder, before, after) => text + \" LIKE ('\" + before + \"' || \" + placeholder + \" || '\" + after + \"') ESCAPE '|'\"",
|
|
86
|
+
normalised: false,
|
|
87
|
+
lateral: false,
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* `buildSearchSql(query, columns, firstIndex)` for the SQL routes:
|
|
91
|
+
* `{ where, params, from, rank, rankParams, nextIndex }`, or null when there
|
|
92
|
+
* is nothing to search. `from` is the LATERAL join a `lateral` dialect's
|
|
93
|
+
* route appends after its table (selecting `table.*`, so the worked-out
|
|
94
|
+
* texts never reach a row), '' otherwise. `rank` is the ORDER BY keys, one
|
|
95
|
+
* per searched column, best first.
|
|
96
|
+
*
|
|
97
|
+
* With `firstIndex` the placeholders are numbered (`$n`) and each term is
|
|
98
|
+
* bound once, the rank reusing it; without it they are positional (`?`),
|
|
99
|
+
* every occurrence binds its own copy, and `rankParams` go after the
|
|
100
|
+
* parameters of the whole WHERE, as the ORDER BY comes after it.
|
|
101
|
+
*
|
|
102
|
+
* Uses `escapeLikePattern` and `sanitizeSearchIdentifier`
|
|
103
|
+
* (`generateSearchEscapeHelpersCode`).
|
|
104
|
+
*/
|
|
105
|
+
var generateSearchSqlHelpersCode = function (dialect) { return "".concat((0, exports.generateSearchTermsCode)(dialect.normalised), "\n").concat(dialect.normalised ? "const SEARCH_SPACES_REGEX_SQL = ".concat(jsString(PG_SPACES_REGEX_SQL), "\n") : '', "const searchTextSql = ").concat(dialect.textSql, "\nconst searchLikeSql = ").concat(dialect.likeSql, "\nconst buildSearchSql = (query, columns, firstIndex) => {\n const terms = searchTerms(query)\n const searched = []\n ;(Array.isArray(columns) ? columns : []).forEach((column) => {\n const name = sanitizeSearchIdentifier(column)\n if (searched.indexOf(name) === -1) searched.push(name)\n })\n if (terms.length === 0 || searched.length === 0) return null\n const patterns = terms.map((term) => escapeLikePattern(term))\n const numbered = typeof firstIndex === 'number'\n const params = numbered ? patterns.slice() : []\n const rankParams = []\n const placeholder = (termIndex, bound) => {\n if (numbered) return '$' + (firstIndex + termIndex)\n bound.push(patterns[termIndex])\n return '?'\n }\n const texts = searched.map((column, position) => ").concat(dialect.lateral ? "'\"__tq_search\".\"__tq_search_' + position + '\"'" : 'searchTextSql(column)', ")\n const from = ").concat(dialect.lateral
|
|
106
|
+
? "' CROSS JOIN LATERAL (SELECT ' + searched.map((column, position) => searchTextSql(column) + ' AS \"__tq_search_' + position + '\"').join(', ') + ' OFFSET 0) AS \"__tq_search\"'"
|
|
107
|
+
: "''", "\n const where = '(' + patterns.map((pattern, termIndex) =>\n '(' + texts.map((text) => searchLikeSql(text, placeholder(termIndex, params), '%', '%')).join(' OR ') + ')'\n ).join(' AND ') + ')'\n const rank = texts.map((text) => '(' + patterns.map((pattern, termIndex) =>\n 'CASE WHEN ' + searchLikeSql(text, placeholder(termIndex, rankParams), '% ', ' %') + ' THEN 3' +\n ' WHEN ' + searchLikeSql(text, placeholder(termIndex, rankParams), '% ', '%') + ' THEN 2' +\n ' WHEN ' + searchLikeSql(text, placeholder(termIndex, rankParams), '%', '%') + ' THEN 1 ELSE 0 END'\n ).join(' + ') + ') DESC').join(', ')\n return { where, params, from, rank, rankParams, nextIndex: numbered ? firstIndex + patterns.length : firstIndex }\n}"); };
|
|
108
|
+
exports.generateSearchSqlHelpersCode = generateSearchSqlHelpersCode;
|
|
109
|
+
/**
|
|
110
|
+
* For the routes that search rows in memory: `matchesSearch(row, query,
|
|
111
|
+
* columns, valueOf)`, true when every term is in one of the columns, and
|
|
112
|
+
* `rankBySearch(rows, query, columns, valueOf)`, the same order as the SQL
|
|
113
|
+
* routes' rank, applied after the list's sort (a stable sort keeps it among
|
|
114
|
+
* rows that score alike) and before the page is cut. Stored text is read
|
|
115
|
+
* the normalised way: tags, entities and punctuation as spaces.
|
|
116
|
+
*/
|
|
117
|
+
var generateSearchRankHelperCode = function () { return "".concat((0, exports.generateSearchTermsCode)(true), "\nconst searchText = (value) => {\n const text = value === null || value === undefined ? '' : typeof value === 'object' ? JSON.stringify(value) : String(value)\n return ' ' + searchPunctuationToSpaces(text.toLowerCase().replace(/<[^>]*>|&[a-z0-9#]+;/g, ' ')) + ' '\n}\nconst searchScore = (text, terms) => terms.reduce((score, term) =>\n score + (text.indexOf(' ' + term + ' ') !== -1 ? 3 : text.indexOf(' ' + term) !== -1 ? 2 : text.indexOf(term) !== -1 ? 1 : 0), 0)\nconst matchesSearch = (row, query, columns, valueOf) => {\n const terms = searchTerms(query)\n if (terms.length === 0) return true\n const texts = (Array.isArray(columns) ? columns : []).map((column) => searchText(valueOf(row, column)))\n return terms.every((term) => texts.some((text) => text.indexOf(term) !== -1))\n}\nconst rankBySearch = (rows, query, columns, valueOf) => {\n const terms = searchTerms(query)\n if (!Array.isArray(columns) || columns.length === 0 || terms.length === 0) return rows\n return rows\n .map((row, index) => ({ row, index, scores: columns.map((column) => searchScore(searchText(valueOf(row, column)), terms)) }))\n .sort((a, b) => {\n for (let position = 0; position < a.scores.length; position++) {\n if (a.scores[position] !== b.scores[position]) return b.scores[position] - a.scores[position]\n }\n return a.index - b.index\n })\n .map((entry) => entry.row)\n}"); };
|
|
118
|
+
exports.generateSearchRankHelperCode = generateSearchRankHelperCode;
|
|
119
|
+
//# sourceMappingURL=search-relevance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-relevance.js","sourceRoot":"","sources":["../../src/search-relevance.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;;AAEH;;;GAGG;AACU,QAAA,kBAAkB,GAC7B,kFAAkF;IAClF,wGAAwG,CAAA;AAE1G,sFAAsF;AACtF,IAAM,QAAQ,GAAG,UAAC,KAAa;IAC7B,OAAA,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAC3B,oBAAoB,EACpB,UAAC,IAAI,IAAK,OAAA,KAAK,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAA5D,CAA4D,CACvE;AAHD,CAGC,CAAA;AAEH;;;;;;GAMG;AACH,IAAM,mBAAmB,GAAG,WAAI,CAC9B,yBAAyB;IACzB,0BAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;SACzB,MAAM,CAAC,UAAC,IAAI,IAAK,OAAA,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAvB,CAAuB,CAAC;SACzC,IAAI,CAAC,EAAE,CAAC;IACX,SAAS,CACV,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,MAAG,CAAA;AAExB;;;;;GAKG;AACI,IAAM,uBAAuB,GAAG,UACrC,UAAmB,IACR,OAAA,qCAA8B,QAAQ,CAAC,0BAAkB,CAAC,yXAWrE,UAAU;IACR,CAAC,CAAC,sDAAsD;IACxD,CAAC,CAAC,gIAC4D,4FAKhE,EAnBW,CAmBX,CAAA;AArBW,QAAA,uBAAuB,2BAqBlC;AAmBF,IAAM,WAAW,GAAG,4IAAoI,CAAA;AAE3I,QAAA,aAAa,GAAqB;IAC7C,OAAO,EAAE,oIAA4H;IACrI,OAAO,EAAE,WAAW;IACpB,UAAU,EAAE,IAAI;IAChB,OAAO,EAAE,IAAI;CACd,CAAA;AAEY,QAAA,mBAAmB,GAAqB;IACnD,OAAO,EAAE,uEAAiE;IAC1E,OAAO,EAAE,WAAW;IACpB,UAAU,EAAE,KAAK;IACjB,OAAO,EAAE,KAAK;CACf,CAAA;AAED,IAAM,eAAe,GAAG,wIAAgI,CAAA;AAE3I,QAAA,gBAAgB,GAAqB;IAChD,OAAO,EAAE,yFAAqF;IAC9F,OAAO,EAAE,eAAe;IACxB,UAAU,EAAE,KAAK;IACjB,OAAO,EAAE,KAAK;CACf,CAAA;AAEY,QAAA,kBAAkB,GAAqB;IAClD,OAAO,EAAE,+EAA6E;IACtF,OAAO,EAAE,eAAe;IACxB,UAAU,EAAE,KAAK;IACjB,OAAO,EAAE,KAAK;CACf,CAAA;AAEY,QAAA,iBAAiB,GAAqB;IACjD,OAAO,EAAE,+EAAyE;IAClF,OAAO,EAAE,sIAA8H;IACvI,UAAU,EAAE,KAAK;IACjB,OAAO,EAAE,KAAK;CACf,CAAA;AAED;;;;;;;;;;;;;;;GAeG;AACI,IAAM,4BAA4B,GAAG,UAC1C,OAAyB,IACd,OAAA,UAAG,IAAA,+BAAuB,EAAC,OAAO,CAAC,UAAU,CAAC,eAEzD,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,0CAAmC,QAAQ,CAAC,mBAAmB,CAAC,OAAI,CAAC,CAAC,CAAC,EAAE,mCACvE,OAAO,CAAC,OAAO,qCAChB,OAAO,CAAC,OAAO,gxBAmBnC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,oDAAgD,CAAC,CAAC,CAAC,uBAAuB,+BAG5F,OAAO,CAAC,OAAO;IACb,CAAC,CAAC,kLAA8K;IAChL,CAAC,CAAC,IAAI,0uBAWV,EAvCW,CAuCX,CAAA;AAzCW,QAAA,4BAA4B,gCAyCvC;AAEF;;;;;;;GAOG;AACI,IAAM,4BAA4B,GAAG,cAAc,OAAA,UAAG,IAAA,+BAAuB,EAAC,IAAI,CAAC,q4CAyBxF,EAzBwD,CAyBxD,CAAA;AAzBW,QAAA,4BAA4B,gCAyBvC"}
|