Skip to content

Commit

Permalink
feat: add the whole structure
Browse files Browse the repository at this point in the history
  • Loading branch information
productdevbook committed Jan 30, 2024
1 parent 5865411 commit 31ab604
Show file tree
Hide file tree
Showing 9 changed files with 368 additions and 8 deletions.
46 changes: 46 additions & 0 deletions src/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { andScope } from './plugins/andScope'
import { orScope } from './plugins/orScope'
import { orderByScope } from './plugins/orderByScope'
import { textScope } from './plugins/textScope'
import { twoPointScope } from './plugins/twoPointScope'
import type { SearchFilter } from './types'

export async function unSearch(
config: SearchFilter,
) {
config._data ??= {
wheres: [],
orderBy: [],
textArray: [],
}

config.default.ignore = config.default.ignore ?? {
filterText: ['id'],
}

config.regex = config.regex ?? /([a-zA-Z]{1,20}):(\/([^\/]+)\/\/|([^:\s]{1,100}))/g
config.plugins = config.plugins ?? [orScope, andScope, orderByScope, twoPointScope, textScope]

const textArray = config.search.split(' ')
config._data.textArray = textArray

for await (const plugin of config.plugins) {
if (typeof plugin === 'function') {
const { separation } = plugin()
if (typeof separation === 'function')
separation(config as Required<SearchFilter>)
}
}

for await (const plugin of config.plugins) {
if (typeof plugin === 'function') {
const { setup } = plugin()
if (typeof setup === 'function')
setup(config as Required<SearchFilter>)
}
}

return {
config,
}
}
10 changes: 2 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,2 @@
export function handler(_event: any, _context: any, callback: any) {
// eslint-disable-next-line no-console
console.log('Hello World')
callback(null, {
statusCode: 200,
body: 'Hello World',
})
}
export { unSearch } from './core'
export type { SearchFilter, FilterConfig } from './types'
40 changes: 40 additions & 0 deletions src/plugins/andScope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { defineFilterPlugin } from '../utils'

export function andScope() {
return defineFilterPlugin({
separation(params) {
for (const [index, text] of params._data.textArray.entries()) {
if (typeof text === 'object' || !text.includes('AND'))
continue

const andFunction = params.scopeTags.AND

params._data.textArray[index] = {
func: andFunction,
text,
index,
type: 'and',
}
}
},
setup(params) {
for (const text of params._data.textArray) {
if (typeof text !== 'object' || text.type !== 'and')
continue

if (typeof text === 'object' && text.type === 'and') {
const prev = params._data.textArray[text.index - 1]
const next = params._data.textArray[text.index + 1]

if (prev && next && typeof prev === 'object' && typeof next === 'object') {
params._data.wheres.push(text.func(
prev.func(params.columKeys[prev.key as keyof typeof params.columKeys], prev.text),
next.func(params.columKeys[next.key as keyof typeof params.columKeys], next.text),
))
params._data.textArray.splice(text.index - 1, 3)
}
}
}
},
})
}
40 changes: 40 additions & 0 deletions src/plugins/orScope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { defineFilterPlugin } from '../utils'

export function orScope() {
return defineFilterPlugin({
separation(params) {
for (const [index, text] of params._data.textArray.entries()) {
if (typeof text === 'object' || !text.includes('OR'))
continue

const orFunction = params.scopeTags.OR

params._data.textArray[index] = {
func: orFunction,
text,
index,
type: 'or',
}
}
},
setup(params) {
for (const text of params._data.textArray) {
if (typeof text !== 'object' || text.type !== 'or')
continue

if (typeof text === 'object' && text.type === 'or') {
const prev = params._data.textArray[text.index - 1]
const next = params._data.textArray[text.index + 1]

if (prev && next && typeof prev === 'object' && typeof next === 'object') {
params._data.wheres.push(text.func(
prev.func(params.columKeys[prev.key as keyof typeof params.columKeys], prev.text),
next.func(params.columKeys[next.key as keyof typeof params.columKeys], next.text),
))
params._data.textArray.splice(text.index - 1, 3)
}
}
}
},
})
}
48 changes: 48 additions & 0 deletions src/plugins/orderByScope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { defineFilterPlugin } from '../utils'

export function orderByScope() {
return defineFilterPlugin({
separation(params) {
for (const [index, text] of params._data.textArray.entries()) {
if (typeof text === 'object' || !((text.includes('asc:') || text.includes('desc:'))))
continue

if (text.includes('asc:[') || text.includes('desc:[')) {
const [columnKey, value] = text.split(':').map(key => key.trim())
const ascOrDesc = columnKey.includes('asc') ? 'asc' : 'desc'
const filterFunction = params.scopeTags[ascOrDesc as keyof typeof params.scopeTags]

for (const key of value.replace('[', '').replace(']', '').split(',')) {
params._data.textArray[index] = {
func: filterFunction,
text: key,
index,
type: 'orderBy',
}
}
}

if (text.includes('asc:') || text.includes('desc:')) {
const [columnKey, value] = text.split(':').map(key => key.trim())
const filterFunction = params.scopeTags[columnKey as keyof typeof params.scopeTags]
params._data.textArray[index] = {
func: filterFunction,
text: value,
index,
type: 'orderBy',
}
}
}
},
setup(params) {
for (const item of params._data.textArray) {
if (typeof item === 'object' && item.type === 'orderBy') {
params._data.orderBy.push(
item.func(params.columKeys[item.text as keyof typeof params.columKeys], item.text),
)
params._data.textArray.splice(item.index, 1)
}
}
},
})
}
39 changes: 39 additions & 0 deletions src/plugins/textScope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { defineFilterPlugin } from '../utils'

export function textScope(ignoreKeys: string[] = ['id']) {
return defineFilterPlugin({
separation(params) {
for (const [index, text] of params._data.textArray.entries()) {
if (typeof text === 'object'
|| text.includes(':')
|| text.includes('OR')
|| text.includes('AND')
)
continue

const filterFunction = params.scopeTags['<ilike>']

params._data.textArray[index] = {
func: filterFunction,
text,
index,
type: 'text',
}
}
},
setup(params) {
for (const text of params._data.textArray) {
if (typeof text === 'object' && text.type === 'text') {
const filterFunction = text.func
for (const key of Object.keys(params.columKeys)) {
if (ignoreKeys.includes(key))
continue

params._data.wheres.push(filterFunction(params.columKeys[key as keyof typeof params.columKeys], text.text))
params._data.textArray.splice(text.index, 1)
}
}
}
},
})
}
44 changes: 44 additions & 0 deletions src/plugins/twoPointScope.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { defineFilterPlugin } from '../utils'

export function twoPointScope() {
return defineFilterPlugin({
separation(params) {
for (const [index, text] of params._data.textArray.entries()) {
if (typeof text === 'object' || !text.includes(':'))
continue

const [columnKey, value] = text.split(':').map(key => key.trim())

let filter = params.scopeTagsArray.find(f => value.includes(f))
if (!filter) {
if (columnKey === 'id')
filter = '='

else
filter = '<ilike>'
}

const filterFunction = params.scopeTags[filter as keyof typeof params.scopeTags]

params._data.textArray[index] = {
func: filterFunction,
text: value.replace(filter, '').trim(),
index,
key: columnKey,
type: ':',
}
}
},
setup(params) {
for (const text of params._data.textArray) {
if (typeof text === 'object' && text.type === ':') {
const filterFunction = text.func
const key = text.key
const value = text.text
params._data.wheres.push(filterFunction(params.columKeys[key as keyof typeof params.columKeys], value))
params._data.textArray.splice(text.index, 1)
}
}
},
})
}
104 changes: 104 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
export interface SearchFilter {
/**
* username:hello, email:hello, username:[email protected] or:[username, email] and:[username, email] asc:[username, email] desc:[username, email]
*/
search: string
/**
* {
* Drizzle Example
* username: tablesPzg.user.username,
*
* Postgres Example
* username: SQL`users.username`
* }
*/
columKeys: Record<string, any>

/**
* {
* Drizzle Example
* '=': (key: Column, value: string) => eq(key, value),
* '!=': (key: Column, value: string) => ne(key, value),
*
* Postgres Example
* '=': (key: SQLWrapper, value: string) => SQL`${key} = ${value}`,
* '!=': (key: SQLWrapper, value: string) => SQL`${key} != ${value}`,
*
*/
scopeTags: {
[key in string]: (key: any, value: string) => void
}

/**
* The order is important here. If >= is at the beginning, >= will not be perceived and will be corrupted.
* Make sure that the order is correct.
* ['like', 'ilike', '>=', '<=', '>', '<', '!=', '=']
*/
scopeTagsArray: string[]

/**
*
* @example
* const regex = /([a-zA-Z]{1,20}):(\/([^\/]+)\/\/|([^:\s]{1,100}))/g
*
* @default
* /([a-zA-Z]{1,20}):(\/([^\/]+)\/\/|([^:\s]{1,100}))/g
*/
regex?: RegExp

/**
*
* @example
* const plugins = [ORScope, orderByScope]
*
* @default
* [ORScope, orderByScope]
*/
plugins?: (() => FilterConfig)[]

default: {
/**
* @default
* filter: SearchFilter.default.filterText
*/
filterText: {
filter: string
column: any
key: string
}[]
ignore?: {
/** @default ['id'] */
filterText?: string[]
}
}

/**
* @private
* @ignore
* @default
* {
* wheres: [],
* orderBy: [],
* }
*/
_data?: {
wheres: any[]
orderBy: any[]
textArray: (string | {
func: any
text: string
index: number
key?: string
type: string
})[]
}
}

export interface FilterConfig {
setup?(
params: Required<SearchFilter>,
): void
separation?(
params: Required<SearchFilter>,
): void
}
5 changes: 5 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { FilterConfig } from './types'

export function defineFilterPlugin(config: FilterConfig) {
return config
}

0 comments on commit 31ab604

Please sign in to comment.