index.ts (6820B)
1 import { ValidationRegex } from "../types/index.js"; 2 3 export type IModelsQueryValue = string | number | boolean | null; 4 export type IModelsQueryBindValue = string | number | boolean | null;; 5 export type IModelsQueryBindValueTuple = [string, IModelsQueryValue]; 6 export type IModelsQueryBindValueOpt = (IModelsQueryBindValue | null); 7 export type IModelsQueryFilterOption = `equals` | `starts-with` | `ends-with` | `contains` | `ne`; 8 export type IModelsQueryFilterOptionList = `between` | `in`; 9 export type IModelsQueryFilterCondition = `and` | `or` | `not` 10 11 export type IModelsSortCreatedAt = 'newest' | 'oldest'; 12 export type IModelsQueryParam = { query: string; bind_values: IModelsQueryBindValue[] }; 13 export type IModelsFormErrorTuple = [boolean, string]; 14 export type IModelsFormValidationTuple = [RegExp, string]; 15 export type IModelsSchemaErrors = { err_s: string[]; }; 16 export type IModelsForm = { 17 label?: string; 18 placeholder?: string; 19 validateKeypress?: boolean; 20 preventFocusRest?: boolean; 21 hidden?: boolean; 22 optional?: boolean; 23 default?: string | number; 24 rxpv: ValidationRegex; 25 }; 26 27 export type IModelQueryFilterMapValuesTuplesOption = [IModelsQueryValue, IModelsQueryFilterOption]; 28 export type IModelQueryFilterMapValuesTuplesOptionList = [IModelsQueryValue[], IModelsQueryFilterOptionList]; 29 export type IModelQueryFilterMapValuesTuples = ModelQueryFilterMapTuple<IModelQueryFilterMapValuesTuplesOption> | ModelQueryFilterMapTuple<IModelQueryFilterMapValuesTuplesOptionList>; 30 export type IModelQueryFilterMapValues = IModelsQueryValue | IModelQueryFilterMapValuesTuples; 31 32 export type ModelQueryFilterMapTupleBasis = 33 | [IModelsQueryValue, IModelsQueryFilterOption] 34 | [IModelsQueryValue, IModelsQueryFilterOption, IModelsQueryFilterCondition] 35 | [IModelsQueryValue[], IModelsQueryFilterOptionList] 36 | [IModelsQueryValue[], IModelsQueryFilterOptionList, IModelsQueryFilterCondition]; 37 38 export type ModelQueryFilterMapTuple<T extends ModelQueryFilterMapTupleBasis> = 39 T extends [IModelsQueryValue, IModelsQueryFilterOption] 40 ? [IModelsQueryValue, IModelsQueryFilterOption, IModelsQueryFilterCondition] 41 : T extends [IModelsQueryValue[], IModelsQueryFilterOptionList] 42 ? [IModelsQueryValue[], IModelsQueryFilterOptionList, IModelsQueryFilterCondition] 43 : T; 44 45 export type IModelQueryFilterMap<ModelFilter extends object> = { 46 [K in keyof ModelFilter]: ModelFilter[K] | [ModelFilter[K], IModelsQueryFilterOption] | [ModelFilter[K], IModelsQueryFilterOption, IModelsQueryFilterCondition] | [ModelFilter[K][], IModelsQueryFilterOptionList] | [ModelFilter[K][], IModelsQueryFilterOptionList, IModelsQueryFilterCondition]; 47 }; 48 export type IModelQueryFilterMapParsed = { query_values: string[]; bind_values: IModelsQueryValue[]; }; 49 50 export const parse_model_query_value = (val: IModelsQueryValue): IModelsQueryBindValue => { 51 if (typeof val === `boolean`) return val ? '1' : '0'; 52 else if (typeof val === `number`) return val; 53 else if (typeof val === `string` && val) return val; 54 return null; 55 }; 56 57 export const is_model_query_filter_option = (value: string): value is IModelsQueryFilterOption => { 58 return ['equals', 'starts-with', 'ends-with', 'contains', 'ne'].includes(value); 59 } 60 61 export const is_model_query_filter_option_list = (value: string): value is IModelsQueryFilterOptionList => { 62 return ['between', 'in'].includes(value); 63 } 64 65 export const is_model_query_values = (value: unknown): value is IModelsQueryValue => { 66 return typeof value === `string` || typeof value === `number` || typeof value === `boolean`; 67 } 68 69 export const list_model_query_values_assert = (arr: (IModelsQueryValue | undefined)[]): (IModelsQueryValue)[] => { 70 return arr.filter((item): item is string | number | boolean => item !== undefined); 71 } 72 73 export const parse_model_filter_map = <T extends object>(opts: IModelQueryFilterMap<T>): IModelQueryFilterMapParsed => { 74 const bind_values: IModelsQueryValue[] = []; 75 const query_values: string[] = []; 76 77 for (const [index, entry] of Object.entries(opts).entries()) { 78 const [field, filters] = entry as [string, IModelQueryFilterMapValues]; 79 80 if (is_model_query_values(filters)) { 81 query_values.push(`${field} = ?`); 82 bind_values.push(filters); 83 } else if (Array.isArray(filters)) { 84 const [filters_val, filters_opt] = filters; 85 const filter_condition = index === 0 ? `` : typeof filters[2] === `undefined` ? `AND ` : ` ${filters[2]}`; 86 if (is_model_query_values(filters_val) && is_model_query_filter_option(filters_opt)) { 87 switch (filters_opt) { 88 case `starts-with`: { 89 query_values.push(`${filter_condition}${field} LIKE ?`); 90 bind_values.push(`${filters[0]}%`); 91 } break; 92 case `ends-with`: { 93 query_values.push(`${filter_condition}${field} LIKE ?`); 94 bind_values.push(`%${filters[0]}`); 95 } break; 96 case `contains`: { 97 query_values.push(`${filter_condition}${field} LIKE ?`); 98 bind_values.push(`%${filters[0]}%`); 99 } break; 100 case `ne`: { 101 query_values.push(`${filter_condition}${field} != ?`); 102 bind_values.push(`${filters[0]}`); 103 } break; 104 case `equals`: { 105 query_values.push(`${filter_condition}${field} = ?`); 106 bind_values.push(filters[0]); 107 } break; 108 default: 109 throw new Error("util.model.parse_model_filter_map.invalid_condition"); 110 }; 111 } else if (is_model_query_filter_option_list(filters_opt)) { 112 switch (filters_opt) { 113 case `between`: { 114 query_values.push(`${filter_condition}${field} BETWEEN ? AND ?`); 115 bind_values.push(...filters[0].slice(0, 2)); 116 } break; 117 case `in`: { 118 query_values.push(`${filter_condition}${field} IN (${`? `.repeat(filters[0].length).trim().split(" ").join(", ")})`); 119 bind_values.push(...list_model_query_values_assert(filters[0])); 120 } break; 121 default: 122 throw new Error("util.model.parse_model_filter_map.invalid_condition"); 123 }; 124 } 125 } 126 } 127 if (!query_values.length) throw new Error("Error: invalid filter."); 128 if (!bind_values.length) throw new Error("Error: invalid filter."); 129 return { query_values, bind_values }; 130 };