Comparing version 0.0.2-0 to 0.0.3
export type Query<T> = { | ||
[P in keyof T]?: QueryCondition<T[P]>; | ||
} & Operations<T>; | ||
type QueryCondition<FieldType> = FieldType | Conditions<FieldType>; | ||
interface Conditions<ValueType> { | ||
export type QueryCondition<FieldType> = FieldType | Conditions<FieldType> | Operations<QueryCondition<FieldType>>; | ||
export interface Conditions<ValueType> { | ||
$eq?: ValueType; | ||
@@ -14,2 +14,8 @@ $ne?: ValueType; | ||
$nin?: ValueType[]; | ||
$sub?: ValueType[]; | ||
$nsub?: ValueType[]; | ||
$sup?: ValueType[]; | ||
$nsup?: ValueType[]; | ||
$con?: ValueType; | ||
$ncon?: ValueType; | ||
$exists?: boolean; | ||
@@ -20,4 +26,7 @@ $match?: RegExp | string; | ||
$nbits?: number; | ||
$typeof: string; | ||
$ntypeof: string; | ||
} | ||
interface Operations<T> { | ||
export type ConditionKeys = keyof Conditions<unknown>; | ||
export interface Operations<T> { | ||
$and?: Query<T>[]; | ||
@@ -28,3 +37,15 @@ $or?: Query<T>[]; | ||
} | ||
export declare const createQuery: <T extends object>(query: Query<T>, rootName: string) => (data: T) => any; | ||
export type OperationKeys = keyof Operations<unknown>; | ||
export declare const stringify: (value: unknown) => string; | ||
export interface Condition { | ||
(valuePath: string, value: any, context: Context): string; | ||
} | ||
declare class Context { | ||
private counter; | ||
private values; | ||
private cacheMap; | ||
register(code: string): string; | ||
toString(): string; | ||
} | ||
export declare const createFilter: <T extends object>(query?: Query<T>) => (data: T) => any; | ||
export type SortType = 'asc' | 'desc' | 1 | -1; | ||
@@ -34,6 +55,3 @@ export type Sort<T> = T extends object ? { | ||
} : SortType; | ||
export declare const SORT_EXPR: RegExp; | ||
export declare const SORT_MAP: Record<SortType, 1 | -1>; | ||
export declare const sortCodeGen: <T>(sort: Sort<T>, valuePath: [string, string], path: string) => string; | ||
export declare const createSort: <T>(sort: Sort<T>, rootName: string) => (data: T) => any; | ||
export declare const createSort: <T>(sort: Sort<T>) => (a: T, b: T) => any; | ||
export {}; |
@@ -0,1 +1,10 @@ | ||
export const stringify = (value)=>{ | ||
if (value === undefined) { | ||
return 'undefined'; | ||
} | ||
if (value instanceof RegExp) { | ||
return `/${value.source}/${value.flags}`; | ||
} | ||
return JSON.stringify(value); | ||
}; | ||
const conditions = { | ||
@@ -8,4 +17,19 @@ $eq: (valuePath, value)=>`(${valuePath} === ${value})`, | ||
$lte: (valuePath, value)=>`(${valuePath} <= ${value})`, | ||
$in: (valuePath, value)=>`(${value}.includes(${valuePath}))`, | ||
$nin: (valuePath, value)=>`(!${value}.includes(${valuePath}))`, | ||
$in: (valuePath, value, context)=>{ | ||
const name = context.register(`new Set(${value})`); | ||
return `${name}.has(${valuePath})`; | ||
}, | ||
$nin: (valuePath, value, context)=>`!${conditions.$in(valuePath, value, context)}`, | ||
$sub: (valuePath, value, context)=>{ | ||
const name = context.register(`new Set(${value})`); | ||
return `${valuePath}.every((value) => ${name}.has(value))`; | ||
}, | ||
$nsub: (valuePath, value, context)=>`!${conditions.$sub(valuePath, value, context)}`, | ||
$sup: (valuePath, value, context)=>{ | ||
const name = context.register(`new Set(${valuePath})`); | ||
return `${value}.every((value) => ${name}.has(value))`; | ||
}, | ||
$nsup: (valuePath, value, context)=>`!${conditions.$sup(valuePath, value, context)}`, | ||
$con: (valuePath, value)=>`${valuePath}.includes(${value})`, | ||
$ncon: (valuePath, value)=>`!${valuePath}.includes(${value})`, | ||
$exists: (valuePath, value)=>`((${valuePath} !== undefined && ${valuePath} !== null) === ${value})`, | ||
@@ -15,3 +39,5 @@ $match: (valuePath, value)=>`${value}.test('' + ${valuePath})`, | ||
$bits: (valuePath, value)=>`((${valuePath} & ${value}) !== 0)`, | ||
$nbits: (valuePath, value)=>`((${valuePath} & ${value}) === 0)` | ||
$nbits: (valuePath, value)=>`((${valuePath} & ${value}) === 0)`, | ||
$typeof: (valuePath, value)=>`(typeof ${valuePath} === ${value})`, | ||
$ntypeof: (valuePath, value)=>`(typeof ${valuePath} !== ${value})` | ||
}; | ||
@@ -21,10 +47,26 @@ const operations = { | ||
$or: (value)=>`(${value.join(' || ')})`, | ||
$nor: (value)=>`(!(${value.join(' || ')}))`, | ||
$not: (value)=>`(!(${value}))` | ||
$nor: (value)=>`!(${value.join(' || ')})`, | ||
$not: (value)=>`!(${value})` | ||
}; | ||
const queryCodeGen = (query, valuePath, path)=>{ | ||
class Context { | ||
counter = 0; | ||
values = []; | ||
cacheMap = new Map(); | ||
register(code) { | ||
if (!this.cacheMap.has(code)) { | ||
const name = `v$$${this.counter++}`; | ||
this.values.push(`const ${name} = ${code};`); | ||
this.cacheMap.set(code, name); | ||
} | ||
return this.cacheMap.get(code); | ||
} | ||
toString() { | ||
return this.values.join('\n'); | ||
} | ||
} | ||
const filterCodeGen = (query, valuePath, context)=>{ | ||
if (typeof query === 'object' && query !== null) { | ||
const condition = Object.entries(query).map(([key, value])=>{ | ||
if (key in conditions) { | ||
return conditions[key](valuePath, JSON.stringify(value)); | ||
return conditions[key](valuePath, stringify(value), context); | ||
} | ||
@@ -35,24 +77,27 @@ if (key in operations) { | ||
]; | ||
return operations[key](values.map((value)=>queryCodeGen(value, valuePath, path))); | ||
return operations[key](values.map((v)=>filterCodeGen(v, valuePath, context))); | ||
} | ||
const valueSubPath = `${valuePath}?.[${JSON.stringify(key)}]`; | ||
const valueSubPath = `${valuePath}?.[${stringify(key)}]`; | ||
if (value instanceof RegExp) { | ||
return conditions.$match(valueSubPath, value.toString()); | ||
return conditions.$match(valueSubPath, value.toString(), context); | ||
} | ||
if (typeof value === 'object' && value !== null) { | ||
return queryCodeGen(value, valueSubPath, path); | ||
return filterCodeGen(value, valueSubPath, context); | ||
} | ||
return conditions.$eq(valueSubPath, JSON.stringify(value)); | ||
return conditions.$eq(valueSubPath, JSON.stringify(value), context); | ||
}); | ||
return `${condition.join(' && ')}`; | ||
if (condition.length > 0) { | ||
return `${condition.join(' && ')}`; | ||
} | ||
} | ||
return 'true'; | ||
}; | ||
export const createQuery = (query, rootName)=>{ | ||
const code = queryCodeGen(query, 'data', rootName); | ||
const filter = new Function('data', `return ${code || 'true'};`); | ||
export const createFilter = (query)=>{ | ||
const context = new Context(); | ||
const code = filterCodeGen(query, 'data', context); | ||
const filter = new Function('data', `${context}\nreturn ${code};`); | ||
return (data)=>filter(data); | ||
}; | ||
export const SORT_EXPR = /^(asc|desc|1|-1)$/i; | ||
export const SORT_MAP = { | ||
const SORT_EXPR = /^(asc|desc|1|-1)$/i; | ||
const SORT_MAP = { | ||
asc: 1, | ||
@@ -63,3 +108,3 @@ desc: -1, | ||
}; | ||
export const sortCodeGen = (sort, valuePath, path)=>{ | ||
const sortCodeGen = (sort, valuePath)=>{ | ||
if (typeof sort === 'object' && sort !== null) { | ||
@@ -72,5 +117,7 @@ const condition = Object.entries(sort).map(([key, value])=>{ | ||
valueSubPathB | ||
], path); | ||
]); | ||
}); | ||
return condition.join(' || '); | ||
if (condition.length > 0) { | ||
return condition.join(' || '); | ||
} | ||
} | ||
@@ -85,12 +132,11 @@ if (typeof sort === 'string' && SORT_EXPR.test(sort)) { | ||
}; | ||
export const createSort = (sort, rootName)=>{ | ||
export const createSort = (sort)=>{ | ||
const code = sortCodeGen(sort, [ | ||
'a', | ||
'b' | ||
], rootName); | ||
const filter = new Function('a', 'b', `return ${code || 0};`); | ||
console.log(filter.toString()); | ||
return (data)=>filter(data); | ||
]); | ||
const sortFn = new Function('a', 'b', `return ${code};`); | ||
return (a, b)=>sortFn(a, b); | ||
}; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "ajoq", | ||
"version": "0.0.2-0", | ||
"version": "0.0.3", | ||
"description": "", | ||
@@ -17,4 +17,17 @@ "type": "module", | ||
"devDependencies": { | ||
"@swc/jest": "^0.2.37", | ||
"@types/jest": "^29.5.14", | ||
"@types/node": "^22.10.0", | ||
"@typescript-eslint/eslint-plugin": "^8.16.0", | ||
"@typescript-eslint/parser": "^8.16.0", | ||
"@typescript-eslint/typescript-estree": "^8.16.0", | ||
"eslint": "^9.15.0", | ||
"eslint-config-airbnb-base": "^15.0.0", | ||
"eslint-config-prettier": "^9.1.0", | ||
"eslint-plugin-import": "^2.31.0", | ||
"eslint-plugin-prettier": "^5.2.1", | ||
"husky": "^9.1.7", | ||
"inop": "^0.7.4", | ||
"jest": "^29.7.0", | ||
"prettier": "^3.4.1", | ||
"ts-node": "^10.9.2", | ||
@@ -24,4 +37,6 @@ "typescript": "^5.7.2" | ||
"scripts": { | ||
"build": "rm -rf build && NODE_ENV=production inop src/ build -i __tests__ && tsc --declaration --emitDeclarationOnly" | ||
"build": "rm -rf build && NODE_ENV=production inop src/ build -i __tests__ && tsc --declaration --emitDeclarationOnly", | ||
"test": "jest", | ||
"lint": "eslint src" | ||
} | ||
} |
141
src/index.ts
@@ -5,7 +5,5 @@ export type Query<T> = { | ||
type QueryCondition<FieldType> = | ||
| FieldType | ||
| Conditions<FieldType>; | ||
export type QueryCondition<FieldType> = FieldType | Conditions<FieldType> | Operations<QueryCondition<FieldType>>; | ||
interface Conditions<ValueType> { | ||
export interface Conditions<ValueType> { | ||
$eq?: ValueType; | ||
@@ -19,2 +17,8 @@ $ne?: ValueType; | ||
$nin?: ValueType[]; | ||
$sub?: ValueType[]; | ||
$nsub?: ValueType[]; | ||
$sup?: ValueType[]; | ||
$nsup?: ValueType[]; | ||
$con?: ValueType; | ||
$ncon?: ValueType; | ||
$exists?: boolean; | ||
@@ -25,7 +29,9 @@ $match?: RegExp | string; | ||
$nbits?: number; | ||
}; | ||
$typeof: string; | ||
$ntypeof: string; | ||
} | ||
type ConditionKeys = keyof Conditions<unknown>; | ||
export type ConditionKeys = keyof Conditions<unknown>; | ||
interface Operations<T> { | ||
export interface Operations<T> { | ||
$and?: Query<T>[]; | ||
@@ -35,7 +41,21 @@ $or?: Query<T>[]; | ||
$not?: Query<T>; | ||
} | ||
export type OperationKeys = keyof Operations<unknown>; | ||
export const stringify = (value: unknown) => { | ||
if (value === undefined) { | ||
return 'undefined'; | ||
} | ||
if (value instanceof RegExp) { | ||
return `/${value.source}/${value.flags}`; | ||
} | ||
return JSON.stringify(value); | ||
}; | ||
type OperationKeys = keyof Operations<unknown>; | ||
export interface Condition { | ||
(valuePath: string, value: any, context: Context): string; | ||
} | ||
const conditions: Record<ConditionKeys, any> = { | ||
const conditions: Record<ConditionKeys, Condition> = { | ||
$eq: (valuePath: string, value: unknown) => `(${valuePath} === ${value})`, | ||
@@ -47,9 +67,26 @@ $ne: (valuePath: string, value: unknown) => `(${valuePath} !== ${value})`, | ||
$lte: (valuePath: string, value: unknown) => `(${valuePath} <= ${value})`, | ||
$in: (valuePath: string, value: unknown) => `(${value}.includes(${valuePath}))`, | ||
$nin: (valuePath: string, value: unknown) => `(!${value}.includes(${valuePath}))`, | ||
$exists: (valuePath: string, value: unknown) => `((${valuePath} !== undefined && ${valuePath} !== null) === ${value})`, | ||
$match: (valuePath: string, value: unknown) => `${value}.test('' + ${valuePath})`, | ||
$nmatch: (valuePath: string, value: unknown) => `!${value}.test('' + ${valuePath})`, | ||
$in: (valuePath: string, value: unknown, context: Context) => { | ||
const name = context.register(`new Set(${value})`); | ||
return `${name}.has(${valuePath})`; | ||
}, | ||
$nin: (valuePath: string, value: unknown, context: Context) => `!${conditions.$in(valuePath, value, context)}`, | ||
$sub: (valuePath: string, value: unknown, context: Context) => { | ||
const name = context.register(`new Set(${value})`); | ||
return `${valuePath}.every((value) => ${name}.has(value))`; | ||
}, | ||
$nsub: (valuePath: string, value: unknown, context: Context) => `!${conditions.$sub(valuePath, value, context)}`, | ||
$sup: (valuePath: string, value: unknown, context: Context) => { | ||
const name = context.register(`new Set(${valuePath})`); | ||
return `${value}.every((value) => ${name}.has(value))`; | ||
}, | ||
$nsup: (valuePath: string, value: unknown, context: Context) => `!${conditions.$sup(valuePath, value, context)}`, | ||
$con: (valuePath: string, value: unknown) => `${valuePath}.includes(${value})`, | ||
$ncon: (valuePath: string, value: unknown) => `!${valuePath}.includes(${value})`, | ||
$exists: (valuePath: string, value: boolean) => `((${valuePath} !== undefined && ${valuePath} !== null) === ${value})`, | ||
$match: (valuePath: string, value: RegExp) => `${value}.test('' + ${valuePath})`, | ||
$nmatch: (valuePath: string, value: RegExp) => `!${value}.test('' + ${valuePath})`, | ||
$bits: (valuePath: string, value: unknown) => `((${valuePath} & ${value}) !== 0)`, | ||
$nbits: (valuePath: string, value: unknown) => `((${valuePath} & ${value}) === 0)`, | ||
$typeof: (valuePath: string, value: unknown) => `(typeof ${valuePath} === ${value})`, | ||
$ntypeof: (valuePath: string, value: unknown) => `(typeof ${valuePath} !== ${value})`, | ||
}; | ||
@@ -60,35 +97,54 @@ | ||
$or: (value: unknown[]) => `(${value.join(' || ')})`, | ||
$nor: (value: unknown[]) => `(!(${value.join(' || ')}))`, | ||
$not: (value: unknown) => `(!(${value}))`, | ||
$nor: (value: unknown[]) => `!(${value.join(' || ')})`, | ||
$not: (value: unknown[]) => `!(${value})`, | ||
}; | ||
class Context { | ||
private counter = 0; | ||
private values: string[] = []; | ||
private cacheMap = new Map<string, string>(); | ||
register(code: string) { | ||
if (!this.cacheMap.has(code)) { | ||
const name = `v$$${this.counter++}`; | ||
this.values.push(`const ${name} = ${code};`); | ||
this.cacheMap.set(code, name); | ||
} | ||
return this.cacheMap.get(code)!; | ||
} | ||
toString() { | ||
return this.values.join('\n'); | ||
} | ||
} | ||
const queryCodeGen = <T extends object>(query: Query<T>, valuePath: string, path: string): string => { | ||
const filterCodeGen = <T extends object>(query: Query<T> | undefined, valuePath: string, context: Context): string => { | ||
if (typeof query === 'object' && query !== null) { | ||
const condition = Object.entries(query).map(([key, value]) => { | ||
if (key in conditions) { | ||
return conditions[key as ConditionKeys](valuePath, JSON.stringify(value)); | ||
return conditions[key as ConditionKeys](valuePath, stringify(value), context); | ||
} | ||
if (key in operations) { | ||
const values = Array.isArray(value) ? value : [value]; | ||
return operations[key as OperationKeys](values.map((value) => queryCodeGen(value, valuePath, path))); | ||
return operations[key as OperationKeys](values.map((v) => filterCodeGen(v, valuePath, context))); | ||
} | ||
const valueSubPath = `${valuePath}?.[${JSON.stringify(key)}]`; | ||
const valueSubPath = `${valuePath}?.[${stringify(key)}]`; | ||
if (value instanceof RegExp) { | ||
return conditions.$match(valueSubPath, value.toString()); | ||
return conditions.$match(valueSubPath, value.toString(), context); | ||
} | ||
if (typeof value === 'object' && value !== null) { | ||
return queryCodeGen(value, valueSubPath, path); | ||
return filterCodeGen(value, valueSubPath, context); | ||
} | ||
return conditions.$eq(valueSubPath, JSON.stringify(value)); | ||
return conditions.$eq(valueSubPath, JSON.stringify(value), context); | ||
}); | ||
return `${condition.join(' && ')}`; | ||
if (condition.length > 0) { | ||
return `${condition.join(' && ')}`; | ||
} | ||
} | ||
return 'true'; | ||
} | ||
}; | ||
export const createQuery = <T extends object>(query: Query<T>, rootName: string) => { | ||
const code = queryCodeGen(query, 'data', rootName); | ||
const filter = new Function('data', `return ${code || 'true'};`); | ||
export const createFilter = <T extends object>(query?: Query<T>) => { | ||
const context = new Context(); | ||
const code = filterCodeGen(query, 'data', context); | ||
const filter = new Function('data', `${context}\nreturn ${code};`); | ||
return (data: T) => filter(data); | ||
@@ -101,9 +157,9 @@ }; | ||
? { | ||
[K in keyof T]?: T[K] extends object ? Sort<T[K]> : SortType; | ||
} | ||
[K in keyof T]?: T[K] extends object ? Sort<T[K]> : SortType; | ||
} | ||
: SortType; | ||
export const SORT_EXPR = /^(asc|desc|1|-1)$/i | ||
const SORT_EXPR = /^(asc|desc|1|-1)$/i; | ||
export const SORT_MAP: Record<SortType, 1 | -1> = { | ||
const SORT_MAP: Record<SortType, 1 | -1> = { | ||
asc: 1, | ||
@@ -115,3 +171,3 @@ desc: -1, | ||
export const sortCodeGen = <T>(sort: Sort<T>, valuePath: [string, string], path: string): string => { | ||
const sortCodeGen = <T>(sort: Sort<T>, valuePath: [string, string]): string => { | ||
if (typeof sort === 'object' && sort !== null) { | ||
@@ -121,5 +177,7 @@ const condition = Object.entries(sort).map(([key, value]) => { | ||
const valueSubPathB = `${valuePath[1]}?.[${JSON.stringify(key)}]`; | ||
return sortCodeGen(value, [valueSubPathA, valueSubPathB], path); | ||
return sortCodeGen(value, [valueSubPathA, valueSubPathB]); | ||
}); | ||
return condition.join(' || '); | ||
if (condition.length > 0) { | ||
return condition.join(' || '); | ||
} | ||
} | ||
@@ -135,7 +193,6 @@ if (typeof sort === 'string' && SORT_EXPR.test(sort)) { | ||
export const createSort = <T>(sort: Sort<T>, rootName: string) => { | ||
const code = sortCodeGen(sort, ['a', 'b'], rootName); | ||
const filter = new Function('a', 'b', `return ${code || 0};`); | ||
console.log(filter.toString()); | ||
return (data: T) => filter(data); | ||
export const createSort = <T>(sort: Sort<T>) => { | ||
const code = sortCodeGen(sort, ['a', 'b']); | ||
const sortFn = new Function('a', 'b', `return ${code};`); | ||
return (a: T, b: T) => sortFn(a, b); | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
49391
581
17
10