UNPKG

@lucidcms/core

Version:

The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.

1 lines 9.4 kB
{"version":3,"file":"query-builder.mjs","names":[],"sources":["../../../../src/libs/db/query-builder/query-builder.ts"],"sourcesContent":["import type {\n\tExpressionBuilder,\n\tOperandExpression,\n\tReferenceExpression,\n\tSelectQueryBuilder,\n\tSqlBool,\n} from \"kysely\";\nimport type {\n\tFilterObject,\n\tFilterOperator,\n\tQueryParamFilterCondition,\n\tQueryParams,\n} from \"../../../types/query-params.js\";\nimport type { DatabaseConfig } from \"../types.js\";\nimport compileFilterExpression from \"./utils/compile-filter-expression.js\";\nimport getFilterOperator from \"./utils/get-filter-operator.js\";\nimport getTableKeyValue from \"./utils/get-table-key-value.js\";\n\ntype CustomFilterHandler<DB, Table extends keyof DB> = (params: {\n\teb: ExpressionBuilder<DB, Table>;\n\tfilter: QueryParamFilterCondition;\n}) => OperandExpression<SqlBool> | undefined;\n\ntype QueryBuilderMeta<DB, Table extends keyof DB> = {\n\ttableKeys?: {\n\t\tfilters?: Record<string, ReferenceExpression<DB, Table>>;\n\t\tsorts?: Record<string, ReferenceExpression<DB, Table>>;\n\t};\n\toperators?: Record<string, FilterOperator>;\n\tcustomFilters?: Record<string, CustomFilterHandler<DB, Table>>;\n};\n\ntype MappedFilterOrCondition<DB, Table extends keyof DB> =\n\t| {\n\t\t\ttype: \"custom\";\n\t\t\tcustomFilter: CustomFilterHandler<DB, Table>;\n\t\t\tfilter: QueryParamFilterCondition;\n\t }\n\t| {\n\t\t\ttype: \"mapped\";\n\t\t\tcondition: NonNullable<ReturnType<typeof conditionFromFilter<DB, Table>>>;\n\t };\n\n/** Maps public filter keys to table expressions, ignoring unknown keys. */\nconst conditionFromFilter = <DB, Table extends keyof DB>(\n\tkey: string,\n\tfilter: FilterObject,\n\tmeta?: QueryBuilderMeta<DB, Table>,\n):\n\t| {\n\t\t\tkey: ReferenceExpression<DB, Table>;\n\t\t\tfilter: FilterObject & { operator: FilterOperator };\n\t }\n\t| undefined => {\n\tconst tableKey = getTableKeyValue<DB, Table>(key, meta?.tableKeys?.filters);\n\tif (!tableKey) return undefined;\n\n\treturn {\n\t\tkey: tableKey,\n\t\tfilter: {\n\t\t\t...filter,\n\t\t\toperator: getFilterOperator(key, filter, meta?.operators),\n\t\t},\n\t};\n};\n\nconst queryBuilder = <DB, Table extends keyof DB, O>(\n\tquery: {\n\t\tmain: SelectQueryBuilder<DB, Table, O>;\n\t\tcount?: SelectQueryBuilder<\n\t\t\tDB,\n\t\t\tTable,\n\t\t\t{\n\t\t\t\tcount: unknown;\n\t\t\t}\n\t\t>;\n\t},\n\tconfig: {\n\t\tqueryParams: Partial<QueryParams>;\n\t\tdatabase: Pick<DatabaseConfig, \"caseInsensitiveLikeOperator\">;\n\t\tmeta?: QueryBuilderMeta<DB, Table>;\n\t},\n) => {\n\tlet mainQuery = query.main;\n\tlet countQuery = query.count;\n\n\t// -----------------------------------------\n\t// Filters\n\tconst filters = Object.entries(config.queryParams.filter || {});\n\n\tfor (const [key, f] of filters) {\n\t\tconst customFilter = config.meta?.customFilters?.[key];\n\t\tif (customFilter) {\n\t\t\tmainQuery = mainQuery.where(\n\t\t\t\t(eb) =>\n\t\t\t\t\tcustomFilter({\n\t\t\t\t\t\teb,\n\t\t\t\t\t\tfilter: { key, ...f },\n\t\t\t\t\t}) ?? eb.val(true),\n\t\t\t);\n\t\t\tif (countQuery) {\n\t\t\t\tcountQuery = countQuery.where(\n\t\t\t\t\t(eb) =>\n\t\t\t\t\t\tcustomFilter({\n\t\t\t\t\t\t\teb,\n\t\t\t\t\t\t\tfilter: { key, ...f },\n\t\t\t\t\t\t}) ?? eb.val(true),\n\t\t\t\t);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst condition = conditionFromFilter<DB, Table>(key, f, config.meta);\n\t\tif (!condition) continue;\n\n\t\tmainQuery = mainQuery.where((eb) =>\n\t\t\tcompileFilterExpression({\n\t\t\t\teb,\n\t\t\t\treference: condition.key,\n\t\t\t\tfilter: condition.filter,\n\t\t\t\tcaseInsensitiveLikeOperator:\n\t\t\t\t\tconfig.database.caseInsensitiveLikeOperator,\n\t\t\t}),\n\t\t);\n\t\tif (countQuery) {\n\t\t\tcountQuery = countQuery.where((eb) =>\n\t\t\t\tcompileFilterExpression({\n\t\t\t\t\teb,\n\t\t\t\t\treference: condition.key,\n\t\t\t\t\tfilter: condition.filter,\n\t\t\t\t\tcaseInsensitiveLikeOperator:\n\t\t\t\t\t\tconfig.database.caseInsensitiveLikeOperator,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t}\n\n\tconst filterOrGroups = config.queryParams.filterOr || [];\n\tconst mappedFilterOrGroups = filterOrGroups\n\t\t.map((group) =>\n\t\t\tgroup\n\t\t\t\t.map((filter) => {\n\t\t\t\t\tconst customFilter = config.meta?.customFilters?.[filter.key];\n\t\t\t\t\tif (customFilter) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\ttype: \"custom\",\n\t\t\t\t\t\t\tcustomFilter,\n\t\t\t\t\t\t\tfilter,\n\t\t\t\t\t\t} satisfies MappedFilterOrCondition<DB, Table>;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst condition = conditionFromFilter<DB, Table>(\n\t\t\t\t\t\tfilter.key,\n\t\t\t\t\t\tfilter,\n\t\t\t\t\t\tconfig.meta,\n\t\t\t\t\t);\n\t\t\t\t\tif (!condition) return undefined;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"mapped\",\n\t\t\t\t\t\tcondition,\n\t\t\t\t\t} satisfies MappedFilterOrCondition<DB, Table>;\n\t\t\t\t})\n\t\t\t\t.filter((filter): filter is NonNullable<typeof filter> =>\n\t\t\t\t\tBoolean(filter),\n\t\t\t\t),\n\t\t)\n\t\t.filter((group) => group.length > 0);\n\n\tif (mappedFilterOrGroups.length > 0) {\n\t\t/** Applies OR groups as a single ANDed predicate on the current query. */\n\t\tconst applyFilterOr = <Output>(qb: SelectQueryBuilder<DB, Table, Output>) =>\n\t\t\tqb.where((eb) => {\n\t\t\t\tconst groupExpressions: OperandExpression<SqlBool>[] = [];\n\n\t\t\t\tfor (const group of mappedFilterOrGroups) {\n\t\t\t\t\tconst expressions: OperandExpression<SqlBool>[] = [];\n\n\t\t\t\t\tfor (const filter of group) {\n\t\t\t\t\t\tif (filter.type === \"custom\") {\n\t\t\t\t\t\t\tconst expression = filter.customFilter({\n\t\t\t\t\t\t\t\teb,\n\t\t\t\t\t\t\t\tfilter: filter.filter,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tif (expression !== undefined) expressions.push(expression);\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\texpressions.push(\n\t\t\t\t\t\t\tcompileFilterExpression({\n\t\t\t\t\t\t\t\teb,\n\t\t\t\t\t\t\t\treference: filter.condition.key,\n\t\t\t\t\t\t\t\tfilter: filter.condition.filter,\n\t\t\t\t\t\t\t\tcaseInsensitiveLikeOperator:\n\t\t\t\t\t\t\t\t\tconfig.database.caseInsensitiveLikeOperator,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (expressions.length > 0) {\n\t\t\t\t\t\tgroupExpressions.push(eb.and(expressions));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn groupExpressions.length > 0\n\t\t\t\t\t? eb.or(groupExpressions)\n\t\t\t\t\t: eb.val(true);\n\t\t\t});\n\n\t\tmainQuery = applyFilterOr(mainQuery);\n\t\tif (countQuery) countQuery = applyFilterOr(countQuery);\n\t}\n\n\t// -----------------------------------------\n\t// Sort\n\tif (config.queryParams.sort) {\n\t\tfor (const sort of config.queryParams.sort) {\n\t\t\tconst tableKey = getTableKeyValue<DB, Table>(\n\t\t\t\tsort.key,\n\t\t\t\tconfig.meta?.tableKeys?.sorts,\n\t\t\t);\n\t\t\tif (!tableKey) continue;\n\t\t\tmainQuery = mainQuery.orderBy(tableKey, sort.direction);\n\t\t}\n\t}\n\n\t// -----------------------------------------\n\t// Pagination\n\tif (\n\t\tconfig.queryParams.perPage !== undefined &&\n\t\tconfig.queryParams.page !== undefined &&\n\t\tconfig.queryParams.perPage !== -1\n\t) {\n\t\tmainQuery = mainQuery\n\t\t\t.limit(config.queryParams.perPage)\n\t\t\t.offset((config.queryParams.page - 1) * config.queryParams.perPage);\n\t}\n\n\treturn {\n\t\tmain: mainQuery,\n\t\tcount: countQuery,\n\t};\n};\n\nexport default queryBuilder;\n"],"mappings":"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"}