Skip to content

Instantly share code, notes, and snippets.

@ezzabuzaid
Last active March 12, 2025 01:06
Show Gist options
  • Select an option

  • Save ezzabuzaid/94a8b865b806dc89214e5b01253d2f78 to your computer and use it in GitHub Desktop.

Select an option

Save ezzabuzaid/94a8b865b806dc89214e5b01253d2f78 to your computer and use it in GitHub Desktop.
January archive
import Ajv from 'ajv';
import addErrors from 'ajv-errors';
import addFormats from 'ajv-formats';
import validator from 'validator';
import { ErrorObject, JSONSchemaType } from 'ajv';
import { PartialSchema } from 'ajv/dist/types/json-schema';
import { ProblemDetailsException } from 'rfc-7807-problem-details';
const ajv = new Ajv({
allErrors: true,
useDefaults: true,
removeAdditional: 'failing',
coerceTypes: true,
});
addErrors(ajv);
addFormats(ajv);
function isBetween(date: string, startDate: string, endDate: string) {
if (!date) {
return false;
}
if (!startDate) {
return false;
}
if (!endDate) {
return false;
}
return (
validator.isAfter(date, startDate) && validator.isBefore(date, endDate)
);
}
type Input<T> =
T extends Record<infer K, any>
? {
[P in K]: unknown;
}
: never;
const validations = [
['isBefore', validator.isBefore],
['isAfter', validator.isAfter],
['isBoolean', validator.isBoolean],
['isDate', validator.isDate],
['isNumeric', validator.isNumeric],
['isLatLong', validator.isLatLong],
['isMobilePhone', validator.isMobilePhone],
['isEmpty', validator.isEmpty],
['isDecimal', validator.isDecimal],
['isURL', validator.isURL],
['isEmail', validator.isEmail],
['isBetween', isBetween],
];
validations.forEach(([key, value]) => {
const keyword = key as string;
ajv.addKeyword({
keyword: keyword,
validate: (schema: any, data: any) => {
if (schema === undefined || schema === null) {
return false;
}
const func = value as any;
return func.apply(validator, [
data,
...(Array.isArray(schema) ? schema : [schema]),
]);
},
});
});
export function createSchema<T>(
properties: Record<
keyof T,
PartialSchema<any> & {
required?: boolean;
}
>,
): PartialSchema<T> {
const required: string[] = [];
const requiredErrorMessages: Record<string, string> = {};
for (const [key, value] of Object.entries(properties) as any[]) {
if (value.required) {
required.push(key);
}
if ('errorMessage' in value && value.errorMessage?.required) {
// move the required error message from the property schema to the root schema
// as the required keyword is not part of the property schema
requiredErrorMessages[key] = value.errorMessage.required;
delete value.errorMessage.required;
}
}
const extendSchema: Record<string, unknown> = {};
if (Object.keys(requiredErrorMessages).length) {
extendSchema['errorMessage'] = {
required: requiredErrorMessages,
};
}
const clearProperties = Object.fromEntries(
(Object.entries(properties) as any[]).map(([key, value]) => {
const { required, ...rest } = value;
return [key, rest];
}),
);
return {
type: 'object',
properties: clearProperties,
required: required,
additionalProperties: false,
...extendSchema,
} as JSONSchemaType<T>;
}
/**
* Validate input against schema
*
* @param schema ajv augmented json-schema
* @param input input to validate
* @returns
*/
export function validateInput<T>(
schema: PartialSchema<T>,
input: Record<keyof T, unknown>,
): asserts input is T {
const validate = ajv.compile(schema);
const valid = validate(input);
if (!valid && validate.errors) {
throw formatErrors(validate.errors);
}
}
function formatErrors(
errors: ErrorObject<string, Record<string, any>, unknown>[],
parent?: ErrorObject<string, Record<string, any>, unknown>,
): ErrorObject<string, Record<string, any>, unknown> {
return errors.reduce(
(acc, it) => {
if (it.keyword === 'errorMessage') {
return {
...acc,
...formatErrors(it.params['errors'], it),
};
}
const property = (it.instancePath || it.params['missingProperty'])
.replace('.', '')
.replace('/', '');
return { ...acc, [property]: parent?.message || it.message || '' };
},
{} as ErrorObject<string, Record<string, any>, unknown>,
);
}
export function validateOrThrow<T>(
schema: PartialSchema<T>,
input: Record<keyof T, unknown>,
): asserts input is T {
try {
validateInput(schema, input);
} catch (errors: any) {
const exception = new ProblemDetailsException({
type: 'validation-failed',
status: 400,
title: 'Bad Request.',
detail: 'Validation failed.',
});
exception.Details.errors = errors;
throw exception;
}
}
import { classes } from '@automapper/classes';
import {
CamelCaseNamingConvention,
createMap,
createMapper,
} from '@automapper/core';
export const mapper = createMapper({
strategyInitializer: classes(),
namingConventions: new CamelCaseNamingConvention(),
});
export function AutoMapHost(): ClassDecorator {
return function (target) {
createMap(mapper, target as never, target as never);
};
}
import type { Plugin } from 'prettier';
export async function formatCode(
code: string,
extension?: string,
ignoreError = true
): Promise<string> {
if (!code || code.trim().length === 0) return '';
function whatIsParserImport(): {
parserImport: Promise<Plugin>[];
parserName: string;
} {
switch (extension) {
case 'ts':
return {
parserImport: [import('prettier/plugins/typescript')],
parserName: 'typescript',
};
case 'js':
return {
parserImport: [import('prettier/plugins/babel')],
parserName: 'babel',
};
case 'html':
return {
parserImport: [import('prettier/plugins/html')],
parserName: 'html',
};
case 'css':
return {
parserImport: [import('prettier/plugins/postcss')],
parserName: 'css',
};
case 'scss':
return {
parserImport: [import('prettier/plugins/postcss')],
parserName: 'scss',
};
case 'code-snippets':
case 'json':
case 'prettierrc':
return {
parserImport: [import('prettier/plugins/babel')],
parserName: 'json',
};
case 'md':
return {
parserImport: [import('prettier/plugins/markdown')],
parserName: 'markdown',
};
case 'yaml':
case 'yml':
return {
parserImport: [import('prettier/plugins/yaml')],
parserName: 'yaml',
};
case '':
case 'gitignore':
case 'dockerignore':
case 'prettierignore':
case 'Dockerfile':
case 'toml':
case 'env':
case 'txt':
return {
parserImport: [],
parserName: '',
};
default:
return {
parserImport: [],
parserName: '',
};
}
}
const { parserImport, parserName } = whatIsParserImport();
if (!parserName) return code;
const [prettier, ...plugins] = await Promise.all([
import('prettier/standalone'),
import('prettier/plugins/estree').then((e) => e as any),
...parserImport,
] as const);
try {
return prettier
.format(code, {
parser: parserName,
plugins: plugins,
singleQuote: true,
})
.then((formattedCode) => formattedCode.trim());
} catch (error) {
if (error instanceof Error)
if (error.name === 'SyntaxError') {
return ignoreError === true ? code : formatCode(code, 'ts', true);
}
if (!ignoreError) {
throw error;
}
return code;
}
}
interface Language {
code: string;
name: string;
native: string;
}
interface Location {
geoname_id: number;
capital: string;
languages: Language[];
country_flag: string;
country_flag_emoji: string;
country_flag_emoji_unicode: string;
calling_code: string;
is_eu: boolean;
}
interface TimeZone {
id: string;
current_time: Date;
gmt_offset: number;
code: string;
is_daylight_saving: boolean;
}
interface Currency {
code: string;
name: string;
plural: string;
symbol: string;
symbol_native: string;
}
interface Connection {
asn: number;
isp: string;
}
export interface IpData {
ip: string;
type: string;
continent_code: string;
continent_name: string;
country_code: string;
country_name: string;
region_code: string;
region_name: string;
city: string;
zip: string;
latitude: number;
longitude: number;
location: Location;
time_zone: TimeZone;
currency: Currency;
connection: Connection;
}
export async function lookupIp(ip?: string | null) {
const access_key = '';
return ip
? await (
await fetch(`https://api.ipapi.com/api/${ip}?access_key=${access_key}`)
).json()
: undefined;
}
export type WithChildren<T extends { id: string }> = T & {
children: WithChildren<T>[];
data: Record<string, any>;
};
export interface WithSequence {
sequence: string;
parentSequence?: string;
siblingSequence?: string;
}
export interface SourceTarget {
source: string;
target: string;
data: Record<string, any>;
}
export interface ITree<T extends WithSequence = WithSequence> {
source?: T;
target?: T;
paths: ITree<T>[];
type: 'root' | 'leaf' | 'branch';
orphan?: boolean;
self: T;
}
export interface IBetterTree<T> {
id: string;
children: IBetterTree<T>[];
data: Record<string, any>;
}
export interface Edge<T extends WithSequence> {
source: T;
target: T;
path: boolean;
}
export class LinearTree<T extends WithSequence> {
line: ITree<T>[] = [];
orphans: T[] = [];
constructor(root: T) {
this.line.push({
self: root,
source: undefined,
target: undefined,
paths: [],
type: 'root',
});
}
#findParent(node: T) {
if (!node.parentSequence) {
return this.line;
}
const stack = [...this.line];
while (stack.length) {
const subtree = stack.pop()!;
if (node.parentSequence === subtree.self.sequence) {
return subtree.paths;
}
stack.push(...subtree.paths);
}
return null;
}
addNode(node: T) {
const parent = this.#findParent(node);
switch (true) {
case !parent:
this.orphans.push(node);
return;
case parent?.length === 0:
parent.push({
self: node,
source: undefined,
target: undefined,
paths: [],
type: 'leaf',
});
this.#tryAddOrphans();
return;
case !node.siblingSequence:
// at this point we know that the parent is not empty
// and the node has no sibling
parent.push({
self: node,
source: undefined,
target: undefined,
paths: [],
type: 'leaf',
});
this.#tryAddOrphans();
return;
}
for (const branch of parent) {
if (node.siblingSequence === branch.self.sequence) {
// sibiling found, good. now need to add it after the sibiling
const index = parent.findIndex(
(it) => it.source?.sequence === branch.source?.sequence
);
const sourceSibiling = parent[index];
const targetSibiling = parent[index + 1];
sourceSibiling.target = node;
// NOTE: if the node have same sequance sibling, it'll conflict
parent.splice(index + 1, 0, {
self: node,
source: sourceSibiling.self,
target: targetSibiling?.self,
paths: [],
type: 'leaf',
});
this.#tryAddOrphans();
return;
}
if (node.sequence === branch.self.siblingSequence) {
// sibiling found, good. now need to add it before the sibiling
const index = parent.findIndex(
(it) => it.source?.sequence === branch.source?.sequence
);
const sibiling = parent[index];
sibiling.target = node;
parent.splice(index - 1, 0, {
self: node,
source: sibiling.self,
target: undefined,
paths: [],
type: 'leaf',
});
this.#tryAddOrphans();
return;
}
}
this.orphans.push(node);
}
#tryAddOrphans() {
// check if there are orphans that can be added
const clone = [...this.orphans];
this.orphans = [];
clone.forEach((orphan) => this.addNode(orphan));
}
connect(source: WithSequence, target: WithSequence) {
//
}
toJson() {
//
}
toEdges(stack = [...this.line], path = false) {
const edges: Edge<T>[] = [];
while (stack.length) {
const subtreeTarget = stack.pop();
if (!subtreeTarget || !subtreeTarget.source) {
continue;
}
const subtreeSource = stack.at(-1)!;
edges.push({
source: subtreeSource.self,
target: subtreeTarget.self,
path: path,
});
if (subtreeTarget.paths) {
const vnodes = subtreeTarget.paths
.map((it) => [
{
...subtreeTarget,
source: undefined, // remove the source as this acts as root to its paths
},
{ ...it, source: subtreeTarget.self },
])
.flat();
edges.push(...this.toEdges(vnodes, true));
}
}
return edges;
}
pretty() {
for (const orphan of this.orphans) {
const parent = this.#findParent(orphan) ?? [...this.line];
parent.push({
self: orphan,
source: undefined,
target: undefined,
paths: [],
type: 'leaf',
orphan: true,
});
}
return this.line;
}
}
export class BetterTree<T extends SourceTarget> {
line: IBetterTree<T>[] = [];
orphans: T[] = [];
constructor(
private start: {
source: string;
nodes: T[];
}
) {
this.start.nodes.forEach((node) => this.addNode(node));
}
#findParent(node: T) {
const stack = [...this.line];
while (stack.length) {
const subtree = stack.pop()!;
if (node.source === subtree.id) {
return subtree;
}
stack.push(...subtree.children);
}
return null;
}
#tryAddOrphans() {
// check if there are orphans that can be added
const clone = [...this.orphans];
this.orphans = [];
clone.forEach((orphan) => this.addNode(orphan));
}
addNode(node: T) {
const [root] = this.line;
if (!root) {
const maybeRoot = this.start.source === node.source ? node : null;
if (maybeRoot) {
this.line.push({
id: node.source,
children: [],
data: node.data,
});
this.line.push({
id: node.target,
children: [],
data: node.data,
});
this.#tryAddOrphans();
return;
} else {
this.orphans.push(node);
return;
}
}
if (node.data?.['child']) {
const parent = this.#findParent(node);
if (parent) {
parent.children.push({
id: node.target,
children: [],
data: node.data,
});
this.#tryAddOrphans();
} else {
this.orphans.push(node);
}
return;
} else {
const last = this.line.at(-1);
if (!last) {
throw new Error(`No parent found for ${node.source}`);
}
if (last.id === node.source) {
this.line.push({
id: node.target,
children: [],
data: node.data,
});
this.#tryAddOrphans();
} else {
this.orphans.push(node);
}
}
}
map<R extends { id: string }>(
mapFn: (id: string) => R,
array = [...this.line]
) {
const list: WithChildren<R>[] = [];
while (array.length) {
const node = array.shift()!;
list.push({
...node,
...mapFn(node.id),
children: this.map(mapFn, [...node.children]),
data: node.data,
});
}
return list;
}
}
// const tree = new BetterTree<SourceTarget>({
// source: '7986d237-e6e1-4ae4-8b34-fd677e63028a',
// nodes,
// });
// console.dir(tree.line, {
// showHidden: false,
// depth: Infinity,
// maxArrayLength: Infinity,
// colors: true,
// });
import {
GenericQueryCondition,
QueryBuilder,
QueryColumn,
Visitor,
toAst,
} from '@january/compiler/transpilers';
import { camelcase } from 'stringcase';
import { pascalcase } from '@faslh/utils';
export class TypeOrmVisitor extends Visitor<string | null> {
constructor(
private _rootExpr: QueryBuilder.QuerySelectExpr,
private tableName: string,
private qbVar = 'qb'
) {
super();
}
private _wrapInIfStatement(condition: any, statement: string) {
return `if(${condition}) {
${statement}
}`;
}
private _wrapBrackets(statement: string) {
// https://github.com/typeorm/typeorm/issues/6170#issuecomment-832790446
return `new Brackets(qb => {${statement}})`;
// return statement
}
private _addSelect(columns: QueryColumn[]) {
// use it to group by non-aggregated columns
const aggregateFound = columns.some((column) => column.aggregator);
const groupbyCols = columns
.filter((column) => !column.aggregator)
.map((column) => `'${this.tableName}.${column.name}'`);
const selectColumns = columns
.map((column) => {
if (column.aggregator) {
/**
* we are using scape character because of having sql aggregators inside string
* the generated Code will be like this:
* "qb.addSelect('avg(\'Transactions.sales\') as avgSales');"
*/
return `'${column.aggregator}(\"${this.tableName}.${
column.name
}\") as ${column.alias ?? column.name}'`;
} else if (column.name.includes('.')) {
return `'${column.name} as ${
column.alias ?? column.name.split('.')[1]
}'`;
}
return `'${this.tableName}.${column.name}'`;
})
.join(', ');
return `${this.qbVar}.addSelect(${selectColumns});${
aggregateFound && groupbyCols.length
? `${this.qbVar}.addGroupBy(${groupbyCols.join(', ')});`
: ''
}`;
}
private _addJoins(joinsFields: string[]) {
return joinsFields
.map((join) => {
return `${this.qbVar}.innerJoin('${
this.tableName
}.${join.toLowerCase()}', '${join}');`;
})
.join(';');
}
public _visitBetween(
identifier: QueryBuilder.Identifier,
expr: QueryBuilder.BinaryExpression<
QueryBuilder.Identifier,
QueryBuilder.Identifier
>,
context: any
) {
const left = expr.left.accept(this, {
format: (value: string) => {
return `:${value}`;
},
});
const right = expr.right.accept(this, {
format: (value: string) => {
return `:${value}`;
},
});
const statement = `${identifier.accept(this, {
format: (value: string) => {
const [tableNameOrFieldName, maybeFieldName] = value.split('.');
if (!maybeFieldName) {
return `${this.tableName}.${value}`;
}
return `${tableNameOrFieldName}.${maybeFieldName}`;
},
})} BETWEEN :min AND :max`;
const parameters = `{ min: ${left}, max: ${right} }`;
const query = `${this.qbVar}.${context.combinator}Where('${statement}', ${parameters})`;
return query;
}
public visitDateLiteralExpr(
expr: QueryBuilder.Literal<'date'>,
context: any
): any {
// Why not use StringLiteral instead?
return `'${expr.value}'`;
}
public visitBinaryExpr(
expr: QueryBuilder.BinaryExpression<any, any>,
context: any
): any {
switch (expr.operator) {
case 'between':
return this._visitBetween(expr.left, expr.right, context);
case 'like':
return this._visitLike(expr, {
...context,
required: context.required ?? false,
operator: context.inverse ? 'NOT LIKE' : 'LIKE',
prefix: context.prefix ?? '',
postfix: context.postfix ?? '',
});
case 'is':
return this._visitIs(expr, {
...context,
operator: context.inverse ? 'IS NOT' : 'IS',
});
case '===':
return this._equal(expr, {
...context,
operator: context.inverse ? '!=' : '=',
});
case '<':
return this._equal(expr, {
...context,
operator: context.inverse ? '>' : '<',
});
case '>':
return this._equal(expr, {
...context,
operator: context.inverse ? '<' : '>',
});
case '<=':
return this._equal(expr, {
...context,
operator: context.inverse ? '>=' : '<=',
});
case '>=':
return this._equal(expr, {
...context,
operator: context.inverse ? '<=' : '>=',
});
case 'in':
return this._visitIn(expr, {
...context,
operator: context.inverse ? 'NOT IN' : 'IN',
});
default:
throw new Error(
`Expression is not supported. operator: ${expr.operator}`
);
}
}
private _visitIs(expr: QueryBuilder.BinaryExpression, context: any) {
const left = this._acceptLeftExpression(expr, context);
const binding = this._acceptBindings(expr, context);
const statement = `${left} ${context.operator} ${binding}`;
const parameters = this._acceptParameters(expr, context);
const isRequired = context.required === true;
const query = `${this.qbVar}.${context.combinator}Where('${statement}', ${parameters})`;
const finalQuery = !isRequired
? this._wrapInIfStatement(expr.right.accept(this, {}), query)
: query;
return finalQuery;
}
private _visitLike(expr: QueryBuilder.BinaryExpression, context: any) {
const left = this._acceptLeftExpression(expr, context);
const binding = this._acceptBindings(expr, context);
const statement = `${left} ${context.operator} ${binding}`;
const right = expr.right.accept(this, {
...context,
});
const keyName = expr.left.accept(this, {
...context,
format: (value: string) => {
const [tableNameOrFieldName, fieldName] = value.split('.');
if (!fieldName) {
return `${tableNameOrFieldName}`;
}
return `${camelcase(value)}`;
},
});
const rightWithBinding = `${
context.prefix ? `'${context.prefix}' + ` : ''
}${right}${context.postfix ? ` + '${context.postfix}'` : ''}`;
const valueName = expr.right.accept(this, {
...context,
});
const parameters = `{${keyName}: ${rightWithBinding}}`;
const isRequired = context.required === true;
const query = `${this.qbVar}.${context.combinator}Where('${statement}', ${parameters})`;
const finalQuery = !isRequired
? this._wrapInIfStatement(valueName, query)
: query;
return finalQuery;
}
private _visitIn(expr: QueryBuilder.BinaryExpression, context: any) {
const left = this._acceptLeftExpression(expr, context);
const right = expr.right.accept(this, {
...context,
});
const binding = this._acceptBindings(expr, context);
const statement = `${left} ${context.operator} ${binding}`;
const keyName = expr.left.accept(this, {
...context,
format: (value: string) => {
const [tableName, prop] = value.split('.');
if (!prop) {
return `${value}`;
}
return `${camelcase(value)}`;
},
});
const parameters = `{ ${keyName}: ${right} }`;
const valueName = expr.right.accept(this, {
...context,
});
const isRequired = context.required === true;
const query = `${this.qbVar}.${context.combinator}Where('${statement}', ${parameters})`;
const finalQuery = !isRequired
? this._wrapInIfStatement(valueName, query)
: query;
return finalQuery;
}
_acceptLeftExpression(
expr: QueryBuilder.BinaryExpression<any, any>,
context: any
) {
return expr.left.accept(this, {
...context,
format: (value: string) => {
const [tableNameOrFieldName, maybeFieldName] = value.split('.');
if (!maybeFieldName) {
return `${this.tableName}.${value}`;
}
return `${tableNameOrFieldName}.${maybeFieldName}`;
},
});
}
_acceptBindings(
expr: QueryBuilder.BinaryExpression<any, any>,
context: {
operator: string;
} & any
) {
return expr.left.accept(this, {
...context,
format: (value: string) => {
const [tableName, prop] = value.split('.');
if (!prop) {
return `:${value}`;
}
return `:${camelcase(value)}`;
},
});
}
_acceptParameters(
expr: QueryBuilder.BinaryExpression<any, any>,
context: {
operator: string;
} & any
) {
const valueName = expr.right.accept(this, {
...context,
});
const keyName = expr.left.accept(this, {
...context,
format: (value: string) => {
const [tableName, prop] = value.split('.');
if (!prop) {
return `${value}`;
}
return `${camelcase(value)}`;
},
});
if (valueName === 'WILL_BE_USED_FROM_DESTRUCTURED_INPUT') {
return `{${keyName}}`;
}
return `{ ${keyName}: ${valueName} }`;
}
private _equal(
expr: QueryBuilder.BinaryExpression<any, any>,
context: {
operator: string;
} & any
) {
// TODO: use template design pattern to override specific steps
// for instance, _visitLike method uses same logic here with only
// difference being that right expressions is formatted differently.
const left = this._acceptLeftExpression(expr, context);
const binding = this._acceptBindings(expr, context);
const statement = `${left} ${context.operator} ${binding}`;
const valueName = expr.right.accept(this, {
...context,
});
const parameters = this._acceptParameters(expr, context);
const isRequired = context.required === true;
const query = `${this.qbVar}.${context.combinator}Where('${statement}', ${parameters})`;
const finalQuery = !isRequired
? this._wrapInIfStatement(
valueName === 'WILL_BE_USED_FROM_DESTRUCTURED_INPUT'
? left
: valueName,
query
)
: query;
return finalQuery;
}
public visitNumericLiteralExpr(
expr: QueryBuilder.Literal<'numeric'>
): string {
return `${expr.value}`;
}
public visitNullLiteralExpr(expr: QueryBuilder.Literal<'null'>): null {
return null;
}
public visitBooleanLiteralExpr(
expr: QueryBuilder.Literal<'boolean'>
): string {
return `${expr.value}`;
}
public visitStringLiteralExpr(expr: QueryBuilder.Literal<'string'>): string {
return `'${expr.value}'`;
}
public visitIdentifier(
expr: QueryBuilder.Identifier,
context: {
format?: (value: string) => string;
}
): string {
if (context.format) {
return context.format(expr.value);
}
return expr.value;
}
public visitCombinator(expr: QueryBuilder.Combinator, context: any): string {
return '';
}
visitListExpr(expr: QueryBuilder.ListExpr, context: any) {
const children: string[] = expr.value.map((childExpr) =>
childExpr.accept(this, context)
);
return `[${children.join(',')}]`;
}
public visitQuerySelectExpr(
expr: QueryBuilder.QuerySelectExpr,
context: any
): any {
const { columns, joinsFields } = context;
const addSelectAndJoins =
columns && columns.length
? `${this._addSelect(columns)}${this._addJoins(joinsFields)}`
: '';
const query = expr.value.map((group) => group.accept(this)).join(';');
return `${addSelectAndJoins}${query}`;
}
public visitGroupExpr(expr: QueryBuilder.GroupExpr, context: any): any {
const qb = expr.value.map((childExpr) => {
return childExpr.accept(this, {
...context,
combinator: expr.combinator.operator,
});
});
if (qb.length > 1) {
return `${this.qbVar}.${
expr.combinator.operator
}Where(${this._wrapBrackets(qb.join(';'))})`;
}
return qb.join(';');
}
public execute(): string {
const result = this._rootExpr.accept(this);
return result;
}
public executeWithQueryBuiler() {
const queryBuilder = `const ${this.qbVar} = createQueryBuilder(${pascalcase(
this.tableName
)},'${this.tableName}')`;
const result = this._rootExpr.accept(this);
return `${queryBuilder};${result}`;
}
}
export function runTypeormVisitor(props: {
query: GenericQueryCondition<unknown>;
tableName: string;
qbVarName?: string;
}) {
return new TypeOrmVisitor(
toAst(props.query) as QueryBuilder.QuerySelectExpr,
pascalcase(props.tableName),
props.qbVarName
).execute();
}
import { Contracts, IncomingActionProperty } from '@faslh/compiler/contracts';
import { camelcase } from '@faslh/utils';
const naminize = (input: string[]) => camelcase(input.join(' '));
function format(
it: Omit<Contracts.DefaultQueryConditionContract, 'operator'>
): Record<string, IncomingActionProperty> {
return {
[naminize(it.input)]: {
input: it.data.input as string,
defaultValue: it.data.defaultValue,
validations: it.data.validation,
},
};
}
function processSelect(
select: Contracts.QuerySelectConditionContract
): Record<string, IncomingActionProperty> {
return select.data
.map(processGroup)
.flat()
.reduce<Record<string, IncomingActionProperty>>((acc, item) => {
return {
...acc,
...item,
};
}, {});
}
function processGroup(
group: Contracts.GroupQueryConditionContract
): Record<string, IncomingActionProperty> {
return group.data
.map((item) => flatQueryConditions(item))
.flat()
.reduce<Record<string, IncomingActionProperty>>((acc, item) => {
return {
...acc,
...item,
};
}, {});
}
function processBetween(
group: Contracts.BetweenQueryConditionContract
): Record<string, IncomingActionProperty> {
return {
...format({
input: group.input,
data: group.data.min,
}),
...format({
input: group.input,
data: group.data.max,
}),
};
}
function processDefault(
group: Contracts.DefaultQueryConditionContract
): Record<string, IncomingActionProperty> {
return format({
input: group.input,
data: group.data,
});
}
export function flatQueryConditions(
condition: Contracts.QueryConditionContract
): Record<string, IncomingActionProperty> {
switch (condition.operator) {
case 'group':
return processGroup(condition);
case 'between':
return processBetween(condition);
case 'querySelect':
return processSelect(condition);
default:
return processDefault(condition);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment