agsamantha/node_modules/@langchain/community/dist/structured_query/supabase_utils.js
2024-10-02 15:15:21 -05:00

250 lines
9.7 KiB
JavaScript

import { Comparators, Comparison, Operation, Operators, StructuredQuery, } from "@langchain/core/structured_query";
/**
* Utility class used to duplicate parameters for a proxy object,
* specifically designed to work with `SupabaseFilter` objects. It
* contains methods to handle different types of operations such as "or",
* "filter", "in", "contains", "textSearch", "match", "not", and default
* operations.
*/
export class ProxyParamsDuplicator {
constructor() {
Object.defineProperty(this, "duplicationAllowedOps", {
enumerable: true,
configurable: true,
writable: true,
value: [
"eq",
"neq",
"lt",
"lte",
"gt",
"gte",
"like",
"ilike",
"or",
"in",
"contains",
"match",
"not",
"textSearch",
"filter",
]
});
Object.defineProperty(this, "values", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
}
/**
* Creates a proxy handler for a `SupabaseFilter` object. The handler
* intercepts get operations and applies specific logic based on the
* property being accessed.
* @returns A proxy handler for a `SupabaseFilter` object.
*/
buildProxyHandler() {
const proxyHandler = {
get: (target, prop, receiver) => {
if (typeof target[prop] === "function") {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (...args) => {
if (this.duplicationAllowedOps.includes(String(prop))) {
switch (String(prop)) {
case "or":
// args[0]: filters, args[1]: { foreignTable }
this.addOrClause(args[0], args[1]);
break;
case "filter":
// args[0]: column, args[1]: operator, args[2]: value
this.addFilterClause(args[0], args[1], args[2]);
break;
case "in":
// args[0]: column, args[1]: values
this.addInClause(args[0], args[1]);
break;
case "contains":
// args[0]: column, args[1]: value
this.addContainsClause(args[0], args[1]);
break;
case "textSearch":
// args[0]: column, args[1]: query, args[2]: { config, type }
this.addTextSearchClause(args[0], args[1], args[2]);
break;
case "match":
// args[0]: query
this.addMatchClause(args[0]);
break;
case "not":
// args[0]: column, args[1]: operator, args[2]: value
this.addNotClause(args[0], args[1], args[2]);
break;
default:
// args[0]: column, args[1]: value
this.addDefaultOpClause(prop, args[0], args[1]);
}
return new Proxy(target, proxyHandler);
}
else {
throw new Error("Filter operation not supported for 'or' mergeFiltersOperator");
}
};
}
else {
return Reflect.get(target, prop, receiver);
}
},
};
return proxyHandler;
}
/**
* Removes type annotations from a value string.
* @param value The value string to clean.
* @returns The cleaned value string.
*/
removeType(value) {
let cleanedValue = value;
if (cleanedValue.includes("::float")) {
cleanedValue = cleanedValue.replace("::float", "");
}
if (cleanedValue.includes("::int")) {
cleanedValue = cleanedValue.replace("::int", "");
}
return cleanedValue;
}
/**
* Adds a default operation clause to the values array.
* @param prop The operation property.
* @param column The column to apply the operation to.
* @param value The value for the operation.
*/
addDefaultOpClause(prop, column, value) {
this.values.push([this.removeType(column), `${String(prop)}.${value}`]);
}
/**
* Adds an 'or' clause to the values array.
* @param filters The filters for the 'or' clause.
* @param foreignTable Optional foreign table for the 'or' clause.
*/
addOrClause(filters, { foreignTable } = {}) {
const key = foreignTable ? `${foreignTable}.or` : "or";
this.values.push([this.removeType(key), `(${filters})`]);
}
/**
* Adds a 'filter' clause to the values array.
* @param column The column to apply the filter to.
* @param operator The operator for the filter.
* @param value The value for the filter.
*/
addFilterClause(column, operator, value) {
this.values.push([this.removeType(column), `${operator}.${value}`]);
}
/**
* Adds an 'in' clause to the values array.
* @param column The column to apply the 'in' clause to.
* @param values The values for the 'in' clause.
*/
addInClause(column, values) {
const cleanedValues = values
.map((s) => {
if (typeof s === "string" && /[,()]/.test(s))
return `"${s}"`;
else
return `${s}`;
})
.join(",");
this.values.push([this.removeType(column), `in.(${cleanedValues})`]);
}
/**
* Adds a 'contains' clause to the values array.
* @param column The column to apply the 'contains' clause to.
* @param value The value for the 'contains' clause.
*/
addContainsClause(column, value) {
if (typeof value === "string") {
this.values.push([this.removeType(column), `cs.${value}`]);
}
else if (Array.isArray(value)) {
this.values.push([this.removeType(column), `cs.{${value.join(",")}}`]);
}
else {
this.values.push([
this.removeType(column),
`cs.${JSON.stringify(value)}`,
]);
}
}
/**
* Adds a 'textSearch' clause to the values array.
* @param column The column to apply the 'textSearch' clause to.
* @param query The query for the 'textSearch' clause.
* @param config Optional configuration for the 'textSearch' clause.
* @param type Optional type for the 'textSearch' clause.
*/
addTextSearchClause(column, query, { config, type, } = {}) {
let typePart = "";
if (type === "plain") {
typePart = "pl";
}
else if (type === "phrase") {
typePart = "ph";
}
else if (type === "websearch") {
typePart = "w";
}
const configPart = config === undefined ? "" : `(${config})`;
this.values.push([
this.removeType(column),
`${typePart}fts${configPart}.${query}`,
]);
}
/**
* Adds a 'not' clause to the values array.
* @param column The column to apply the 'not' clause to.
* @param operator The operator for the 'not' clause.
* @param value The value for the 'not' clause.
*/
addNotClause(column, operator, value) {
this.values.push([column, `not.${operator}.${value}`]);
}
/**
* Adds a 'match' clause to the values array.
* @param query The query for the 'match' clause.
*/
addMatchClause(query) {
Object.entries(query).forEach(([column, value]) => {
this.values.push([column, `eq.${value}`]);
});
}
/**
* Returns the flattened parameters as a string.
* @returns The flattened parameters as a string.
*/
flattenedParams() {
const mapped = this.values.map(([k, v]) => `${k}.${v}`);
if (mapped.length === 1)
return mapped[0];
return `and(${mapped.join(",")})`;
}
/**
* Gets flattened parameters from a `SupabaseFilter` and a
* `SupabaseFilterRPCCall`.
* @param rpc The `SupabaseFilter` object.
* @param filter The `SupabaseFilterRPCCall` object.
* @returns The flattened parameters as a string.
*/
static getFlattenedParams(rpc, filter) {
const proxiedDuplicator = new ProxyParamsDuplicator();
const proxiedRpc = new Proxy(rpc, proxiedDuplicator.buildProxyHandler());
void filter(proxiedRpc);
return proxiedDuplicator.flattenedParams();
}
}
/**
* Converts a `SupabaseMetadata` object into a `StructuredQuery` object.
* The function creates a new `StructuredQuery` object and uses the
* `Operation` and `Comparison` classes to build the query.
*/
export function convertObjectFilterToStructuredQuery(objFilter) {
return new StructuredQuery("", new Operation(Operators.and, Object.entries(objFilter).map(([column, value]) => new Comparison(Comparators.eq, column, value))));
}