web_lib

Common web application libraries
git clone https://radroots.dev/git/web_lib.git
Log | Files | Refs | LICENSE

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 };