UNPKG

rajt

Version:

A serverless bundler layer, fully typed for AWS Lambda (Node.js and LLRT) and Cloudflare Workers.

175 lines (145 loc) 4.75 kB
import type { Condition, Operator } from './types' export default class QueryBuilder { #filters: Condition[] = [] #keyConditions: Condition[] = [] #limit?: number #startKey?: Record<string, any> #index?: string #attrCounter = 1 #valCounter = 1 #fieldAttrMap: Record<string, string> = {} #fieldValMap: Record<string, string> = {} filter(field: string, operator: Operator, value: any = null) { this.#filters.push({ type: 'filter', field, operator, value }) return this } keyCondition(field: string, operator: Operator | any, value?: any) { const noVal = value === undefined this.#keyConditions.push({ type: 'keyCondition', field, operator: noVal ? '=' : operator, value: noVal ? operator : value }) return this } limit(n: number) { this.#limit = n return this } exclusiveStartKey(key: Record<string, any>) { this.#startKey = key return this } index(name: string) { this.#index = name return this } private attrName(field: string) { if (!this.#fieldAttrMap[field]) this.#fieldAttrMap[field] = '#a'+ this.#attrCounter++ return this.#fieldAttrMap[field] } private valName(val: any) { val = String(val) if (!this.#fieldValMap[val]) this.#fieldValMap[val] = ':v'+ this.#valCounter++ return this.#fieldValMap[val] } #resetCounters() { this.#attrCounter = 0 this.#valCounter = 0 this.#fieldAttrMap = {} this.#fieldValMap = {} } buildExpression(conditions: Condition[]) { const exprParts: string[] = [] const values: Record<string, any> = {} const names: Record<string, string> = {} for (const cond of conditions) { const attr = this.attrName(cond.field) const val = Array.isArray(cond.value) ? '' : this.valName(cond.value) names[attr] = cond.field switch (cond.operator) { case 'between': { const val0 = this.valName(cond.value[0]) const val1 = this.valName(cond.value[1]) exprParts.push(`${attr} BETWEEN ${val0} AND ${val1}`) values[val0] = cond.value[0] values[val1] = cond.value[1] break } case 'begins_with': { exprParts.push(`begins_with(${attr}, ${val})`) values[val] = cond.value break } case 'in': { const inVals = cond.value.map((v: any) => { const key = this.valName(v) values[key] = v return key }) exprParts.push(`${attr} IN (${inVals.join(', ')})`) break } case 'attribute_exists': { exprParts.push(`attribute_exists(${attr})`) break } case 'attribute_not_exists': { exprParts.push(`attribute_not_exists(${attr})`) break } case 'attribute_type': { exprParts.push(`attribute_type(${attr}, ${val})`) values[val] = cond.value break } case 'contains': { exprParts.push(`contains(${attr}, ${val})`) values[val] = cond.value break } case 'size': { exprParts.push(`size(${attr}) = ${val}`) values[val] = cond.value break } default: { exprParts.push(`${attr} ${cond.operator} ${val}`) values[val] = cond.value } } } return { expression: exprParts.length ? exprParts.join(' AND ') : undefined, names: Object.keys(names).length ? names : undefined, values: Object.keys(values).length ? values : undefined, } } get filters() { const filter = this.buildExpression(this.#filters) const params: any = {} if (this.#limit) params.Limit = this.#limit if (this.#startKey) params.ExclusiveStartKey = this.#startKey if (filter.expression) params.FilterExpression = filter.expression if (filter.names) params.ExpressionAttributeNames = filter.names if (filter.values) params.ExpressionAttributeValues = filter.values return params } get conditions() { const keys = this.buildExpression(this.#keyConditions) const filters = this.filters const params: any = { ...filters } if (this.#index) params.IndexName = this.#index if (keys.expression) params.KeyConditionExpression = keys.expression if (keys.names || filters?.ExpressionAttributeNames) params.ExpressionAttributeNames = { ...(keys?.names || {}), ...(filters?.ExpressionAttributeNames || {}) } if (keys.values || filters?.ExpressionAttributeValues) params.ExpressionAttributeValues = { ...(keys?.values || {}), ...(filters?.ExpressionAttributeValues || {}) } return params } }