UNPKG

@tanstack/optimistic

Version:

Core optimistic updates library

308 lines (307 loc) 9.53 kB
class BaseQueryBuilder { /** * Create a new QueryBuilder instance. */ constructor(query = {}) { this.query = {}; this.query = query; } /** * Specify the collection to query from. * This is the first method that must be called in the chain. * * @param collection The collection name to query from * @param as Optional alias for the collection * @returns A new QueryBuilder with the from clause set */ from(collection, as) { if (typeof collection === `object` && collection !== null) { return this.fromCollectionRef(collection); } else if (typeof collection === `string`) { return this.fromInputReference( collection, as ); } else { throw new Error(`Invalid collection type`); } } fromCollectionRef(collectionRef) { var _a; const keys = Object.keys(collectionRef); if (keys.length !== 1) { throw new Error(`Expected exactly one key`); } const key = keys[0]; const collection = collectionRef[key]; const newBuilder = new BaseQueryBuilder(); Object.assign(newBuilder.query, this.query); newBuilder.query.from = key; (_a = newBuilder.query).collections ?? (_a.collections = {}); newBuilder.query.collections[key] = collection; return newBuilder; } fromInputReference(collection, as) { const newBuilder = new BaseQueryBuilder(); Object.assign(newBuilder.query, this.query); newBuilder.query.from = collection; if (as) { newBuilder.query.as = as; } return newBuilder; } /** * Specify what columns to select. * Overwrites any previous select clause. * * @param selects The columns to select * @returns A new QueryBuilder with the select clause set */ select(...selects) { const validatedSelects = selects.map((select) => { if (typeof select === `object` && // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition select !== null && !Array.isArray(select)) { const result = {}; for (const [key, value] of Object.entries(select)) { if (typeof value === `object` && value !== null && !Array.isArray(value)) { const keys = Object.keys(value); if (keys.length === 1) { const funcName = keys[0]; const allowedFunctions = [ `SUM`, `COUNT`, `AVG`, `MIN`, `MAX`, `DATE`, `JSON_EXTRACT`, `JSON_EXTRACT_PATH`, `UPPER`, `LOWER`, `COALESCE`, `CONCAT`, `LENGTH`, `ORDER_INDEX` ]; if (!allowedFunctions.includes(funcName)) { console.warn( `Unsupported function: ${funcName}. Expected one of: ${allowedFunctions.join(`, `)}` ); } } } result[key] = value; } return result; } return select; }); const newBuilder = new BaseQueryBuilder( this.query ); newBuilder.query.select = validatedSelects; return newBuilder; } /** * Add a where clause to filter the results. * Can be called multiple times to add AND conditions. * * @param leftOrCondition The left operand or complete condition * @param operator Optional comparison operator * @param right Optional right operand * @returns A new QueryBuilder with the where clause added */ where(leftOrCondition, operator, right) { const newBuilder = new BaseQueryBuilder(); Object.assign(newBuilder.query, this.query); let condition; if (operator !== void 0 && right !== void 0) { condition = [leftOrCondition, operator, right]; } else { condition = leftOrCondition; } if (!newBuilder.query.where) { newBuilder.query.where = condition; } else { const andArray = [newBuilder.query.where, `and`, condition]; newBuilder.query.where = andArray; } return newBuilder; } /** * Add a having clause to filter the grouped results. * Can be called multiple times to add AND conditions. * * @param leftOrCondition The left operand or complete condition * @param operator Optional comparison operator * @param right Optional right operand * @returns A new QueryBuilder with the having clause added */ having(leftOrCondition, operator, right) { const newBuilder = new BaseQueryBuilder(); Object.assign(newBuilder.query, this.query); let condition; if (operator !== void 0 && right !== void 0) { condition = [leftOrCondition, operator, right]; } else { condition = leftOrCondition; } if (!newBuilder.query.having) { newBuilder.query.having = condition; } else { const andArray = [newBuilder.query.having, `and`, condition]; newBuilder.query.having = andArray; } return newBuilder; } join(joinClause) { if (typeof joinClause.from === `object` && joinClause.from !== null) { return this.joinCollectionRef( joinClause ); } else { return this.joinInputReference( joinClause ); } } joinCollectionRef(joinClause) { var _a; const newBuilder = new BaseQueryBuilder(); Object.assign(newBuilder.query, this.query); const keys = Object.keys(joinClause.from); if (keys.length !== 1) { throw new Error(`Expected exactly one key in CollectionRef`); } const key = keys[0]; const collection = joinClause.from[key]; if (!collection) { throw new Error(`Collection not found for key: ${key}`); } const joinClauseCopy = { type: joinClause.type, from: key, on: joinClause.on, where: joinClause.where }; if (!newBuilder.query.join) { newBuilder.query.join = [joinClauseCopy]; } else { newBuilder.query.join = [...newBuilder.query.join, joinClauseCopy]; } (_a = newBuilder.query).collections ?? (_a.collections = {}); newBuilder.query.collections[key] = collection; return newBuilder; } joinInputReference(joinClause) { const newBuilder = new BaseQueryBuilder(); Object.assign(newBuilder.query, this.query); const joinClauseCopy = { ...joinClause }; if (!newBuilder.query.join) { newBuilder.query.join = [joinClauseCopy]; } else { newBuilder.query.join = [...newBuilder.query.join, joinClauseCopy]; } joinClause.as ?? joinClause.from; return newBuilder; } /** * Add an orderBy clause to sort the results. * Overwrites any previous orderBy clause. * * @param orderBy The order specification * @returns A new QueryBuilder with the orderBy clause set */ orderBy(orderBy) { const newBuilder = new BaseQueryBuilder(); Object.assign(newBuilder.query, this.query); newBuilder.query.orderBy = orderBy; return newBuilder; } /** * Set a limit on the number of results returned. * * @param limit Maximum number of results to return * @returns A new QueryBuilder with the limit set */ limit(limit) { const newBuilder = new BaseQueryBuilder(); Object.assign(newBuilder.query, this.query); newBuilder.query.limit = limit; return newBuilder; } /** * Set an offset to skip a number of results. * * @param offset Number of results to skip * @returns A new QueryBuilder with the offset set */ offset(offset) { const newBuilder = new BaseQueryBuilder(); Object.assign(newBuilder.query, this.query); newBuilder.query.offset = offset; return newBuilder; } /** * Specify which column(s) to use as keys in the output keyed stream. * * @param keyBy The column(s) to use as keys * @returns A new QueryBuilder with the keyBy clause set */ keyBy(keyBy) { const newBuilder = new BaseQueryBuilder(); Object.assign(newBuilder.query, this.query); newBuilder.query.keyBy = keyBy; return newBuilder; } /** * Add a groupBy clause to group the results by one or more columns. * * @param groupBy The column(s) to group by * @returns A new QueryBuilder with the groupBy clause set */ groupBy(groupBy) { const newBuilder = new BaseQueryBuilder(); Object.assign(newBuilder.query, this.query); newBuilder.query.groupBy = groupBy; return newBuilder; } /** * Define a Common Table Expression (CTE) that can be referenced in the main query. * This allows referencing the CTE by name in subsequent from/join clauses. * * @param name The name of the CTE * @param queryBuilderCallback A function that builds the CTE query * @returns A new QueryBuilder with the CTE added */ with(name, queryBuilderCallback) { const newBuilder = new BaseQueryBuilder(); Object.assign(newBuilder.query, this.query); const cteBuilder = new BaseQueryBuilder(); const cteQueryBuilder = queryBuilderCallback( cteBuilder ); const cteQuery = cteQueryBuilder._query; const withQuery = { ...cteQuery, as: name }; if (!newBuilder.query.with) { newBuilder.query.with = [withQuery]; } else { newBuilder.query.with = [...newBuilder.query.with, withQuery]; } return newBuilder; } get _query() { return this.query; } } function queryBuilder() { return new BaseQueryBuilder(); } export { BaseQueryBuilder, queryBuilder }; //# sourceMappingURL=query-builder.js.map