From d8bb577c53510dd38e7067006a954c590e8c0e5a Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 29 Sep 2025 01:40:31 -0300 Subject: [PATCH 01/74] fix/added retries directly to vitest config file --- vitest.config.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vitest.config.ts b/vitest.config.ts index 471e615..1fd82d2 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -15,6 +15,7 @@ export default defineConfig({ 'src/index.ts', ], include: ['src/**/*.ts'] - } + }, + retry: 3 } }) From ea2e6cd90569d32d482dd7bfe1b12ef9950d157b Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 29 Sep 2025 01:41:07 -0300 Subject: [PATCH 02/74] fix/configured better the typing on orderby --- src/types/OrderBy.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types/OrderBy.ts b/src/types/OrderBy.ts index 38c3cec..ab7e117 100644 --- a/src/types/OrderBy.ts +++ b/src/types/OrderBy.ts @@ -11,6 +11,7 @@ export interface OrderByField { field: string; /** The direction of sorting: 'ASC' for ascending or 'DESC' for descending. */ direction: Direction; + column?: never; } /** @@ -22,6 +23,7 @@ export interface OrderByColumn { column: string; /** The direction of sorting: 'ASC' for ascending or 'DESC' for descending. */ direction: Direction; + field?: never; } type OrderBy = OrderByField | OrderByColumn From 52d7c57ca23ed57e10675d10a54fcb3f54f388c3 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 29 Sep 2025 01:41:45 -0300 Subject: [PATCH 03/74] feat/added subqueries and a function to detect if it's a subquery or a table join --- src/types/Join.ts | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/types/Join.ts b/src/types/Join.ts index dd8c666..d9f4f4e 100644 --- a/src/types/Join.ts +++ b/src/types/Join.ts @@ -1,3 +1,4 @@ +import SelectQuery from "../queryKinds/select.js"; import Statement from "../statementMaker.js"; type joinTypeBase = 'INNER' | 'LEFT' | 'RIGHT' | 'FULL'; @@ -5,16 +6,39 @@ type joinTypeBase = 'INNER' | 'LEFT' | 'RIGHT' | 'FULL'; export type joinType = Lowercase | joinTypeBase; /** - * Join interface represents a SQL JOIN clause. - * It includes the type of join, the table to join, an optional alias, and the condition for the join. + * Join interface represents the base to a SQL JOIN clause. + * It includes the type of join, an optional alias, and the condition for the join. */ -export default interface Join { +interface JoinBase { /** The type of join: INNER, LEFT, RIGHT, or FULL. */ type: joinType; - /** The name of the table to join. */ - table: string; /** An optional alias for the joined table. */ alias?: string; /** The condition for the join, which can be a Statement or a raw SQL string. */ on: Statement | string; } + +/** + * Join interface represents a SQL JOIN clause. + * It includes the type of join, the table to join, an optional alias, and the condition for the join. + */ +type JoinTable = JoinBase & { + /** The name of the table to join. */ + table: string; + subQuery?: never; +}; + +type JoinSubQuery = JoinBase & { + /** A subquery to join. */ + subQuery: SelectQuery; + table?: never; +}; + +type Join = JoinTable | JoinSubQuery; + +export function isJoinTable(join: Join): join is JoinTable { + return (join as JoinTable).table !== undefined; +} + +export default Join; + From e9284a6d680098cf88a9901fcdc298ffd4b33167 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 29 Sep 2025 01:42:24 -0300 Subject: [PATCH 04/74] fix/removed deprecated retries from here --- src/statementMaker.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/statementMaker.test.ts b/src/statementMaker.test.ts index 8505a4b..daac261 100644 --- a/src/statementMaker.test.ts +++ b/src/statementMaker.test.ts @@ -244,7 +244,7 @@ describe('StatementMaker Basics', () => { expect(secondEnd - secondStart).toBeLessThan(firstEnd - firstStart); expect(result1).toStrictEqual(result2); - }, { retry: 3 }); + }); it('should return faster compared reparseOnChange = false', () => { const maker = new StatementMaker() @@ -314,7 +314,7 @@ describe('StatementMaker Basics', () => { expect(secondEnd - secondStart).toBeLessThan(firstEnd - firstStart); expect(result1).toStrictEqual(result2); - }, { retry: 3 }); + }); it('should clone itself correctly', () => { const maker = new StatementMaker() From 591704e7574bc332c4995814dd767df0e962e726 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 29 Sep 2025 01:43:09 -0300 Subject: [PATCH 05/74] feat/fix/fixed how ctes managed with joins and also added subquery joins --- src/queryKinds/query.ts | 21 +++++ src/queryKinds/select.test.ts | 168 +++++++++++++++++++++++++++++++++- src/queryKinds/select.ts | 98 +++++++++++--------- 3 files changed, 244 insertions(+), 43 deletions(-) diff --git a/src/queryKinds/query.ts b/src/queryKinds/query.ts index ef2ca82..2f2177f 100644 --- a/src/queryKinds/query.ts +++ b/src/queryKinds/query.ts @@ -9,6 +9,8 @@ import sqlFlavor from "../types/sqlFlavor.js"; import CteMaker from "../cteMaker.js"; import Statement from "../statementMaker.js"; import QueryKind from "../types/QueryKind.js"; +import Join, { isJoinTable } from "../types/Join.js"; +import SqlEscaper from "../sqlEscaper.js"; /** * An array of function names that can be used to execute SQL queries. @@ -100,6 +102,25 @@ export default abstract class QueryDefinition { return this.builtQuery !== null && this.builtParams !== null; } + /** + * Parses a Join object to ensure proper escaping and cloning of subqueries. + * @param join The Join object to parse. + * @returns The parsed Join object. + */ + protected parseJoinObject(join: Join): Join { + if (isJoinTable(join)) { + return { + ...join, + table: SqlEscaper.escapeTableName(join.table, this.flavor), + } + } else { + return { + ...join, + subQuery: join.subQuery.clone() + } + } + } + /** * The kind of SQL operation represented by the query definition. * It can be one of 'INSERT', 'UPDATE', 'DELETE', or 'SELECT'. diff --git a/src/queryKinds/select.test.ts b/src/queryKinds/select.test.ts index 3d04a19..4064a62 100644 --- a/src/queryKinds/select.test.ts +++ b/src/queryKinds/select.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import SelectQuery from "./select.js"; import Statement from "../statementMaker.js"; -import { Cte } from "../cteMaker.js"; +import CteMaker, { Cte } from "../cteMaker.js"; describe('Select Query', () => { it('should generate correct SELECT SQL', () => { @@ -468,4 +468,170 @@ describe('Select Query', () => { expect(query.build().values).toEqual([18]); }); + + it('should support subQuery for joins', () => { + const subQuery = new SelectQuery('orders', 'o') + .select(['o.user_id']) + .addRawSelect('COUNT(o.id) AS order_count') + .where('o.status = ?', 'completed') + .groupBy('o.user_id'); + + const mainQuery = new SelectQuery('users', 'u') + .select(['u.id', 'u.name', 'sq.order_count']) + .join({ + type: 'LEFT', + subQuery: subQuery, + alias: 'sq', + on: 'u.id = sq.user_id' + }) + .where('u.active = ?', true) + .orderBy({ field: 'sq.order_count', direction: 'DESC' }) + .limit(10); + + const built = mainQuery.build(); + + expect(built.text).toBe('SELECT\n "u"."id",\n "u"."name",\n "sq"."order_count"\nFROM "users" AS u\nLEFT JOIN (\n SELECT\n "o"."user_id",\n COUNT(o.id) AS order_count\n FROM "orders" AS o\n WHERE (o.status = $1)\n GROUP BY "o"."user_id"\n) sq\n ON u.id = sq.user_id\nWHERE (u.active = $2)\nORDER BY "sq"."order_count" DESC\nLIMIT 10'); + expect(built.values).toEqual(['completed', true]); + + + const subQuery2 = new SelectQuery('products', 'p') + .select(['p.category_id']) + .addRawSelect('AVG(p.price) AS avg_price') + .where('p.stock > ?', 0) + .groupBy('p.category_id'); + + const mainQuery2 = new SelectQuery('categories', 'c') + .select(['c.id', 'c.name', 'sq.avg_price']) + .join({ + type: 'INNER', + subQuery: subQuery2, + alias: 'sq', + on: new Statement().and('c.id = sq.category_id').and('sq.avg_price > ?', 50) + }) + .where('c.active = ?', true) + .orderBy({ field: 'sq.avg_price', direction: 'DESC' }) + .limit(5); + + const built2 = mainQuery2.build(); + + expect(built2.text).toBe('SELECT\n "c"."id",\n "c"."name",\n "sq"."avg_price"\nFROM "categories" AS c\nINNER JOIN (\n SELECT\n "p"."category_id",\n AVG(p.price) AS avg_price\n FROM "products" AS p\n WHERE (p.stock > $1)\n GROUP BY "p"."category_id"\n) sq\n ON (c.id = sq.category_id) AND (sq.avg_price > $2)\nWHERE (c.active = $3)\nORDER BY "sq"."avg_price" DESC\nLIMIT 5'); + expect(built2.values).toEqual([0, 50, true]); + }); + + + it('should support multiple subQuery joins', () => { + const ordersSubQuery = new SelectQuery('orders', 'o') + .select(['o.user_id']) + .addRawSelect('COUNT(o.id) AS order_count') + .where('o.status = ?', 'completed') + .groupBy('o.user_id'); + + const reviewsSubQuery = new SelectQuery('reviews', 'r') + .select(['r.user_id']) + .addRawSelect('AVG(r.rating) AS avg_rating') + .where('r.approved = ?', true) + .groupBy('r.user_id'); + + const mainQuery = new SelectQuery('users', 'u') + .select(['u.id', 'u.name', 'osq.order_count', 'rsq.avg_rating']) + .join({ + type: 'LEFT', + subQuery: ordersSubQuery, + alias: 'osq', + on: 'u.id = osq.user_id' + }) + .join({ + type: 'LEFT', + subQuery: reviewsSubQuery, + alias: 'rsq', + on: 'u.id = rsq.user_id' + }) + .where('u.active = ?', true) + .orderBy([ + { field: 'osq.order_count', direction: 'DESC' }, + { field: 'rsq.avg_rating', direction: 'DESC' } + ]) + .limit(10); + + const built = mainQuery.build(); + + expect(built.text).toBe('SELECT\n "u"."id",\n "u"."name",\n "osq"."order_count",\n "rsq"."avg_rating"\nFROM "users" AS u\nLEFT JOIN (\n SELECT\n "o"."user_id",\n COUNT(o.id) AS order_count\n FROM "orders" AS o\n WHERE (o.status = $1)\n GROUP BY "o"."user_id"\n) osq\n ON u.id = osq.user_id\nLEFT JOIN (\n SELECT\n "r"."user_id",\n AVG(r.rating) AS avg_rating\n FROM "reviews" AS r\n WHERE (r.approved = $2)\n GROUP BY "r"."user_id"\n) rsq\n ON u.id = rsq.user_id\nWHERE (u.active = $2)\nORDER BY "osq"."order_count" DESC, "rsq"."avg_rating" DESC\nLIMIT 10'); + expect(built.values).toEqual(['completed', true]); + }); + + it('should support multiple CTEs', () => { + const activeUsersCte = new Cte( + 'active_users', + new SelectQuery('users').where('active = ?', true).select(['id', 'name']), + false + ); + + const recentOrdersCte = new Cte( + 'recent_orders', + new SelectQuery('orders').where('created_at > ?', '2023-01-01').select(['id', 'user_id', 'total']), + false + ); + + const mainQuery = new SelectQuery('active_users', 'au') + .with([activeUsersCte, recentOrdersCte]) + .join({ + type: 'INNER', + table: 'recent_orders', + alias: 'ro', + on: 'au.id = ro.user_id' + }) + .select(['au.id', 'au.name', 'ro.total']) + .orderBy({ field: 'ro.total', direction: 'DESC' }) + .limit(10); + + const built = mainQuery.build(); + + expect(built.text).toBe('WITH active_users AS (\nSELECT\n "id",\n "name"\nFROM "users"\nWHERE (active = $1)\n), recent_orders AS (\nSELECT\n "id",\n "user_id",\n "total"\nFROM "orders"\nWHERE (created_at > $2)\n)\nSELECT\n "au"."id",\n "au"."name",\n "ro"."total"\nFROM "active_users" AS au\nINNER JOIN "recent_orders" ro\n ON au.id = ro.user_id\nORDER BY "ro"."total" DESC\nLIMIT 10'); + expect(built.values).toEqual([true, '2023-01-01']); + }); + + + it('should support multipe CTEs with subQuery joins', () => { + const activeUsersCte = new Cte( + 'active_users', + new SelectQuery('users').where('active = ?', true).select(['id', 'name']), + false + ); + + const recentOrdersCte = new Cte( + 'recent_orders', + new SelectQuery('orders').where('created_at > ?', '2023-01-01').select(['id', 'user_id', 'total']), + false + ); + + const ordersSubQuery = new SelectQuery('orders', 'o') + .select(['o.user_id']) + .addRawSelect('COUNT(o.id) AS order_count') + .where('o.status = ?', 'completed') + .groupBy('o.user_id'); + + const mainQuery = new SelectQuery('active_users', 'au') + .with([activeUsersCte, recentOrdersCte]) + .join({ + type: 'INNER', + subQuery: ordersSubQuery, + alias: 'osq', + on: 'au.id = osq.user_id' + }) + .join({ + type: 'INNER', + table: 'recent_orders', + alias: 'ro', + on: 'au.id = ro.user_id' + }) + .select(['au.id', 'au.name', 'osq.order_count', 'ro.total']) + .orderBy({ field: 'ro.total', direction: 'DESC' }) + .limit(10); + + const built = mainQuery.build(); + + expect(built.text).toBe('WITH active_users AS (\nSELECT\n "id",\n "name"\nFROM "users"\nWHERE (active = $1)\n), recent_orders AS (\nSELECT\n "id",\n "user_id",\n "total"\nFROM "orders"\nWHERE (created_at > $2)\n)\nSELECT\n "au"."id",\n "au"."name",\n "osq"."order_count",\n "ro"."total"\nFROM "active_users" AS au\nINNER JOIN (\n SELECT\n "o"."user_id",\n COUNT(o.id) AS order_count\n FROM "orders" AS o\n WHERE (o.status = $3)\n GROUP BY "o"."user_id"\n) osq\n ON au.id = osq.user_id\nINNER JOIN "recent_orders" ro\n ON au.id = ro.user_id\nORDER BY "ro"."total" DESC\nLIMIT 10'); + expect(built.values).toEqual([true, '2023-01-01', 'completed']); + }); + }); diff --git a/src/queryKinds/select.ts b/src/queryKinds/select.ts index c40ba59..fcdb098 100644 --- a/src/queryKinds/select.ts +++ b/src/queryKinds/select.ts @@ -1,7 +1,7 @@ import CteMaker, { Cte } from "../cteMaker.js"; import SqlEscaper from "../sqlEscaper.js"; import Statement from "../statementMaker.js"; -import Join from "../types/Join.js"; +import Join, { isJoinTable } from "../types/Join.js"; import QueryKind from "../types/QueryKind.js"; import OrderBy, { isOrderByField } from "../types/OrderBy.js"; import QueryDefinition from "./query.js"; @@ -307,19 +307,24 @@ export default class SelectQuery extends QueryDefinition { join: Join | Join[] ): this { if (Array.isArray(join)) { - this.joins.push(...join.map(j => ({ - ...j, - table: SqlEscaper.escapeTableName(j.table, this.flavor), - }))); + this.joins.push(...join.map(j => this.parseJoinObject(j))); } else { - this.joins.push({ - ...join, - table: SqlEscaper.escapeTableName(join.table, this.flavor), - }); + this.joins.push(this.parseJoinObject(join)); } return this; } + public parseOrderByObject(orderBy: OrderBy): OrderBy { + let field = ''; + if(isOrderByField(orderBy)) field = orderBy.field; + else field = orderBy.column; + + return { + ...orderBy, + field: SqlEscaper.escapeSelectIdentifiers([field], this.flavor)[0]! + } as OrderBy; + } + /** * Adds ORDER BY clauses to the query, * either as a single OrderBy object or an array of OrderBy objects. @@ -331,26 +336,10 @@ export default class SelectQuery extends QueryDefinition { ): this { if (Array.isArray(orderBy)) { this.orderBys.push( - ...orderBy.map(ob => { - let field = ''; - if(isOrderByField(ob)) field = ob.field; - else field = ob.column; - - return { - ...ob, - field: SqlEscaper.escapeSelectIdentifiers([field], this.flavor)[0]! - } - }) + ...orderBy.map(ob => this.parseOrderByObject(ob)) ); } else { - let field = ''; - if(isOrderByField(orderBy)) field = orderBy.field; - else field = orderBy.column; - - this.orderBys.push({ - ...orderBy, - field: SqlEscaper.escapeSelectIdentifiers([field], this.flavor)[0]! - }); + this.orderBys.push(this.parseOrderByObject(orderBy)); } return this; } @@ -546,10 +535,12 @@ export default class SelectQuery extends QueryDefinition { this.whereStatement = this.whereStatement || new Statement(); let ctesClause = ''; + let cteValues: any[] = []; if (this.ctes) { const ctesBuilt = this.ctes.build(); ctesClause = ctesBuilt.text; - this.whereStatement.addParams(ctesBuilt.values); + cteValues = ctesBuilt.values; + this.whereStatement.addOffset(cteValues.length); } let selectClause = this.selectFields.length > 0 ? this.selectFields.join(',\n ') : '*'; @@ -565,21 +556,44 @@ export default class SelectQuery extends QueryDefinition { if (this.tableAlias) fromClause += ` AS ${this.tableAlias}`; let joinClauses = ''; - let currentOffset = 0; + let currentOffset = cteValues.length; let parametersToAdd: any = []; for (const join of this.joins) { - const onClause = - typeof join.on === 'string' ? join.on - : (() => { - join.on.disableWhere(); - join.on.addOffset(currentOffset); - const stmt = join.on.build(false); - currentOffset += stmt.values.length; - parametersToAdd.push(...stmt.values); - return stmt.statement; - })(); - joinClauses += - `${joinClauses ? '\n' : ''}${join.type.toUpperCase()} JOIN ${join.table} ${join.alias}\n ON ${onClause}`; + if(isJoinTable(join)) { + const onClause = + typeof join.on === 'string' ? join.on + : (() => { + join.on.disableWhere(); + join.on.addOffset(currentOffset); + const stmt = join.on.build(false); + currentOffset += stmt.values.length; + parametersToAdd.push(...stmt.values); + return stmt.statement; + })(); + joinClauses += + `${joinClauses ? '\n' : ''}${join.type.toUpperCase()} JOIN ${join.table} ${join.alias}\n ON ${onClause}`; + } else { + join.subQuery.resetWhereOffset(); + join.subQuery.addWhereOffset(currentOffset); + join.subQuery.disabledAnalysis = true; + const subQueryBuilt = join.subQuery.build(deepAnalysis); + join.subQuery.disabledAnalysis = false; + currentOffset += subQueryBuilt.values.length; + parametersToAdd.push(...subQueryBuilt.values); + const onClause = + typeof join.on === 'string' ? join.on + : (() => { + join.on.disableWhere(); + join.on.addOffset(currentOffset); + const stmt = join.on.build(false); + currentOffset += stmt.values.length; + parametersToAdd.push(...stmt.values); + return stmt.statement; + })(); + subQueryBuilt.text = this.spaceLines(subQueryBuilt.text, 1); + joinClauses += + `${joinClauses ? '\n' : ''}${join.type.toUpperCase()} JOIN (\n${subQueryBuilt.text}\n) ${join.alias}\n ON ${onClause}`; + } } this.whereStatement.addParams(parametersToAdd); @@ -587,7 +601,7 @@ export default class SelectQuery extends QueryDefinition { this.whereStatement.enableWhere(); const stmt = this.whereStatement.build(); const whereClause = stmt.statement; - const values = stmt.values; + const values = [...cteValues, ...stmt.values]; let groupByClause = ''; if ( From 422a4ab4c36b74838bd2fd59a313a0a80b3df55b Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 29 Sep 2025 01:43:31 -0300 Subject: [PATCH 06/74] feat/fix/fixed how ctes managed with queries and also added subquery joins to update --- src/queryKinds/update.test.ts | 173 +++++++++++++++++++++++++++++++++- src/queryKinds/update.ts | 65 ++++++++----- 2 files changed, 214 insertions(+), 24 deletions(-) diff --git a/src/queryKinds/update.test.ts b/src/queryKinds/update.test.ts index 2ddef62..b78cb7d 100644 --- a/src/queryKinds/update.test.ts +++ b/src/queryKinds/update.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import UpdateQuery from "./update.js"; import Statement from "../statementMaker.js"; -import { Cte } from "../cteMaker.js"; +import CteMaker, { Cte } from "../cteMaker.js"; import SelectQuery from "./select.js"; describe('Update Query', () => { @@ -320,4 +320,175 @@ describe('Update Query', () => { expect(secondBuild.values).toEqual([false, 'inactive']); }); + it('should support subQuery in joins', () => { + const subQuery = new SelectQuery('departments', 'd') + .select(['d.id', 'd.name']) + .where('d.active = ?', true); + + const query = new UpdateQuery('employees', 'e') + .using('companies', 'c') + .join({ + subQuery, + alias: 'dept', + type: 'INNER', + on: 'e.department_id = dept.id' + }) + .set({ 'e.status': 'active' }) + .where( + new Statement() + .and('e.company_id = c.id') + .and('c.active = ?', true) + .and('dept.name = ?', 'Engineering') + ) + .build(); + + expect(query.text).toBe('UPDATE "employees" e\nSET "e"."status" = $1\nFROM "companies" c\nINNER JOIN (\n SELECT\n "d"."id",\n "d"."name"\n FROM "departments" AS d\n WHERE (d.active = $2)\n) dept\n ON e.department_id = dept.id\nWHERE (e.company_id = c.id)\n AND (c.active = $2)\n AND (dept.name = $3)'); + expect(query.values).toEqual(['active', true, 'Engineering']); + + const subQuery2 = new SelectQuery('departments', 'd') + .select(['d.id', 'd.name']) + .where('d.active = ?', true); + + const query2 = new UpdateQuery('employees', 'e') + .using('companies', 'c') + .join({ + subQuery: subQuery2, + alias: 'dept', + type: 'INNER', + on: new Statement() + .and('e.department_id = dept.id') + .and('dept.name = ?', 'Engineering') + }) + .set({ 'e.status': 'active' }) + .where( + new Statement() + .and('e.company_id = c.id') + .and('c.active = ?', true) + ) + .build(); + + expect(query2.text).toBe('UPDATE "employees" e\nSET "e"."status" = $1\nFROM "companies" c\nINNER JOIN (\n SELECT\n "d"."id",\n "d"."name"\n FROM "departments" AS d\n WHERE (d.active = $2)\n) dept\n ON (e.department_id = dept.id) AND (dept.name = $3)\nWHERE (e.company_id = c.id)\n AND (c.active = $2)'); + expect(query2.values).toEqual(['active', true, 'Engineering']); + }); + + it('should support multiple joins with subQueries', () => { + const regionSubQuery = new SelectQuery('regions', 'r') + .select(['r.id', 'r.name']) + .where('r.active = ?', true); + + const departmentSubQuery = new SelectQuery('departments', 'd') + .select(['d.id', 'd.name']) + .where('d.active = ?', true); + + const query = new UpdateQuery('employees', 'e') + .using('companies', 'c') + .join([ + { + subQuery: regionSubQuery, + alias: 'reg', + type: 'INNER', + on: 'e.region_id = reg.id' + }, + { + subQuery: departmentSubQuery, + alias: 'dept', + type: 'LEFT', + on: new Statement() + .and('e.department_id = dept.id') + .and('dept.name = ?', 'Engineering') + } + ]) + .set({ 'e.status': 'active' }) + .where( + new Statement() + .and('e.company_id = c.id') + .and('c.active = ?', true) + .and('reg.name = ?', 'North') + ) + .build(); + + expect(query.text).toBe('UPDATE "employees" e\nSET "e"."status" = $1\nFROM "companies" c\nINNER JOIN (\n SELECT\n "r"."id",\n "r"."name"\n FROM "regions" AS r\n WHERE (r.active = $2)\n) reg\n ON e.region_id = reg.id\nLEFT JOIN (\n SELECT\n "d"."id",\n "d"."name"\n FROM "departments" AS d\n WHERE (d.active = $2)\n) dept\n ON (e.department_id = dept.id) AND (dept.name = $3)\nWHERE (e.company_id = c.id)\n AND (c.active = $2)\n AND (reg.name = $4)'); + expect(query.values).toEqual(['active', true, 'Engineering', 'North']); + }); + + it('should support multiple CTEs', () => { + const query = new UpdateQuery('employees', 'e') + .with([ + new Cte( + 'active_companies', + new SelectQuery('companies', 'c') + .select(['c.id', 'c.name']) + .where('c.active = ?', true), + false + ), + new Cte( + 'active_departments', + new SelectQuery('departments', 'd') + .select(['d.id', 'd.name']) + .where('d.active = ?', true), + false + ) + ]) + .using('active_companies', 'ac') + .join({ + table: 'active_departments', + alias: 'ad', + type: 'INNER', + on: new Statement() + .and('e.department_id = ad.id') + .and('ad.name = ?', 'Engineering') + }) + .set({ 'e.status': 'active' }) + .where( + new Statement() + .and('e.company_id = ac.id') + .and('ac.name = ?', 'TechCorp') + ) + .build(); + + expect(query.text).toBe('WITH active_companies AS (\nSELECT\n "c"."id",\n "c"."name"\nFROM "companies" AS c\nWHERE (c.active = $1)\n), active_departments AS (\nSELECT\n "d"."id",\n "d"."name"\nFROM "departments" AS d\nWHERE (d.active = $1)\n)\nUPDATE "employees" e\nSET "e"."status" = $2\nFROM "active_companies" ac\nINNER JOIN "active_departments" ad\n ON (e.department_id = ad.id) AND (ad.name = $3)\nWHERE (e.company_id = ac.id)\n AND (ac.name = $4)'); + expect(query.values).toEqual([true, 'active', 'Engineering', 'TechCorp']); + }); + + it('should support CTEs with CTEMaker directly', () => { + const cteMaker = new CteMaker( + new Cte( + 'active_companies', + new SelectQuery('companies', 'c') + .select(['c.id', 'c.name']) + .where('c.active = ?', true), + false + ), + new Cte( + 'active_departments', + new SelectQuery('departments', 'd') + .select(['d.id', 'd.name']) + .where('d.active = ?', true), + false + ) + ); + + const query = new UpdateQuery('employees', 'e') + .with(cteMaker) + .using('active_companies', 'ac') + .join({ + table: 'active_departments', + alias: 'ad', + type: 'INNER', + on: new Statement() + .and('e.department_id = ad.id') + .and('ad.name = ?', 'Engineering') + }) + .set({ 'e.status': 'active' }) + .where( + new Statement() + .and('e.company_id = ac.id') + .and('ac.name = ?', 'TechCorp') + ) + .build(); + + expect(query.text).toBe('WITH active_companies AS (\nSELECT\n "c"."id",\n "c"."name"\nFROM "companies" AS c\nWHERE (c.active = $1)\n), active_departments AS (\nSELECT\n "d"."id",\n "d"."name"\nFROM "departments" AS d\nWHERE (d.active = $1)\n)\nUPDATE "employees" e\nSET "e"."status" = $2\nFROM "active_companies" ac\nINNER JOIN "active_departments" ad\n ON (e.department_id = ad.id) AND (ad.name = $3)\nWHERE (e.company_id = ac.id)\n AND (ac.name = $4)'); + expect(query.values).toEqual([true, 'active', 'Engineering', 'TechCorp']); + }); + }); diff --git a/src/queryKinds/update.ts b/src/queryKinds/update.ts index 5f7fce9..81e72e3 100644 --- a/src/queryKinds/update.ts +++ b/src/queryKinds/update.ts @@ -1,7 +1,7 @@ import CteMaker, { Cte } from "../cteMaker.js"; import SqlEscaper from "../sqlEscaper.js"; import Statement from "../statementMaker.js"; -import Join from "../types/Join.js"; +import Join, { isJoinTable } from "../types/Join.js"; import QueryKind from "../types/QueryKind.js"; import SetValue from "../types/SetValue.js"; import QueryDefinition from "./query.js"; @@ -87,17 +87,13 @@ export default class UpdateQuery extends QueryDefinition { * @param join - The JOIN clause(s) to be added. * @returns The current UpdateQuery instance for method chaining. */ - public join(join: Join | Join[]): this { + public join( + join: Join | Join[] + ): this { if (Array.isArray(join)) { - this.joins.push(...join.map(j => ({ - ...j, - table: SqlEscaper.escapeTableName(j.table, this.flavor), - }))); + this.joins.push(...join.map(j => this.parseJoinObject(j))); } else { - this.joins.push({ - ...join, - table: SqlEscaper.escapeTableName(join.table, this.flavor), - }); + this.joins.push(this.parseJoinObject(join)); } return this; } @@ -288,21 +284,44 @@ export default class UpdateQuery extends QueryDefinition { } let joinClauses = ''; - let currentOffset = setValues.length; + let currentOffset = setValues.length + cteValues.length; let parametersToAdd: any = []; for (const join of this.joins) { - const onClause = - typeof join.on === 'string' ? join.on - : (() => { - join.on.disableWhere(); - join.on.addOffset(currentOffset); - const stmt = join.on.build(false); - currentOffset += stmt.values.length; - parametersToAdd.push(...stmt.values); - return stmt.statement; - })(); - joinClauses += - `${joinClauses ? '\n' : ''}${join.type.toUpperCase()} JOIN ${join.table} ${join.alias}\n ON ${onClause}`; + if(isJoinTable(join)) { + const onClause = + typeof join.on === 'string' ? join.on + : (() => { + join.on.disableWhere(); + join.on.addOffset(currentOffset); + const stmt = join.on.build(false); + currentOffset += stmt.values.length; + parametersToAdd.push(...stmt.values); + return stmt.statement; + })(); + joinClauses += + `${joinClauses ? '\n' : ''}${join.type.toUpperCase()} JOIN ${join.table} ${join.alias}\n ON ${onClause}`; + } else { + join.subQuery.resetWhereOffset(); + join.subQuery.addWhereOffset(currentOffset); + (join.subQuery as any).disabledAnalysis = true; + const subQueryBuilt = join.subQuery.build(deepAnalysis); + (join.subQuery as any).disabledAnalysis = false; + currentOffset += subQueryBuilt.values.length; + parametersToAdd.push(...subQueryBuilt.values); + const onClause = + typeof join.on === 'string' ? join.on + : (() => { + join.on.disableWhere(); + join.on.addOffset(currentOffset); + const stmt = join.on.build(false); + currentOffset += stmt.values.length; + parametersToAdd.push(...stmt.values); + return stmt.statement; + })(); + subQueryBuilt.text = this.spaceLines(subQueryBuilt.text, 1); + joinClauses += + `${joinClauses ? '\n' : ''}${join.type.toUpperCase()} JOIN (\n${subQueryBuilt.text}\n) ${join.alias}\n ON ${onClause}`; + } } this.whereStatement.addParams(parametersToAdd); From cc7cc961bf15e3e3d3b303d9a38612b3a77286a6 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 29 Sep 2025 01:44:26 -0300 Subject: [PATCH 07/74] Mark issue #70 as complete From 88c03aeb180e733b0b7e59fce1e5cdadd6e252a8 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 29 Sep 2025 01:52:22 -0300 Subject: [PATCH 08/74] bump up version to 0.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 54d9e1f..a553ee0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sqm", - "version": "0.6.4", + "version": "0.7.0", "description": "A lightweight and flexible query maker library for building raw SQL queries in JavaScript.", "main": "dist/index.js", "types": "dist/index.d.ts", From b4a01c92854b442570702add0c3c2b10b17de73d Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 29 Sep 2025 23:20:35 -0300 Subject: [PATCH 09/74] feat/added selecting specific fields from union selects also added tests for that --- src/queryKinds/union.test.ts | 79 +++++++++++++++++++++++++++++++++++ src/queryKinds/union.ts | 80 +++++++++++++++++++++++++++++++++++- 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/queryKinds/union.test.ts b/src/queryKinds/union.test.ts index 32cc01e..2a56a68 100644 --- a/src/queryKinds/union.test.ts +++ b/src/queryKinds/union.test.ts @@ -385,4 +385,83 @@ describe("Union Query", () => { expect(rebuiltOriginal).toEqual(built); }); + it('should support custom select clause', () => { + const select1 = Query.select + .from("table1") + .select(["column1", "column2"]) + .where("column1 = ?", "value1"); + + const select2 = Query.select + .from("table2") + .select(["column1", "column2"]) + .where("column2 = ?", "value2"); + + const unionQuery = new Union() + .addMany([ + { + query: select1, + type: 'union' + }, + { + query: select2, + type: 'union all' + } + ]) + .as('union_table') + .select('column1') + .addSelect('column2') + .addRawSelect('COUNT(column2) AS count_column2') + .groupBy('column1') + .build(); + + console.log(unionQuery.text); + + expect(unionQuery.text).toBe('SELECT\n "column1",\n "column2",\n COUNT(column2) AS count_column2\n FROM (\n (SELECT\n "column1",\n "column2"\n FROM "table1"\n WHERE (column1 = $1))\n\n UNION ALL\n\n (SELECT\n "column1",\n "column2"\n FROM "table2"\n WHERE (column2 = $2))\n) AS union_table\nGROUP BY "column1"'); + expect(unionQuery.values).toEqual(['value1', 'value2']); + }); + + it('should support raw select clause', () => { + const select1 = Query.select + .from("table1") + .select(["column1", "column2"]) + .where("column1 = ?", "value1"); + + const select2 = Query.select + .from("table2") + .select(["column1", "column2"]) + .where("column2 = ?", "value2"); + + const unionQuery = new Union() + .addMany([ + { + query: select1, + type: 'union' + }, + { + query: select2, + type: 'union all' + } + ]) + .as('union_table') + .rawSelect('column1') + .addSelect([ + 'column2', + ]) + .rawSelect([ + 'column1', + 'column2', + 'COUNT(column2) AS count_column2' + ]) + .addRawSelect('COUNT(column2) AS count_column2') + .select([ + 'column1', + 'column2' + ]) + .groupBy('column1') + .build(); + + expect(unionQuery.text).toBe('SELECT\n "column1",\n "column2"\n FROM (\n (SELECT\n "column1",\n "column2"\n FROM "table1"\n WHERE (column1 = $1))\n\n UNION ALL\n\n (SELECT\n "column1",\n "column2"\n FROM "table2"\n WHERE (column2 = $2))\n) AS union_table\nGROUP BY "column1"'); + expect(unionQuery.values).toEqual(['value1', 'value2']); + }); + }); diff --git a/src/queryKinds/union.ts b/src/queryKinds/union.ts index 9bcb85e..a10cb3f 100644 --- a/src/queryKinds/union.ts +++ b/src/queryKinds/union.ts @@ -3,6 +3,7 @@ import QueryKind from "../types/QueryKind.js"; import OrderBy from "../types/OrderBy.js"; import QueryDefinition from "./query.js"; import SelectQuery from "./select.js"; +import SqlEscaper from "../sqlEscaper.js"; /** Allowed types for UnionType */ export const UnionTypes = { @@ -42,6 +43,9 @@ export type SelectQueryWithUnionType = { * and can optionally assign an alias to the resulting union query. */ export default class Union extends QueryDefinition { + + private selectFields: string[] = []; + /** Needed alias for the union query */ private unionAlias: string | null = null; @@ -98,6 +102,67 @@ export default class Union extends QueryDefinition { }; } + /** + * Specifies the fields to select in the union query. + * If not called, defaults to selecting all fields ('*'). + * @param fields A single field name or an array of field names to select. + * @returns The current Union instance for method chaining. + */ + public select(fields: string | string[]): Union { + if (Array.isArray(fields)) { + this.rawSelect(SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); + } else { + this.rawSelect(SqlEscaper.escapeSelectIdentifiers([fields], this.flavor)); + } + return this; + } + + /** + * Adds fields to the existing selection in the union query. + * If no fields have been selected yet, this behaves like the select() method. + * @param fields A single field name or an array of field names to add to the selection. + * @returns The current Union instance for method chaining. + */ + public addSelect(fields: string | string[]): Union { + if (Array.isArray(fields)) { + this.addRawSelect(SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); + } else { + this.addRawSelect(SqlEscaper.escapeSelectIdentifiers([fields], this.flavor)); + } + return this; + } + + /** + * Specifies raw fields to select in the union query without any escaping. + * Use this method with caution, as it does not perform any SQL injection protection. + * @param fields A single raw field string or an array of raw field strings to select. + * @returns The current Union instance for method chaining. + */ + public rawSelect(fields: string | string[]): Union { + if (Array.isArray(fields)) { + this.selectFields = fields; + } else { + this.selectFields = [fields]; + } + return this; + } + + /** + * Adds raw fields to the existing selection in the union query without any escaping. + * Use this method with caution, as it does not perform any SQL injection protection. + * If no fields have been selected yet, this behaves like the rawSelect() method. + * @param fields A single raw field string or an array of raw field strings to add to the selection. + * @returns The current Union instance for method chaining. + */ + public addRawSelect(fields: string | string[]): Union { + if (Array.isArray(fields)) { + this.selectFields.push(...fields); + } else { + this.selectFields.push(fields); + } + return this; + } + /** * Assigns an alias to the resulting union query. * @param alias The alias to assign to the union query. @@ -317,6 +382,13 @@ export default class Union extends QueryDefinition { let unionItself: string = ''; const values: any[] = []; + let selectClause = ''; + if (this.selectFields.length > 0) { + selectClause = this.selectFields.join(',\n '); + } else { + selectClause = '*'; + } + // Add offset on each select query to ensure correct parameter indexing let paramOffset = 1; for (const { query, type: unionType } of this.selectQueries) { @@ -375,8 +447,11 @@ export default class Union extends QueryDefinition { offsetClause = `OFFSET ${this.offsetCount}`; } + const firstLine = + `SELECT${selectClause.length > 1 ? '\n' : ''} ${selectClause}${selectClause.length > 1 ? '\n' : ''} FROM (`; + const union = [ - 'SELECT * FROM (', + firstLine, `${unionItself}\n) AS ${this.unionAlias || 'union_subquery'}`, whereClause, groupByClause, @@ -436,6 +511,8 @@ export default class Union extends QueryDefinition { */ public clone(): Union { const newUnion = new Union(); + newUnion.selectFields = [...this.selectFields]; + newUnion.flavor = this.flavor; newUnion.unionAlias = this.unionAlias; newUnion.limitCount = this.limitCount; newUnion.offsetCount = this.offsetCount; @@ -459,6 +536,7 @@ export default class Union extends QueryDefinition { * After calling this method, the Union instance will be in its initial state. */ public reset(): void { + this.selectFields = []; this.unionAlias = null; this.limitCount = null; this.offsetCount = null; From ef4dd3da8212c8b7a9c581f82000d214166c7ffc Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 29 Sep 2025 23:24:28 -0300 Subject: [PATCH 10/74] fix/refact/select and addSelect methods from the select query now uses the respective raw select methods for simplicity, also removed console.log from the test --- src/queryKinds/select.ts | 14 ++++---------- src/queryKinds/union.test.ts | 2 -- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/queryKinds/select.ts b/src/queryKinds/select.ts index fcdb098..c6ff83f 100644 --- a/src/queryKinds/select.ts +++ b/src/queryKinds/select.ts @@ -183,11 +183,9 @@ export default class SelectQuery extends QueryDefinition { */ public select(fields: string | string[]): this { if (Array.isArray(fields)) { - this.selectFields = - SqlEscaper.escapeSelectIdentifiers(fields, this.flavor); + this.rawSelect(SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); } else { - this.selectFields = - SqlEscaper.escapeSelectIdentifiers([fields], this.flavor); + this.rawSelect(SqlEscaper.escapeSelectIdentifiers([fields], this.flavor)); } return this; } @@ -230,13 +228,9 @@ export default class SelectQuery extends QueryDefinition { */ public addSelect(fields: string | string[]): this { if (Array.isArray(fields)) { - const escaped = - SqlEscaper.escapeSelectIdentifiers(fields, this.flavor); - this.selectFields.push(...escaped); + this.addRawSelect(SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); } else { - const escaped = - SqlEscaper.escapeSelectIdentifiers([fields], this.flavor); - this.selectFields.push(...escaped); + this.addRawSelect(SqlEscaper.escapeSelectIdentifiers([fields], this.flavor)); } return this; } diff --git a/src/queryKinds/union.test.ts b/src/queryKinds/union.test.ts index 2a56a68..34f6db4 100644 --- a/src/queryKinds/union.test.ts +++ b/src/queryKinds/union.test.ts @@ -414,8 +414,6 @@ describe("Union Query", () => { .groupBy('column1') .build(); - console.log(unionQuery.text); - expect(unionQuery.text).toBe('SELECT\n "column1",\n "column2",\n COUNT(column2) AS count_column2\n FROM (\n (SELECT\n "column1",\n "column2"\n FROM "table1"\n WHERE (column1 = $1))\n\n UNION ALL\n\n (SELECT\n "column1",\n "column2"\n FROM "table2"\n WHERE (column2 = $2))\n) AS union_table\nGROUP BY "column1"'); expect(unionQuery.values).toEqual(['value1', 'value2']); }); From b9cdce66422a2828142e844d6eab695395826eb6 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 29 Sep 2025 23:27:37 -0300 Subject: [PATCH 11/74] Fixed issue #64 From 85da5b8c058c4abf5ac8b0abfdfebb07d555b159 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 30 Sep 2025 00:30:06 -0300 Subject: [PATCH 12/74] docs/add docs for creating unions --- src/queryMaker.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/queryMaker.ts b/src/queryMaker.ts index fc9a363..61cf937 100644 --- a/src/queryMaker.ts +++ b/src/queryMaker.ts @@ -86,6 +86,10 @@ class Query { return new Cte(); } + /** + * Initiates a new UNION query. + * @returns A new Union instance with a build method that respects the deepAnalysisDefault setting. + */ public get union(): Union { const unionQuery = new Union(); (unionQuery as any).flavor = this.flavor; @@ -145,6 +149,10 @@ class Query { return new Cte(); } + /** + * Initiates a new UNION query. + * @returns A new Union instance. + */ public static get union(): Union { return new Union(); } From 43327227f0b0ee9543c86834cb1fbb3ea5483f48 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 30 Sep 2025 00:39:06 -0300 Subject: [PATCH 13/74] refact/moved queryKind files to queryKind/dml since they were all dml query kinds --- src/cteMaker.ts | 4 ++-- src/queryKinds/{ => dml}/delete.test.ts | 4 ++-- src/queryKinds/{ => dml}/delete.ts | 10 +++++----- src/queryKinds/{ => dml}/insert.test.ts | 2 +- src/queryKinds/{ => dml}/insert.ts | 8 ++++---- src/queryKinds/{ => dml}/query.test.ts | 2 +- src/queryKinds/{ => dml}/query.ts | 16 ++++++++-------- src/queryKinds/{ => dml}/select.test.ts | 4 ++-- src/queryKinds/{ => dml}/select.ts | 12 ++++++------ src/queryKinds/{ => dml}/union.test.ts | 4 ++-- src/queryKinds/{ => dml}/union.ts | 8 ++++---- src/queryKinds/{ => dml}/update.test.ts | 4 ++-- src/queryKinds/{ => dml}/update.ts | 12 ++++++------ src/queryMaker.ts | 10 +++++----- src/types/Join.ts | 2 +- 15 files changed, 51 insertions(+), 51 deletions(-) rename src/queryKinds/{ => dml}/delete.test.ts (99%) rename src/queryKinds/{ => dml}/delete.ts (97%) rename src/queryKinds/{ => dml}/insert.test.ts (99%) rename src/queryKinds/{ => dml}/insert.ts (98%) rename src/queryKinds/{ => dml}/query.test.ts (99%) rename src/queryKinds/{ => dml}/query.ts (97%) rename src/queryKinds/{ => dml}/select.test.ts (99%) rename src/queryKinds/{ => dml}/select.ts (98%) rename src/queryKinds/{ => dml}/union.test.ts (99%) rename src/queryKinds/{ => dml}/union.ts (99%) rename src/queryKinds/{ => dml}/update.test.ts (99%) rename src/queryKinds/{ => dml}/update.ts (98%) diff --git a/src/cteMaker.ts b/src/cteMaker.ts index c95d072..f448273 100644 --- a/src/cteMaker.ts +++ b/src/cteMaker.ts @@ -1,5 +1,5 @@ -import QueryDefinition from "./queryKinds/query.js"; -import SelectQuery from "./queryKinds/select.js"; +import QueryDefinition from "./queryKinds/dml/query.js"; +import SelectQuery from "./queryKinds/dml/select.js"; /** * Cte represents a Common Table Expression (CTE) in SQL. diff --git a/src/queryKinds/delete.test.ts b/src/queryKinds/dml/delete.test.ts similarity index 99% rename from src/queryKinds/delete.test.ts rename to src/queryKinds/dml/delete.test.ts index 8dbd5f5..a7e7dd0 100644 --- a/src/queryKinds/delete.test.ts +++ b/src/queryKinds/dml/delete.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import DeleteQuery from "./delete.js"; -import Statement from "../statementMaker.js"; -import { Cte } from "../cteMaker.js"; +import Statement from "../../statementMaker.js"; +import { Cte } from "../../cteMaker.js"; import SelectQuery from "./select.js"; describe('Delete Query', () => { diff --git a/src/queryKinds/delete.ts b/src/queryKinds/dml/delete.ts similarity index 97% rename from src/queryKinds/delete.ts rename to src/queryKinds/dml/delete.ts index a2fd035..c910573 100644 --- a/src/queryKinds/delete.ts +++ b/src/queryKinds/dml/delete.ts @@ -1,8 +1,8 @@ -import CteMaker, { Cte } from "../cteMaker.js"; -import SqlEscaper from "../sqlEscaper.js"; -import Statement from "../statementMaker.js"; -import QueryKind from "../types/QueryKind.js"; -import UsingTable from "../types/UsingTable.js"; +import CteMaker, { Cte } from "../../cteMaker.js"; +import SqlEscaper from "../../sqlEscaper.js"; +import Statement from "../../statementMaker.js"; +import QueryKind from "../../types/QueryKind.js"; +import UsingTable from "../../types/UsingTable.js"; import QueryDefinition from "./query.js"; /** diff --git a/src/queryKinds/insert.test.ts b/src/queryKinds/dml/insert.test.ts similarity index 99% rename from src/queryKinds/insert.test.ts rename to src/queryKinds/dml/insert.test.ts index 886730f..bf20c24 100644 --- a/src/queryKinds/insert.test.ts +++ b/src/queryKinds/dml/insert.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import InsertQuery from "./insert.js"; import SelectQuery from "./select.js"; -import { Cte } from "../cteMaker.js"; +import { Cte } from "../../cteMaker.js"; describe('Insert Query', () => { diff --git a/src/queryKinds/insert.ts b/src/queryKinds/dml/insert.ts similarity index 98% rename from src/queryKinds/insert.ts rename to src/queryKinds/dml/insert.ts index 90fc76f..7cb264d 100644 --- a/src/queryKinds/insert.ts +++ b/src/queryKinds/dml/insert.ts @@ -1,7 +1,7 @@ -import CteMaker, { Cte } from "../cteMaker.js"; -import SqlEscaper from "../sqlEscaper.js"; -import ColumnValue from "../types/ColumnValue.js"; -import QueryKind from "../types/QueryKind.js"; +import CteMaker, { Cte } from "../../cteMaker.js"; +import SqlEscaper from "../../sqlEscaper.js"; +import ColumnValue from "../../types/ColumnValue.js"; +import QueryKind from "../../types/QueryKind.js"; import QueryDefinition from "./query.js"; import SelectQuery from "./select.js"; diff --git a/src/queryKinds/query.test.ts b/src/queryKinds/dml/query.test.ts similarity index 99% rename from src/queryKinds/query.test.ts rename to src/queryKinds/dml/query.test.ts index 7bad285..5e38926 100644 --- a/src/queryKinds/query.test.ts +++ b/src/queryKinds/dml/query.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import SelectQuery from "./select.js"; -import sqlFlavor from "../types/sqlFlavor.js"; +import sqlFlavor from "../../types/sqlFlavor.js"; import UpdateQuery from "./update.js"; import InsertQuery from "./insert.js"; import DeleteQuery from "./delete.js"; diff --git a/src/queryKinds/query.ts b/src/queryKinds/dml/query.ts similarity index 97% rename from src/queryKinds/query.ts rename to src/queryKinds/dml/query.ts index 2f2177f..118e469 100644 --- a/src/queryKinds/query.ts +++ b/src/queryKinds/dml/query.ts @@ -1,16 +1,16 @@ import { ValidatorOptions } from "class-validator"; -import deepEqual from "../deepEqual.js"; -import { getClassValidator, getZod } from "../getOptionalPackages.js"; +import deepEqual from "../../deepEqual.js"; +import { getClassValidator, getZod } from "../../getOptionalPackages.js"; // Import types only since they are used for type checking only // and zod is optional peer dependency import type z from "zod"; import type { ZodObject } from "zod"; -import sqlFlavor from "../types/sqlFlavor.js"; -import CteMaker from "../cteMaker.js"; -import Statement from "../statementMaker.js"; -import QueryKind from "../types/QueryKind.js"; -import Join, { isJoinTable } from "../types/Join.js"; -import SqlEscaper from "../sqlEscaper.js"; +import sqlFlavor from "../../types/sqlFlavor.js"; +import CteMaker from "../../cteMaker.js"; +import Statement from "../../statementMaker.js"; +import QueryKind from "../../types/QueryKind.js"; +import Join, { isJoinTable } from "../../types/Join.js"; +import SqlEscaper from "../../sqlEscaper.js"; /** * An array of function names that can be used to execute SQL queries. diff --git a/src/queryKinds/select.test.ts b/src/queryKinds/dml/select.test.ts similarity index 99% rename from src/queryKinds/select.test.ts rename to src/queryKinds/dml/select.test.ts index 4064a62..26453a8 100644 --- a/src/queryKinds/select.test.ts +++ b/src/queryKinds/dml/select.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import SelectQuery from "./select.js"; -import Statement from "../statementMaker.js"; -import CteMaker, { Cte } from "../cteMaker.js"; +import Statement from "../../statementMaker.js"; +import CteMaker, { Cte } from "../../cteMaker.js"; describe('Select Query', () => { it('should generate correct SELECT SQL', () => { diff --git a/src/queryKinds/select.ts b/src/queryKinds/dml/select.ts similarity index 98% rename from src/queryKinds/select.ts rename to src/queryKinds/dml/select.ts index c6ff83f..81c3b02 100644 --- a/src/queryKinds/select.ts +++ b/src/queryKinds/dml/select.ts @@ -1,9 +1,9 @@ -import CteMaker, { Cte } from "../cteMaker.js"; -import SqlEscaper from "../sqlEscaper.js"; -import Statement from "../statementMaker.js"; -import Join, { isJoinTable } from "../types/Join.js"; -import QueryKind from "../types/QueryKind.js"; -import OrderBy, { isOrderByField } from "../types/OrderBy.js"; +import CteMaker, { Cte } from "../../cteMaker.js"; +import SqlEscaper from "../../sqlEscaper.js"; +import Statement from "../../statementMaker.js"; +import Join, { isJoinTable } from "../../types/Join.js"; +import QueryKind from "../../types/QueryKind.js"; +import OrderBy, { isOrderByField } from "../../types/OrderBy.js"; import QueryDefinition from "./query.js"; import Union from "./union.js"; diff --git a/src/queryKinds/union.test.ts b/src/queryKinds/dml/union.test.ts similarity index 99% rename from src/queryKinds/union.test.ts rename to src/queryKinds/dml/union.test.ts index 34f6db4..8e62f35 100644 --- a/src/queryKinds/union.test.ts +++ b/src/queryKinds/dml/union.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import Union from "./union.js"; -import Query from "../queryMaker.js"; -import Statement from "../statementMaker.js"; +import Query from "../../queryMaker.js"; +import Statement from "../../statementMaker.js"; describe("Union Query", () => { it('should create a UNION query with two SELECT statements', () => { diff --git a/src/queryKinds/union.ts b/src/queryKinds/dml/union.ts similarity index 99% rename from src/queryKinds/union.ts rename to src/queryKinds/dml/union.ts index a10cb3f..4ace133 100644 --- a/src/queryKinds/union.ts +++ b/src/queryKinds/dml/union.ts @@ -1,9 +1,9 @@ -import Statement from "../statementMaker.js"; -import QueryKind from "../types/QueryKind.js"; -import OrderBy from "../types/OrderBy.js"; +import Statement from "../../statementMaker.js"; +import QueryKind from "../../types/QueryKind.js"; +import OrderBy from "../../types/OrderBy.js"; import QueryDefinition from "./query.js"; import SelectQuery from "./select.js"; -import SqlEscaper from "../sqlEscaper.js"; +import SqlEscaper from "../../sqlEscaper.js"; /** Allowed types for UnionType */ export const UnionTypes = { diff --git a/src/queryKinds/update.test.ts b/src/queryKinds/dml/update.test.ts similarity index 99% rename from src/queryKinds/update.test.ts rename to src/queryKinds/dml/update.test.ts index b78cb7d..acd3c2b 100644 --- a/src/queryKinds/update.test.ts +++ b/src/queryKinds/dml/update.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import UpdateQuery from "./update.js"; -import Statement from "../statementMaker.js"; -import CteMaker, { Cte } from "../cteMaker.js"; +import Statement from "../../statementMaker.js"; +import CteMaker, { Cte } from "../../cteMaker.js"; import SelectQuery from "./select.js"; describe('Update Query', () => { diff --git a/src/queryKinds/update.ts b/src/queryKinds/dml/update.ts similarity index 98% rename from src/queryKinds/update.ts rename to src/queryKinds/dml/update.ts index 81e72e3..94c6f3c 100644 --- a/src/queryKinds/update.ts +++ b/src/queryKinds/dml/update.ts @@ -1,9 +1,9 @@ -import CteMaker, { Cte } from "../cteMaker.js"; -import SqlEscaper from "../sqlEscaper.js"; -import Statement from "../statementMaker.js"; -import Join, { isJoinTable } from "../types/Join.js"; -import QueryKind from "../types/QueryKind.js"; -import SetValue from "../types/SetValue.js"; +import CteMaker, { Cte } from "../../cteMaker.js"; +import SqlEscaper from "../../sqlEscaper.js"; +import Statement from "../../statementMaker.js"; +import Join, { isJoinTable } from "../../types/Join.js"; +import QueryKind from "../../types/QueryKind.js"; +import SetValue from "../../types/SetValue.js"; import QueryDefinition from "./query.js"; /** diff --git a/src/queryMaker.ts b/src/queryMaker.ts index 61cf937..8f36cac 100644 --- a/src/queryMaker.ts +++ b/src/queryMaker.ts @@ -1,9 +1,9 @@ import { Cte } from "./cteMaker.js"; -import DeleteQuery from "./queryKinds/delete.js"; -import InsertQuery from "./queryKinds/insert.js"; -import SelectQuery from "./queryKinds/select.js"; -import Union from "./queryKinds/union.js"; -import UpdateQuery from "./queryKinds/update.js"; +import DeleteQuery from "./queryKinds/dml/delete.js"; +import InsertQuery from "./queryKinds/dml/insert.js"; +import SelectQuery from "./queryKinds/dml/select.js"; +import Union from "./queryKinds/dml/union.js"; +import UpdateQuery from "./queryKinds/dml/update.js"; import Statement from "./statementMaker.js"; import sqlFlavor from "./types/sqlFlavor.js"; diff --git a/src/types/Join.ts b/src/types/Join.ts index d9f4f4e..4bec8a5 100644 --- a/src/types/Join.ts +++ b/src/types/Join.ts @@ -1,4 +1,4 @@ -import SelectQuery from "../queryKinds/select.js"; +import SelectQuery from "../queryKinds/dml/select.js"; import Statement from "../statementMaker.js"; type joinTypeBase = 'INNER' | 'LEFT' | 'RIGHT' | 'FULL'; From 8901862fb3c0eae6df0a222401750270692a19ba Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 30 Sep 2025 00:40:23 -0300 Subject: [PATCH 14/74] refact/renamed QueryDefinition to DmlQueryDefinition --- src/queryKinds/dml/delete.ts | 4 ++-- src/queryKinds/dml/insert.ts | 4 ++-- src/queryKinds/dml/query.ts | 20 ++++++++++---------- src/queryKinds/dml/select.ts | 4 ++-- src/queryKinds/dml/union.ts | 4 ++-- src/queryKinds/dml/update.ts | 6 +++--- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/queryKinds/dml/delete.ts b/src/queryKinds/dml/delete.ts index c910573..9e91311 100644 --- a/src/queryKinds/dml/delete.ts +++ b/src/queryKinds/dml/delete.ts @@ -3,7 +3,7 @@ import SqlEscaper from "../../sqlEscaper.js"; import Statement from "../../statementMaker.js"; import QueryKind from "../../types/QueryKind.js"; import UsingTable from "../../types/UsingTable.js"; -import QueryDefinition from "./query.js"; +import DmlQueryDefinition from "./query.js"; /** * DeleteQuery class represents a SQL DELETE query. @@ -11,7 +11,7 @@ import QueryDefinition from "./query.js"; * adding USING clauses, WHERE conditions, RETURNING fields, and Common Table Expressions (CTEs). * The class supports cloning, resetting, and building the final SQL query string with parameters. */ -export default class DeleteQuery extends QueryDefinition { +export default class DeleteQuery extends DmlQueryDefinition { /** The table from which records will be deleted. */ private deletingFrom: string; /** An optional alias for the table being deleted from. */ diff --git a/src/queryKinds/dml/insert.ts b/src/queryKinds/dml/insert.ts index 7cb264d..0edf04f 100644 --- a/src/queryKinds/dml/insert.ts +++ b/src/queryKinds/dml/insert.ts @@ -2,7 +2,7 @@ import CteMaker, { Cte } from "../../cteMaker.js"; import SqlEscaper from "../../sqlEscaper.js"; import ColumnValue from "../../types/ColumnValue.js"; import QueryKind from "../../types/QueryKind.js"; -import QueryDefinition from "./query.js"; +import DmlQueryDefinition from "./query.js"; import SelectQuery from "./select.js"; /** @@ -10,7 +10,7 @@ import SelectQuery from "./select.js"; * It supports inserting values directly or from a SELECT query. * It also supports Common Table Expressions (CTEs) and RETURNING clauses. */ -export default class InsertQuery extends QueryDefinition { +export default class InsertQuery extends DmlQueryDefinition { /** The table into which records will be inserted. */ private table: string; /** The column-value pairs to be inserted. */ diff --git a/src/queryKinds/dml/query.ts b/src/queryKinds/dml/query.ts index 118e469..1b76299 100644 --- a/src/queryKinds/dml/query.ts +++ b/src/queryKinds/dml/query.ts @@ -55,13 +55,13 @@ type SchemaType = never; /** - * Abstract class QueryDefinition serves as a blueprint for different types of SQL query definitions. + * Abstract class DmlQueryDefinition serves as a blueprint for different types of SQL query definitions. * It defines the essential methods and properties that any concrete query class must implement. * This includes methods for building the SQL query, executing it, cloning the query definition, * resetting its state, and checking if the query is complete. * The class also provides a method to re-analyze the query for duplicate parameters to optimize parameter usage. */ -export default abstract class QueryDefinition { +export default abstract class DmlQueryDefinition { /** * Converts the query definition to its SQL string representation. @@ -87,7 +87,7 @@ export default abstract class QueryDefinition { /** * Creates a deep copy of the current query definition. */ - public abstract clone(): QueryDefinition; + public abstract clone(): DmlQueryDefinition; /** * Resets the query definition to its initial state. @@ -129,9 +129,9 @@ export default abstract class QueryDefinition { /** * Provides access to the current query definition instance. - * @returns The current QueryDefinition instance. + * @returns The current DmlQueryDefinition instance. */ - public get query(): QueryDefinition { + public get query(): DmlQueryDefinition { return this; } @@ -189,7 +189,7 @@ export default abstract class QueryDefinition { /** * Sets the SQL flavor for escaping identifiers. * @param flavor The SQL flavor to set. - * @returns The current QueryDefinition instance for chaining. + * @returns The current DmlQueryDefinition instance for chaining. */ public sqlFlavor(flavor: sqlFlavor) { this.flavor = flavor; @@ -255,7 +255,7 @@ export default abstract class QueryDefinition { */ public validate any)>( schema: T - ): QueryDefinition> { + ): DmlQueryDefinition> { if ((schema as any).safeParse) { this.isZodSchema = true; } else { @@ -269,7 +269,7 @@ export default abstract class QueryDefinition { /** * Configures options for class-validator validation. * @param config The configuration options for class-validator. - * @returns The current QueryDefinition instance for method chaining. + * @returns The current DmlQueryDefinition instance for method chaining. */ public classValidatorConfig( config: ValidatorOptions @@ -403,12 +403,12 @@ export default abstract class QueryDefinition { values: any[], useDeepEqual: boolean = false ): { text: string; values: any[] } { - return QueryDefinition.reAnalyzeParsedQueryForDuplicateParams(query, values, useDeepEqual); + return DmlQueryDefinition.reAnalyzeParsedQueryForDuplicateParams(query, values, useDeepEqual); } /** * Static method to re-analyze a parsed SQL query for duplicate parameters. - * This method can be used independently of any instance of QueryDefinition. + * This method can be used independently of any instance of DmlQueryDefinition. */ public static reAnalyzeParsedQueryForDuplicateParams( query: string, diff --git a/src/queryKinds/dml/select.ts b/src/queryKinds/dml/select.ts index 81c3b02..b924401 100644 --- a/src/queryKinds/dml/select.ts +++ b/src/queryKinds/dml/select.ts @@ -4,7 +4,7 @@ import Statement from "../../statementMaker.js"; import Join, { isJoinTable } from "../../types/Join.js"; import QueryKind from "../../types/QueryKind.js"; import OrderBy, { isOrderByField } from "../../types/OrderBy.js"; -import QueryDefinition from "./query.js"; +import DmlQueryDefinition from "./query.js"; import Union from "./union.js"; /** @@ -12,7 +12,7 @@ import Union from "./union.js"; * It includes methods to build various parts of the query such as SELECT fields, WHERE conditions, JOINs, ORDER BY, LIMIT, OFFSET, GROUP BY, and CTEs. * The class provides functionality to build the final SQL query string and manage query parameters. */ -export default class SelectQuery extends QueryDefinition { +export default class SelectQuery extends DmlQueryDefinition { /** * The table to select from. */ diff --git a/src/queryKinds/dml/union.ts b/src/queryKinds/dml/union.ts index 4ace133..216073d 100644 --- a/src/queryKinds/dml/union.ts +++ b/src/queryKinds/dml/union.ts @@ -1,7 +1,7 @@ import Statement from "../../statementMaker.js"; import QueryKind from "../../types/QueryKind.js"; import OrderBy from "../../types/OrderBy.js"; -import QueryDefinition from "./query.js"; +import DmlQueryDefinition from "./query.js"; import SelectQuery from "./select.js"; import SqlEscaper from "../../sqlEscaper.js"; @@ -42,7 +42,7 @@ export type SelectQueryWithUnionType = { * It supports adding queries with different union types (UNION or UNION ALL) * and can optionally assign an alias to the resulting union query. */ -export default class Union extends QueryDefinition { +export default class Union extends DmlQueryDefinition { private selectFields: string[] = []; diff --git a/src/queryKinds/dml/update.ts b/src/queryKinds/dml/update.ts index 94c6f3c..6927e37 100644 --- a/src/queryKinds/dml/update.ts +++ b/src/queryKinds/dml/update.ts @@ -4,15 +4,15 @@ import Statement from "../../statementMaker.js"; import Join, { isJoinTable } from "../../types/Join.js"; import QueryKind from "../../types/QueryKind.js"; import SetValue from "../../types/SetValue.js"; -import QueryDefinition from "./query.js"; +import DmlQueryDefinition from "./query.js"; /** * UpdateQuery class is used to build SQL UPDATE queries. * It provides methods to specify the table to update, set values, add joins, and define conditions. * The class supports Common Table Expressions (CTEs) and returning clauses. - * It extends the QueryDefinition class to inherit common query functionalities. + * It extends the DmlQueryDefinition class to inherit common query functionalities. */ -export default class UpdateQuery extends QueryDefinition { +export default class UpdateQuery extends DmlQueryDefinition { /** The table to update. */ private table: string; /** Optional alias for the table. */ From 7ac671c985edd74c26914fcf131508d406b4af8d Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 30 Sep 2025 00:43:20 -0300 Subject: [PATCH 15/74] refact/change query.ts to dmlQueryDefinition.ts --- src/queryKinds/dml/delete.ts | 2 +- .../dml/{query.test.ts => dmlQueryDefinition.test.ts} | 0 src/queryKinds/dml/{query.ts => dmlQueryDefinition.ts} | 0 src/queryKinds/dml/insert.ts | 2 +- src/queryKinds/dml/select.ts | 2 +- src/queryKinds/dml/union.ts | 2 +- src/queryKinds/dml/update.ts | 2 +- 7 files changed, 5 insertions(+), 5 deletions(-) rename src/queryKinds/dml/{query.test.ts => dmlQueryDefinition.test.ts} (100%) rename src/queryKinds/dml/{query.ts => dmlQueryDefinition.ts} (100%) diff --git a/src/queryKinds/dml/delete.ts b/src/queryKinds/dml/delete.ts index 9e91311..a36d60c 100644 --- a/src/queryKinds/dml/delete.ts +++ b/src/queryKinds/dml/delete.ts @@ -3,7 +3,7 @@ import SqlEscaper from "../../sqlEscaper.js"; import Statement from "../../statementMaker.js"; import QueryKind from "../../types/QueryKind.js"; import UsingTable from "../../types/UsingTable.js"; -import DmlQueryDefinition from "./query.js"; +import DmlQueryDefinition from "./dmlQueryDefinition.js"; /** * DeleteQuery class represents a SQL DELETE query. diff --git a/src/queryKinds/dml/query.test.ts b/src/queryKinds/dml/dmlQueryDefinition.test.ts similarity index 100% rename from src/queryKinds/dml/query.test.ts rename to src/queryKinds/dml/dmlQueryDefinition.test.ts diff --git a/src/queryKinds/dml/query.ts b/src/queryKinds/dml/dmlQueryDefinition.ts similarity index 100% rename from src/queryKinds/dml/query.ts rename to src/queryKinds/dml/dmlQueryDefinition.ts diff --git a/src/queryKinds/dml/insert.ts b/src/queryKinds/dml/insert.ts index 0edf04f..fdcab75 100644 --- a/src/queryKinds/dml/insert.ts +++ b/src/queryKinds/dml/insert.ts @@ -2,7 +2,7 @@ import CteMaker, { Cte } from "../../cteMaker.js"; import SqlEscaper from "../../sqlEscaper.js"; import ColumnValue from "../../types/ColumnValue.js"; import QueryKind from "../../types/QueryKind.js"; -import DmlQueryDefinition from "./query.js"; +import DmlQueryDefinition from "./dmlQueryDefinition.js"; import SelectQuery from "./select.js"; /** diff --git a/src/queryKinds/dml/select.ts b/src/queryKinds/dml/select.ts index b924401..7a468cd 100644 --- a/src/queryKinds/dml/select.ts +++ b/src/queryKinds/dml/select.ts @@ -4,7 +4,7 @@ import Statement from "../../statementMaker.js"; import Join, { isJoinTable } from "../../types/Join.js"; import QueryKind from "../../types/QueryKind.js"; import OrderBy, { isOrderByField } from "../../types/OrderBy.js"; -import DmlQueryDefinition from "./query.js"; +import DmlQueryDefinition from "./dmlQueryDefinition.js"; import Union from "./union.js"; /** diff --git a/src/queryKinds/dml/union.ts b/src/queryKinds/dml/union.ts index 216073d..3eb72a7 100644 --- a/src/queryKinds/dml/union.ts +++ b/src/queryKinds/dml/union.ts @@ -1,7 +1,7 @@ import Statement from "../../statementMaker.js"; import QueryKind from "../../types/QueryKind.js"; import OrderBy from "../../types/OrderBy.js"; -import DmlQueryDefinition from "./query.js"; +import DmlQueryDefinition from "./dmlQueryDefinition.js"; import SelectQuery from "./select.js"; import SqlEscaper from "../../sqlEscaper.js"; diff --git a/src/queryKinds/dml/update.ts b/src/queryKinds/dml/update.ts index 6927e37..b140fc3 100644 --- a/src/queryKinds/dml/update.ts +++ b/src/queryKinds/dml/update.ts @@ -4,7 +4,7 @@ import Statement from "../../statementMaker.js"; import Join, { isJoinTable } from "../../types/Join.js"; import QueryKind from "../../types/QueryKind.js"; import SetValue from "../../types/SetValue.js"; -import DmlQueryDefinition from "./query.js"; +import DmlQueryDefinition from "./dmlQueryDefinition.js"; /** * UpdateQuery class is used to build SQL UPDATE queries. From fc1d68b88b6fb02aaf6ed83e2e328307a83d93d6 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 30 Sep 2025 00:44:51 -0300 Subject: [PATCH 16/74] fix/change query.js to dmlQueryDefinition.js --- src/cteMaker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cteMaker.ts b/src/cteMaker.ts index f448273..19efa72 100644 --- a/src/cteMaker.ts +++ b/src/cteMaker.ts @@ -1,4 +1,4 @@ -import QueryDefinition from "./queryKinds/dml/query.js"; +import QueryDefinition from "./queryKinds/dml/dmlQueryDefinition.js"; import SelectQuery from "./queryKinds/dml/select.js"; /** From 632adada15b1625c74b1c0c7787f570037dae994 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 30 Sep 2025 01:41:11 -0300 Subject: [PATCH 17/74] ongoing/feat/started adding ddl queries --- src/queryKinds/ddl/ddlQueryDefinition.ts | 177 +++++++++++++++++++++++ src/queryKinds/ddl/index.ts | 2 + 2 files changed, 179 insertions(+) create mode 100644 src/queryKinds/ddl/ddlQueryDefinition.ts create mode 100644 src/queryKinds/ddl/index.ts diff --git a/src/queryKinds/ddl/ddlQueryDefinition.ts b/src/queryKinds/ddl/ddlQueryDefinition.ts new file mode 100644 index 0000000..d42486b --- /dev/null +++ b/src/queryKinds/ddl/ddlQueryDefinition.ts @@ -0,0 +1,177 @@ +import QueryKind from "../../types/QueryKind"; + +/** + * An array of function names that can be used to execute SQL queries. + * These functions are commonly found in database client libraries. + */ +const functionNames = ['execute', 'query', 'run', 'all', 'get'] as const; + +/** + * FunctionDeclaration type defines the signature for functions that execute SQL queries. + * It takes a query string and an array of parameters, and returns a promise that resolves + * with any result. + */ +type FunctionDeclaration = (query: string, params?: any[]) => Promise; + +/** + * QueryExecutorObject interface defines the structure for an object that can execute SQL queries. + * It includes optional methods for executing queries in different ways, as well as an optional manager property. + */ +interface QueryExecutorObject { + execute?: FunctionDeclaration; + query?: FunctionDeclaration; + run?: FunctionDeclaration; + all?: FunctionDeclaration; + get?: FunctionDeclaration; + manager?: QueryExecutor; +} + +/** + * QueryExecutor type can be either a QueryExecutorObject or a function that executes a query. + */ +type QueryExecutor = + QueryExecutorObject + | FunctionDeclaration; + +/** + * Abstract class DdlQueryDefinition serves as a blueprint for defining DDL (Data Definition Language) query structures. + * It is intended to be extended by specific DDL query classes such as CreateTableQuery, AlterTableQuery, and DropTableQuery. + * This class will provide common properties and methods that are shared among all DDL query types and also some abstract methods + * that must be implemented by the subclasses to ensure they adhere to a consistent interface for building DDL queries. + */ +export default abstract class DdlQueryDefinition { + /** The name of the table involved in the DDL operation. */ + protected tableName: string = ''; + + /** The built DDL query string, initialized to null. */ + protected builtQuery: string | null = null; + + /** + * Sets the name of the table for the DDL operation. + * @param name - The name of the table. + * @returns The current instance for method chaining. + */ + public table(name: string): this { + this.tableName = name; + return this; + } + + /** + * Checks if the DDL query has been built. + * @returns True if the query has been built, false otherwise. + */ + public isDone(): boolean { + return this.builtQuery !== null; + } + + /** + * Abstract method to build the DDL query string. + * This method must be implemented by subclasses to generate the appropriate SQL statement. + * @param deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). + * @returns The constructed DDL query string. + */ + public abstract build(deepAnalysis?: boolean): string; + + /** + * Builds an EXPLAIN query for the DDL operation. + * This method prefixes the built DDL query with "EXPLAIN". + * @param deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). + * @returns The constructed EXPLAIN query string. + */ + public buildExplain(deepAnalysis?: boolean): string { + return `EXPLAIN ${this.build(deepAnalysis)}`; + } + + /** + * Builds an EXPLAIN ANALYZE query for the DDL operation. + * This method prefixes the built DDL query with "EXPLAIN ANALYZE". + * @param deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). + * @returns The constructed EXPLAIN ANALYZE query string. + */ + public buildExplainAnalyze(deepAnalysis?: boolean): string { + return `EXPLAIN ANALYZE ${this.build(deepAnalysis)}`; + } + + /** + * Utility method to add indentation to each line of a given string. + * This is useful for formatting multi-line SQL queries for better readability. + * @param str - The input string to be indented. + * @param spaces - The number of spaces to indent each line (default is 0). + * @returns The indented string. + */ + protected spaceLines(str: string, spaces: number = 0): string { + const space = ' '.repeat(spaces); + return str.split('\n').map(line => space + line).join('\n'); + } + + /** + * Abstract method to clone the current DDL query definition instance. + * This method must be implemented by subclasses to return a new instance + * that is a copy of the current instance. + * @returns A new instance of the DDL query definition. + */ + public abstract clone(): DdlQueryDefinition; + + /** + * Abstract method to reset the state of the DDL query definition. + * This method must be implemented by subclasses to clear any set properties + * and return the instance to its initial state. + * @returns The current instance for method chaining. + */ + public abstract reset(): this; + + /** + * Abstract method to convert the DDL query definition to its SQL string representation. + * This method must be implemented by subclasses to return the SQL string + * that represents the DDL operation defined by the instance. + * @returns The SQL string representation of the DDL query. + */ + public abstract toSQL(): string; + + /** + * Abstract getter to retrieve the kind of DDL query. + * This property must be implemented by subclasses to return the specific + * type of DDL operation (e.g., 'CREATE', 'ALTER', 'DROP'). + */ + public abstract get kind(): QueryKind; + + /** + * Executes the built SQL query using the provided query executor. + * The query executor can be a function or an object with methods to execute the query. + * The optional noManager parameter can be used to bypass the manager property if present. + * @param queryExecutor The executor to run the SQL query. + * @param noManager If true, bypasses the manager property of the executor object. + * @returns A promise that resolves when the query execution is complete. + * @throws An error if the provided query executor is invalid. + */ + public async execute( + queryExecutor: QueryExecutor, + noManager: boolean = false + ): Promise { + if (typeof queryExecutor === 'function') { + const builtQuery = this.build(); + await queryExecutor(builtQuery); + return; + } + + if (!noManager && 'manager' in queryExecutor && typeof queryExecutor?.manager === 'object') { + for (const functionName of functionNames) { + if (typeof queryExecutor.manager[functionName] === 'function') { + const builtQuery = this.build(); + await queryExecutor.manager[functionName]!(builtQuery); + return; + } + } + } else if(typeof queryExecutor === 'object') { + for (const functionName of functionNames) { + if (typeof queryExecutor[functionName] === 'function') { + const builtQuery = this.build(); + await queryExecutor[functionName]!(builtQuery); + return; + } + } + } + + throw new Error('Invalid query executor provided.'); + } +} diff --git a/src/queryKinds/ddl/index.ts b/src/queryKinds/ddl/index.ts new file mode 100644 index 0000000..c167ce0 --- /dev/null +++ b/src/queryKinds/ddl/index.ts @@ -0,0 +1,2 @@ +export * from './ddlQueryDefinition.js'; +export { default as DdlQueryDefinition } from './ddlQueryDefinition.js'; From 5a1e034e3cb4fae763c8366ba10c963cb9d53ec7 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 30 Sep 2025 01:41:53 -0300 Subject: [PATCH 18/74] feat/added exports for lots of things like dml queries for example --- src/index.ts | 5 ++--- src/queryKinds/dml/dmlQueryDefinition.ts | 7 +++++++ src/queryKinds/dml/index.ts | 12 ++++++++++++ src/types/QueryKind.ts | 5 ++++- src/types/index.ts | 13 +++++++++++++ 5 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 src/queryKinds/dml/index.ts create mode 100644 src/types/index.ts diff --git a/src/index.ts b/src/index.ts index 12a1620..8f658cf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,8 @@ import Query from "./queryMaker.js"; import Statement from "./statementMaker.js"; -import CteMaker from "./cteMaker.js"; +export * from './cteMaker.js' export { Query, - Statement, - CteMaker + Statement } diff --git a/src/queryKinds/dml/dmlQueryDefinition.ts b/src/queryKinds/dml/dmlQueryDefinition.ts index 1b76299..55f6fa1 100644 --- a/src/queryKinds/dml/dmlQueryDefinition.ts +++ b/src/queryKinds/dml/dmlQueryDefinition.ts @@ -135,6 +135,13 @@ export default abstract class DmlQueryDefinition { return this; } + /** + * Utility method to add spaces to each line of a given string. + * This is useful for formatting SQL queries for better readability. + * @param str The string to format. + * @param spaces The number of spaces to add to the beginning of each line (default is 0). + * @returns The formatted string with added spaces. + */ protected spaceLines(str: string, spaces: number = 0): string { const space = ' '.repeat(spaces); return str.split('\n').map(line => space + line).join('\n'); diff --git a/src/queryKinds/dml/index.ts b/src/queryKinds/dml/index.ts new file mode 100644 index 0000000..a1b331b --- /dev/null +++ b/src/queryKinds/dml/index.ts @@ -0,0 +1,12 @@ +export * from './delete.js'; +export { default as DeleteQuery } from './delete.js'; +export * from './insert.js'; +export { default as InsertQuery } from './insert.js'; +export * from './select.js'; +export { default as SelectQuery } from './select.js'; +export * from './update.js'; +export { default as UpdateQuery } from './update.js'; +export * from './union.js'; +export { default as Union } from './union.js'; +export * from './dmlQueryDefinition.js'; +export { default as DmlQueryDefinition } from './dmlQueryDefinition.js'; diff --git a/src/types/QueryKind.ts b/src/types/QueryKind.ts index 8e9b584..d890bee 100644 --- a/src/types/QueryKind.ts +++ b/src/types/QueryKind.ts @@ -5,7 +5,10 @@ enum QueryKind { INSERT = "INSERT", UPDATE = "UPDATE", DELETE = "DELETE", - UNION = "UNION" + UNION = "UNION", + CREATE_TABLE = "CREATE_TABLE", + DROP_TABLE = "DROP_TABLE", + ALTER_TABLE = "ALTER_TABLE", } export default QueryKind; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..81cda64 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,13 @@ +export type { default as ColumnValue } from './ColumnValue.js'; +export * from './Join.js'; +export type { default as Join } from './Join.js'; +export * from './OrderBy.js'; +export type { default as OrderBy } from './OrderBy.js'; +export * from './QueryKind.js'; +export type { default as QueryKind } from './QueryKind.js'; +export * from './SetValue.js'; +export type { default as SetValue } from './SetValue.js'; +export * from './sqlFlavor.js'; +export { default as sqlFlavor } from './sqlFlavor.js'; +export * from './UsingTable.js'; +export type { default as UsingTable } from './UsingTable.js'; From 938bfc3a2b728d614fb780617ad654a6c3ee319f Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 30 Sep 2025 01:42:31 -0300 Subject: [PATCH 19/74] feat/added other entries for exporting and also added all index.ts to vitest exclude list --- tsup.config.ts | 7 ++++++- vitest.config.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tsup.config.ts b/tsup.config.ts index 2f4cc67..7882013 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,7 +1,12 @@ import { defineConfig } from 'tsup' export default defineConfig({ - entry: ['src/index.ts'], + entry: [ + 'src/index.ts', + 'src/types/index.ts', + 'src/queryKinds/dml/index.ts', + 'src/queryKinds/ddl/index.ts' + ], target: ['esnext', 'node21'], format: ['cjs', 'esm'], ignoreWatch: ['**/*.test.ts', '**/*.spec.ts'], diff --git a/vitest.config.ts b/vitest.config.ts index 1fd82d2..3dd1f83 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ '**/*.spec.ts', 'vitest.config.ts', '**/getOptionalPackages.ts', - 'src/index.ts', + '**/index.ts', ], include: ['src/**/*.ts'] }, From e5fbfe0f49714464d472302adc6835ffac5531ff Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 30 Sep 2025 01:50:03 -0300 Subject: [PATCH 20/74] Started adding more query kinds/types as from the issue #65 From 9189b9c172548fdac2a091abb5032b2344da7e29 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 30 Sep 2025 01:54:48 -0300 Subject: [PATCH 21/74] fix/added default export to CteMaker --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 8f658cf..9bb1d29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import Query from "./queryMaker.js"; import Statement from "./statementMaker.js"; export * from './cteMaker.js' +export { default as CteMaker } from './cteMaker.js' export { Query, From fca7c3890df14b5331f763d5b42e2eb26be1b994 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 30 Sep 2025 02:02:33 -0300 Subject: [PATCH 22/74] Fixed some bugs in PR #71 that already presented some bugs found on issue #66, already added some fixes for these. From b04c5c7a5ffb20bcc2ff4e75b2600995c76911dc Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 30 Sep 2025 02:54:05 -0300 Subject: [PATCH 23/74] feat/added error handling from when select queries in unions have different lengths --- src/queryKinds/dml/union.test.ts | 25 +++++++++++++++++ src/queryKinds/dml/union.ts | 46 +++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/queryKinds/dml/union.test.ts b/src/queryKinds/dml/union.test.ts index 8e62f35..2eae0d3 100644 --- a/src/queryKinds/dml/union.test.ts +++ b/src/queryKinds/dml/union.test.ts @@ -462,4 +462,29 @@ describe("Union Query", () => { expect(unionQuery.values).toEqual(['value1', 'value2']); }); + it('should throw if selects have different selected lengths', () => { + const select1 = Query.select + .from("table1") + .select(["column1", "column2"]) + .where("column1 = ?", "value1"); + + const select2 = Query.select + .from("table2") + .select(["column1", "column2", "column3"]) + .where("column2 = ?", "value2"); + + const union = new Union() + .addManyOfType([select1, select2], 'union all') + .as('union_table'); + + expect(() => union.build()).toThrowError('All SELECT queries must have the same number of fields. Query at index 1 differs.'); + expect(() => union.rawUnion()).toThrowError('All SELECT queries must have the same number of fields. Query at index 1 differs.'); + + try { + union.build(); + } catch (e: any) { + expect(e.cause?.selectQuery).toStrictEqual(select2); + } + }); + }); diff --git a/src/queryKinds/dml/union.ts b/src/queryKinds/dml/union.ts index 3eb72a7..8c224c4 100644 --- a/src/queryKinds/dml/union.ts +++ b/src/queryKinds/dml/union.ts @@ -43,7 +43,7 @@ export type SelectQueryWithUnionType = { * and can optionally assign an alias to the resulting union query. */ export default class Union extends DmlQueryDefinition { - + /** The selected fields for the union query */ private selectFields: string[] = []; /** Needed alias for the union query */ @@ -67,15 +67,50 @@ export default class Union extends DmlQueryDefinition { /** Having statement for the union query */ private havingStatement: Statement | null = null; + /** + * Checks if all added SELECT queries have the same number of fields. + * This is important for ensuring that the UNION operation is valid. + * @returns True if all SELECT queries have the same number of fields, + * or the index of the first query that differs in field count. + */ + private allSelectsHaveSameNumberOfFields(): number | null { + if (this.selectQueries.length === 0) return null; + + const selects = this.selectQueries.map(sq => sq.query); + + const firstSelectFieldCount = selects[0]?.columns.length || 0; + + let indexThatDiffers: number | null = null; + const result = selects.every((select, i) => { + if (select.columns.length !== firstSelectFieldCount) { + indexThatDiffers = i; + return false; + } else return true; + }); + + return result ? null : indexThatDiffers; + } + /** * Make the union without selecting from it * Useful when the raw union is needed as a subquery + * @throws Error if no SELECT queries have been added to the union + * @throws Error if the SELECT queries do not have the same number of fields * @returns An object containing the raw SQL text of the union and its parameter values. */ public rawUnion(): { text: string; values: any[] } { if (this.selectQueries.length === 0) { throw new Error('No SELECT queries added to the UNION.'); } + + const differingIndex = this.allSelectsHaveSameNumberOfFields(); + if (differingIndex !== null) { + console.log('This is erroring out') + throw new Error( + `All SELECT queries must have the same number of fields. Query at index ${differingIndex} differs.`, + { cause: { selectQuery: this.selectQueries[differingIndex]?.query } } + ); + } let unionItself: string = ''; const values: any[] = []; @@ -379,6 +414,15 @@ export default class Union extends DmlQueryDefinition { throw new Error('No SELECT queries added to the UNION.'); } + const differingIndex = this.allSelectsHaveSameNumberOfFields(); + if (differingIndex !== null) { + console.log('This is erroring out') + throw new Error( + `All SELECT queries must have the same number of fields. Query at index ${differingIndex} differs.`, + { cause: { selectQuery: this.selectQueries[differingIndex]?.query } } + ); + } + let unionItself: string = ''; const values: any[] = []; From 06c1f703d21848bae1e5883599e34a6c9e92e34b Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Wed, 1 Oct 2025 01:14:26 -0300 Subject: [PATCH 24/74] fix/updated package-lock.json after version bump --- package-lock.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 65a7eab..8040297 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sqm", - "version": "0.6.0", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sqm", - "version": "0.6.0", + "version": "0.7.0", "license": "Apache-2.0", "dependencies": { "ts-pattern": "^5.8.0" @@ -1027,6 +1027,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.24" @@ -2066,6 +2067,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -3032,6 +3034,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3081,6 +3084,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3542,6 +3546,7 @@ "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -3790,6 +3795,7 @@ "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -3828,6 +3834,7 @@ "integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -3926,6 +3933,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", From f7f8157c46d8dfb41c00e45ab3bd26b8c63ef9fc Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Wed, 1 Oct 2025 01:14:51 -0300 Subject: [PATCH 25/74] Test commit for NPM publishing From 2b5e5ff212d17fca6cb0f228f5ce4e1d21bbf3d0 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Wed, 1 Oct 2025 01:40:19 -0300 Subject: [PATCH 26/74] feat/updated publish-npm.yml from npm to pnpm, begin testing in dev branch --- .github/workflows/publish-npm.yml | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index ca779c4..9373c73 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -25,14 +25,32 @@ jobs: node-version: '24' registry-url: 'https://registry.npmjs.org' + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: latest + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Cache pnpm store + uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + - name: Install dependencies - run: npm ci + run: pnpm install - name: Run tests - run: npm test + run: pnpm test - name: Build project - run: npm run build:prod + run: pnpm build:prod - name: Determine version and tag id: version @@ -77,11 +95,11 @@ jobs: - name: Update package version run: | - npm version ${{ steps.version.outputs.version }} --no-git-tag-version --allow-same-version + pnpm version ${{ steps.version.outputs.version }} --no-git-tag-version --allow-same-version - name: Publish to NPM run: | - npm publish --tag ${{ steps.version.outputs.npm_tag }} + pnpm publish --tag ${{ steps.version.outputs.npm_tag }} env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 273d39204f5f03b17972bb4b0ff9636c061ac153 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Wed, 1 Oct 2025 01:42:34 -0300 Subject: [PATCH 27/74] fix/removed git checks on publish --- .github/workflows/publish-npm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 9373c73..f080c5c 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -99,7 +99,7 @@ jobs: - name: Publish to NPM run: | - pnpm publish --tag ${{ steps.version.outputs.npm_tag }} + pnpm publish --tag ${{ steps.version.outputs.npm_tag }} --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From add892350a96c37a67b66dabaf420f57a9d5b3be Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Wed, 1 Oct 2025 01:46:05 -0300 Subject: [PATCH 28/74] test/test pnpm cache From bc0e8377820e672ea45611199dbff0c2f22c1021 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Wed, 1 Oct 2025 01:53:24 -0300 Subject: [PATCH 29/74] feat/added artifacts to releases --- .github/workflows/publish-npm.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index f080c5c..126e30c 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -97,6 +97,14 @@ jobs: run: | pnpm version ${{ steps.version.outputs.version }} --no-git-tag-version --allow-same-version + - name: Create distribution archive + run: | + # Create a compressed archive of the dist directory + tar -czf dist-${{ steps.version.outputs.version }}.tar.gz dist/ + + # Create a zip archive as well for Windows users + zip -r dist-${{ steps.version.outputs.version }}.zip dist/ + - name: Publish to NPM run: | pnpm publish --tag ${{ steps.version.outputs.npm_tag }} --no-git-checks @@ -110,6 +118,9 @@ jobs: tag: v${{ steps.version.outputs.version }} name: Release v${{ steps.version.outputs.version }} commit: ${{ github.sha }} + artifacts: | + dist-${{ steps.version.outputs.version }}.tar.gz + dist-${{ steps.version.outputs.version }}.zip body: | ## Changes @@ -120,6 +131,10 @@ jobs: npm install sqm@latest ``` + Download the distribution archives: + - dist-${{ steps.version.outputs.version }}.tar.gz `compressed tar.gz archive` + - dist-${{ steps.version.outputs.version }}.zip `zip archive` + For full changelog, see the commit history. draft: false prerelease: false @@ -132,6 +147,9 @@ jobs: tag: v${{ steps.version.outputs.version }} name: ${{ steps.version.outputs.branch }} v${{ steps.version.outputs.version }} commit: ${{ github.sha }} + artifacts: | + dist-${{ steps.version.outputs.version }}.tar.gz + dist-${{ steps.version.outputs.version }}.zip body: | ## ${{ steps.version.outputs.branch }} Release @@ -142,6 +160,10 @@ jobs: npm install sqm@${{ steps.version.outputs.npm_tag }} ``` + Download the distribution archives: + - dist-${{ steps.version.outputs.version }}.tar.gz `compressed tar.gz archive` + - dist-${{ steps.version.outputs.version }}.zip `zip archive` + **⚠️ Warning**: This is a pre-release version and may contain bugs or incomplete features. draft: false prerelease: true From b213fff66e00e8e698016295124990ca7c925412 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Wed, 1 Oct 2025 02:00:30 -0300 Subject: [PATCH 30/74] feat/updated workflow from dev to next --- .github/workflows/publish-npm.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 126e30c..89a4211 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -6,7 +6,7 @@ on: - main - rc - beta - - dev + - next workflow_dispatch: jobs: @@ -64,7 +64,7 @@ jobs: NPM_TAG="latest" VERSION=$PACKAGE_VERSION ;; - "rc"|"beta"|"dev") + "rc"|"beta"|"next") IDENTIFIER=$BRANCH_NAME NPM_TAG=$BRANCH_NAME @@ -140,7 +140,7 @@ jobs: prerelease: false generateReleaseNotes: true - - name: Create Pre-release (rc, beta, dev branches) + - name: Create Pre-release (rc, beta, next branches) if: github.ref != 'refs/heads/main' uses: ncipollo/release-action@v1 with: From 23df106b8363affe73b5f2a559558555d588f8c7 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Wed, 1 Oct 2025 02:01:55 -0300 Subject: [PATCH 31/74] feat/updated name dev to next --- UPDATE-STRATEGY.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/UPDATE-STRATEGY.md b/UPDATE-STRATEGY.md index 222cc04..19bf5c6 100644 --- a/UPDATE-STRATEGY.md +++ b/UPDATE-STRATEGY.md @@ -4,34 +4,34 @@ Fisrt I'll clarify branch names: - `main` is the production branch, it should always be stable and deployable. - `rc` is the release candidate branch, it is used for testing new features before they are merged into `main`. - `beta` is the beta branch, it is used for testing new features before they are merged into `rc`. -- `dev` is the development branch, it is used for active development and may be unstable +- `next` is the development branch, it is used for active development and may be unstable ## Update Strategy 1. **Development Phase**: - - All new features and bug fixes are developed in the `dev` branch. - - Regular commits and pushes to `dev` to ensure changes are tracked. + - All new features and bug fixes are developed in the `next` branch. + - Regular commits and pushes to `next` to ensure changes are tracked. - Once a feature or fix is complete, it is merged into the `beta` branch for initial testing. 2. **Beta Testing Phase**: - The `beta` branch is published into npm with the `beta` tag. - Beta testers and early adopters can install the beta version using `npm install sqm@beta`. - - Feedback from beta testers is collected and any issues are addressed in the `dev` branch. + - Feedback from beta testers is collected and any issues are addressed in the `next` branch. - Once the beta version is stable and all critical issues are resolved, it is merged into the `rc` branch. 3. **Release Candidate Phase**: - The `rc` branch is published into npm with the `rc` tag. - Further testing is conducted to ensure stability and performance. - - Any final bugs or issues are fixed in the `dev` branch and merged into `rc`. + - Any final bugs or issues are fixed in the `next` branch and merged into `rc`. - Once the release candidate is deemed stable, it is merged into the `main` branch. 4. **Production Release Phase**: - The `main` branch is published into npm with the `latest` tag. - Users can install the stable version using `npm install sqm`. - Post-release monitoring is conducted to ensure the release is functioning as expected. - - Any critical issues found in production are addressed in the `dev` branch and the cycle repeats. + - Any critical issues found in production are addressed in the `next` branch and the cycle repeats. ## Hotfixes In case of critical bugs in the `main` branch: 1. Create a hotfix branch from `main`. 2. Implement the fix and test it thoroughly. -3. Merge the hotfix branch back into `main`, `rc`, and `dev` branches. +3. Merge the hotfix branch back into `main`, `rc`, and `next` branches. 4. Publish the updated `main` branch and other branches as necessary. 5. Communicate the hotfix to users if necessary. From cac5da976904f521777af22491d5f278290d01a3 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Wed, 1 Oct 2025 22:07:00 -0300 Subject: [PATCH 32/74] fix/fix git url --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a553ee0..609f690 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ ], "repository": { "type": "git", - "url": "https://github.com/NickRMD/queryMaker.git" + "url": "git+https://github.com/NickRMD/queryMaker.git" }, "homepage": "https://github.com/NickRMD/queryMaker#readme", "bugs": { From 5dd7fe42b9a29361641b4b03461f229e05b6ef2b Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Thu, 2 Oct 2025 02:55:07 -0300 Subject: [PATCH 33/74] feat/added columnTypes, columnType class and columns class with a lot of built-in types --- src/types/Column.ts | 311 +++++++++++++++++++++++++++++++++++++++ src/types/ColumnTypes.ts | 181 +++++++++++++++++++++++ src/types/index.ts | 3 + 3 files changed, 495 insertions(+) create mode 100644 src/types/Column.ts create mode 100644 src/types/ColumnTypes.ts diff --git a/src/types/Column.ts b/src/types/Column.ts new file mode 100644 index 0000000..069a5bf --- /dev/null +++ b/src/types/Column.ts @@ -0,0 +1,311 @@ +import { ColumnTypes } from "./ColumnTypes.js"; + +/** + * Class representing a column type. + * It allows setting the type and adding properties, and can build a string representation of the column type. + */ +export class ColumnType { + /** The name of the column type. */ + private typeName: ColumnTypes | null; + /** The properties associated with the column type. */ + private properties: string[] = []; + + constructor( + /** The name of the column type. */ + typeName: ColumnTypes | null = null, + /** The properties associated with the column type. */ + properties: (string | { toString(): string })[] = [] + ) { + this.typeName = typeName; + this.properties = properties.map(prop => prop.toString()); + } + + /** + * Sets the type of the column. + * @param typeName - The name of the column type. + * @returns The current instance for method chaining. + */ + public setType(typeName: ColumnTypes): this { + this.typeName = typeName; + return this; + } + + /** + * Adds a property to the column type. + * @param property - The property to add. + * @returns The current instance for method chaining. + */ + public addProperty(property: string): this { + this.properties.push(property); + return this; + } + + /** + * Builds the string representation of the column type. + * @returns The string representation of the column type. + * @throws Error if the type name is not set. + */ + public build(): string { + if (!this.typeName) { + throw new Error("Type name is not set."); + } + + if (this.properties.length > 0) { + return `${this.typeName}(${this.properties.join(', ')})`; + } + return this.typeName; + } + + /** + * Returns the string representation of the column type. + * @returns The string representation of the column type. + * @throws Error if the type name is not set. + */ + public toString(): string { + return this.build(); + } + +} + +/** + * Class representing a database column with various attributes and constraints. + * It allows setting the column name, type, nullability, primary key status, uniqueness, + * default value, and check conditions. The class can build a string representation of the column definition. + */ +export class ColumnDefinition { + /** The name of the column. */ + private name: string | null = null; + /** The type of the column. */ + private type: ColumnType | null = null; + /** Indicates if the column is nullable. */ + private isNullable: boolean = true; + /** Indicates if the column is a primary key. */ + private isPrimaryKey: boolean = false; + /** Indicates if the column has a unique constraint. */ + private isUnique: boolean = false; + /** The default value for the column, if any. */ + private defaultValue?: string; + /** The check condition for the column, if any. */ + private checkCondition?: string; + + constructor( + name: string | null = null, + type: ColumnType | string | null = null + ) { + this.name = name ?? null; + if (type) { + if (typeof type === 'string') { + this.type = new ColumnType(type as ColumnTypes); + } else { + this.type = type; + } + } else { + this.type = null; + } + } + + /** + * Sets the name of the column. + * @param name - The name of the column. + * @returns The current instance for method chaining. + */ + public setName(name: string): this { + this.name = name; + return this; + } + + /** + * Sets the type of the column. + * @param type - The type of the column, either as a ColumnType instance or a string. + * @returns The current instance for method chaining. + */ + public setType(type: ColumnType | string): this { + if (typeof type === 'string') { + this.type = new ColumnType(type as ColumnTypes); + } else { + this.type = type; + } + return this; + } + + /** + * Marks the column as nullable. + * @returns The current instance for method chaining. + */ + public null(): this { + this.isNullable = true; + return this; + } + + /** + * Marks the column as not nullable. + * @returns The current instance for method chaining. + */ + public notNull(): this { + this.isNullable = false; + return this; + } + + /** + * Marks the column as a primary key. + * This also sets the column as not nullable. + * @returns The current instance for method chaining. + */ + public primaryKey(): this { + this.isPrimaryKey = true; + this.isNullable = false; // Primary key columns cannot be null + return this; + } + + /** + * Marks the column as unique. + * @returns The current instance for method chaining. + */ + public unique(): this { + this.isUnique = true; + return this; + } + + /** + * Sets the default value for the column. + * @param value - The default value for the column. + * @returns The current instance for method chaining. + */ + public default(value: string | { toString(): string }): this { + this.defaultValue = value.toString(); + return this; + } + + /** + * Sets a check condition for the column. + * @param condition - The check condition for the column. + * @returns The current instance for method chaining. + */ + public check(condition: string): this { + this.checkCondition = condition; + return this; + } + + /** + * Builds the string representation of the column definition. + * @returns The string representation of the column definition. + * @throws Error if the column name or type is not set. + */ + public build(forAdding: boolean = false): string { + if (!this.name || !this.type) { + let errorMsg = !this.name && !this.type + ? "Column name and type" + : !this.name + ? "Column name" + : "Column type"; + throw new Error(`${errorMsg} ${!this.name && !this.type ? "are" : "is"} not set.`); + } + + let parts: string[] = []; + parts.push(this.name); + parts.push(this.type.toString()); + + if (this.isPrimaryKey && !forAdding) { + parts.push("PRIMARY KEY"); + } else { + if (this.isUnique && !forAdding) { + parts.push("UNIQUE"); + } + if (!this.isNullable && !forAdding) { + parts.push("NOT NULL"); + } + } + + if (this.defaultValue !== undefined) { + parts.push(`DEFAULT ${this.defaultValue}`); + } + + if (this.checkCondition !== undefined && !forAdding) { + parts.push(`CHECK (${this.checkCondition})`); + } + + return parts.join(' '); + } + + public buildToAdd(tableName: string): string[] { + let addColumn = `ALTER TABLE ${tableName} ADD COLUMN ${this.build(true)}`; + let addColumnNullability = this.isNullable + ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} DROP NOT NULL` + : `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET NOT NULL`; + let addColumnDefault = this.defaultValue !== undefined + ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET DEFAULT ${this.defaultValue}` + : ''; + let addColumnCheck = this.checkCondition !== undefined + ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_check CHECK (${this.checkCondition})` + : ''; + let addColumnPrimaryKey = this.isPrimaryKey + ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_pkey PRIMARY KEY (${this.name})` + : ''; + let addColumnUnique = this.isUnique + ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_unique UNIQUE (${this.name})` + : ''; + + let additions = [ + addColumn, + addColumnNullability, + addColumnDefault, + addColumnCheck, + addColumnPrimaryKey, + addColumnUnique + ].filter(part => part !== ''); + + return additions; + } + + public buildToAlter( + tableName: string, + previousName?: string + ): string[] { + let alterColumnName = previousName !== this.name ? `ALTER TABLE ${tableName} RENAME COLUMN ${previousName} TO ${this.name}` : ''; + let alterColumnType = `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} TYPE ${this.type?.toString()}`; + let alterColumnNullability = this.isNullable + ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} DROP NOT NULL` + : `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET NOT NULL`; + let alterColumnDefault = this.defaultValue !== undefined + ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET DEFAULT ${this.defaultValue}` + : `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} DROP DEFAULT`; + let alterColumnCheck = this.checkCondition !== undefined + ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_check CHECK (${this.checkCondition})` + : ''; + let alterColumnPrimaryKey = this.isPrimaryKey + ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_pkey PRIMARY KEY (${this.name})` + : ''; + let alterColumnUnique = this.isUnique + ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_unique UNIQUE (${this.name})` + : ''; + + let alterations = [ + alterColumnName, + alterColumnType, + alterColumnNullability, + alterColumnDefault, + alterColumnCheck, + alterColumnPrimaryKey, + alterColumnUnique + ].filter(part => part !== ''); + + return alterations; + } + + /** + * Returns the string representation of the column definition. + * @returns The string representation of the column definition. + * @throws Error if the column name or type is not set. + */ + public toString(): string { + return this.build(); + } + +} + +export default function Column( + name: string | null = null, + type: ColumnType | string | null = null +): ColumnDefinition { + return new ColumnDefinition(name, type); +} diff --git a/src/types/ColumnTypes.ts b/src/types/ColumnTypes.ts new file mode 100644 index 0000000..56c6188 --- /dev/null +++ b/src/types/ColumnTypes.ts @@ -0,0 +1,181 @@ +import { ColumnType } from "./Column.js"; + +/** + * Contain all the possible column types in PostgreSQL + * Reference: https://www.postgresql.org/docs/current/datatype.html + */ +export enum ColumnTypesEnum { + // Numeric Types + SMALLINT = "smallint", + INTEGER = "integer", + BIGINT = "bigint", + DECIMAL = "decimal", + NUMERIC = "numeric", + REAL = "real", + DOUBLE_PRECISION = "double precision", + SERIAL = "serial", + BIGSERIAL = "bigserial", + SMALLSERIAL = "smallserial", + MONEY = "money", + + // Character Types + VARCHAR = "character varying", + CHAR = "character", + TEXT = "text", + + // Binary + BYTEA = "bytea", + + // Date/Time + TIMESTAMP = "timestamp", + TIMESTAMPTZ = "timestamptz", + DATE = "date", + TIME = "time", + TIMETZ = "timetz", + INTERVAL = "interval", + + // Boolean + BOOLEAN = "boolean", + + // Enumerated + ENUM = "enum", + + // Geometric + POINT = "point", + LINE = "line", + LSEG = "lseg", + BOX = "box", + PATH = "path", + POLYGON = "polygon", + CIRCLE = "circle", + + // Network + CIDR = "cidr", + INET = "inet", + MACADDR = "macaddr", + MACADDR8 = "macaddr8", + + // Bit Strings + BIT = "bit", + VARBIT = "bit varying", + + // Text Search + TSVECTOR = "tsvector", + TSQUERY = "tsquery", + + // UUID + UUID = "uuid", + + // JSON + JSON = "json", + JSONB = "jsonb", + + // XML + XML = "xml", + + // Arrays + ARRAY = "array", + + // Composite + COMPOSITE = "composite", + + // Range Types + INT4RANGE = "int4range", + INT8RANGE = "int8range", + NUMRANGE = "numrange", + TSRANGE = "tsrange", + TSTZRANGE = "tstzrange", + DATERANGE = "daterange", + + // Special + OID = "oid", + PG_LSN = "pg_lsn", + TXID_SNAPSHOT = "txid_snapshot", + REGPROC = "regproc", + REGPROCEDURE = "regprocedure", + REGOPER = "regoper", + REGOPERATOR = "regoperator", + REGCLASS = "regclass", + REGTYPE = "regtype" +} + +/** + * Union type for ColumnTypesEnum keys + */ +export type ColumnTypesUnion = keyof typeof ColumnTypesEnum; + +/** + * Union type for ColumnTypesEnum values and keys + */ +export type ColumnTypes = ColumnTypesEnum | ColumnTypesUnion; + +/** + * Create a ColumnType of type VARCHAR with specified length + * @param length - The maximum length of the VARCHAR column + * @returns A ColumnType instance representing a VARCHAR column with the specified length + */ +function Varchar(length: number) { + return new ColumnType('VARCHAR', [length]); +} + +/** + * Create a ColumnType of type NUMERIC with specified precision and optional scale + * @param precision - The total number of digits + * @param scale - The number of digits to the right of the decimal point (optional) + * @returns A ColumnType instance representing a NUMERIC column with the specified precision and scale + */ +function Numeric(precision: number, scale?: number) { + if (scale !== undefined) { + return new ColumnType('NUMERIC', [precision, scale]); + } + return new ColumnType('NUMERIC', [precision]); +} + +/** + * Create a ColumnType of type DECIMAL with specified precision and optional scale + * @param precision - The total number of digits + * @param scale - The number of digits to the right of the decimal point (optional) + * @returns A ColumnType instance representing a DECIMAL column with the specified precision and scale + */ +function Decimal(precision: number, scale?: number) { + if (scale !== undefined) { + return new ColumnType('DECIMAL', [precision, scale]); + } + return new ColumnType('DECIMAL', [precision]); +} + +/** + * Create a ColumnType of type CHAR with specified length + * @param length - The fixed length of the CHAR column + * @returns A ColumnType instance representing a CHAR column with the specified length + */ +function Char(length: number) { + return new ColumnType('CHAR', [length]); +} + +/** + * Create a ColumnType of type VARBIT with specified length + * @param length - The maximum length of the VARBIT column + * @returns A ColumnType instance representing a VARBIT column with the specified length + */ +function VarBit(length: number) { + return new ColumnType('VARBIT', [length]); +} + +/** + * Create a ColumnType of type BIT with specified length + * @param length - The fixed length of the BIT column + * @returns A ColumnType instance representing a BIT column with the specified length + */ +function Bit(length: number) { + return new ColumnType('BIT', [length]); +} + +export { + Varchar, + Numeric, + Decimal, + Char, + VarBit, + Bit +} diff --git a/src/types/index.ts b/src/types/index.ts index 81cda64..e6c007e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -11,3 +11,6 @@ export * from './sqlFlavor.js'; export { default as sqlFlavor } from './sqlFlavor.js'; export * from './UsingTable.js'; export type { default as UsingTable } from './UsingTable.js'; +export * from './Column.js'; +export { default as Column } from './Column.js'; +export * from './ColumnTypes.js'; From bfb0fa1b1f96d54ecfea9ef1198666eb2ac63294 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Thu, 2 Oct 2025 02:56:23 -0300 Subject: [PATCH 34/74] feat/fix/changed how DdlQueryDefinition works to adapt to how other classes that extend this are used and built --- src/queryKinds/ddl/ddlQueryDefinition.ts | 83 +++++++++++++++++++++--- 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/src/queryKinds/ddl/ddlQueryDefinition.ts b/src/queryKinds/ddl/ddlQueryDefinition.ts index d42486b..1fe46a4 100644 --- a/src/queryKinds/ddl/ddlQueryDefinition.ts +++ b/src/queryKinds/ddl/ddlQueryDefinition.ts @@ -1,4 +1,6 @@ +import SqlEscaper from "../../sqlEscaper.js"; import QueryKind from "../../types/QueryKind"; +import sqlFlavor from "../../types/sqlFlavor.js"; /** * An array of function names that can be used to execute SQL queries. @@ -44,15 +46,15 @@ export default abstract class DdlQueryDefinition { protected tableName: string = ''; /** The built DDL query string, initialized to null. */ - protected builtQuery: string | null = null; + protected builtQuery: string | string[] | null = null; /** * Sets the name of the table for the DDL operation. * @param name - The name of the table. * @returns The current instance for method chaining. */ - public table(name: string): this { - this.tableName = name; + public table(name: string | null = null): this { + this.tableName = name ? SqlEscaper.escapeTableName(name, this.flavor) : ''; return this; } @@ -70,7 +72,7 @@ export default abstract class DdlQueryDefinition { * @param deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). * @returns The constructed DDL query string. */ - public abstract build(deepAnalysis?: boolean): string; + public abstract build(deepAnalysis?: boolean): string | string[]; /** * Builds an EXPLAIN query for the DDL operation. @@ -126,7 +128,7 @@ export default abstract class DdlQueryDefinition { * that represents the DDL operation defined by the instance. * @returns The SQL string representation of the DDL query. */ - public abstract toSQL(): string; + public abstract toSQL(): string | string[]; /** * Abstract getter to retrieve the kind of DDL query. @@ -135,6 +137,53 @@ export default abstract class DdlQueryDefinition { */ public abstract get kind(): QueryKind; + /** + * The SQL flavor to use for escaping identifiers. + * Default is PostgreSQL. + */ + protected flavor: sqlFlavor = sqlFlavor.postgres; + + /** + * Schemas to be used in the query. + * This is useful for databases that support multiple schemas. + * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. + */ + protected schemas: string[] = []; + + /** + * Sets the SQL flavor for escaping identifiers. + * @param flavor The SQL flavor to set. + * @returns The current DmlQueryDefinition instance for chaining. + */ + public sqlFlavor(flavor: sqlFlavor) { + this.flavor = flavor; + return this; + } + + /** + * Set schemas to be used in the query. + * This is useful for databases that support multiple schemas. + * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. + * @param schemas The schemas to set. + * @returns The current SelectQuery instance for chaining. + */ + public schema(...schemas: string[]): this { + this.schemas = schemas; + return this; + } + + /** + * Adds schemas to the existing list of schemas. + * This is useful for databases that support multiple schemas. + * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. + * @param schemas The schemas to add. + * @returns The current SelectQuery instance for chaining. + */ + public addSchema(...schemas: string[]): this { + this.schemas.push(...schemas); + return this; + } + /** * Executes the built SQL query using the provided query executor. * The query executor can be a function or an object with methods to execute the query. @@ -150,7 +199,13 @@ export default abstract class DdlQueryDefinition { ): Promise { if (typeof queryExecutor === 'function') { const builtQuery = this.build(); - await queryExecutor(builtQuery); + if (Array.isArray(builtQuery)) { + for (const query of builtQuery) { + await queryExecutor(query); + } + } else { + await queryExecutor(builtQuery); + } return; } @@ -158,7 +213,13 @@ export default abstract class DdlQueryDefinition { for (const functionName of functionNames) { if (typeof queryExecutor.manager[functionName] === 'function') { const builtQuery = this.build(); - await queryExecutor.manager[functionName]!(builtQuery); + if (Array.isArray(builtQuery)) { + for (const query of builtQuery) { + await queryExecutor.manager[functionName]!(query); + } + } else { + await queryExecutor.manager[functionName]!(builtQuery); + } return; } } @@ -166,7 +227,13 @@ export default abstract class DdlQueryDefinition { for (const functionName of functionNames) { if (typeof queryExecutor[functionName] === 'function') { const builtQuery = this.build(); - await queryExecutor[functionName]!(builtQuery); + if (Array.isArray(builtQuery)) { + for (const query of builtQuery) { + await queryExecutor[functionName]!(query); + } + } else { + await queryExecutor[functionName]!(builtQuery); + } return; } } From 9f1454e0e2f6e5231fd821a240c3ebe76b00e45a Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Thu, 2 Oct 2025 02:58:19 -0300 Subject: [PATCH 35/74] feat/introduced tables as the first ddl queries to exist in this package, issue #65 is now complete. TODO: needs testing. --- src/queryKinds/ddl/index.ts | 55 +++++ src/queryKinds/ddl/table/Alter.ts | 223 ++++++++++++++++++ src/queryKinds/ddl/table/Create.ts | 120 ++++++++++ src/queryKinds/ddl/table/Drop.ts | 72 ++++++ src/queryKinds/ddl/table/index.ts | 8 + .../ddl/table/tableColumnDefinition.ts | 24 ++ 6 files changed, 502 insertions(+) create mode 100644 src/queryKinds/ddl/table/Alter.ts create mode 100644 src/queryKinds/ddl/table/Create.ts create mode 100644 src/queryKinds/ddl/table/Drop.ts create mode 100644 src/queryKinds/ddl/table/index.ts create mode 100644 src/queryKinds/ddl/table/tableColumnDefinition.ts diff --git a/src/queryKinds/ddl/index.ts b/src/queryKinds/ddl/index.ts index c167ce0..9e32728 100644 --- a/src/queryKinds/ddl/index.ts +++ b/src/queryKinds/ddl/index.ts @@ -1,2 +1,57 @@ +import sqlFlavor from '../../types/sqlFlavor.js'; +import * as tables from './table/index.js'; + +export * as table from './table/index.js'; export * from './ddlQueryDefinition.js'; export { default as DdlQueryDefinition } from './ddlQueryDefinition.js'; + +/** + * Class representing a Table for DDL operations. + * This class provides methods to initiate DDL queries such as CREATE TABLE. + */ +export class Table { + + constructor( + private deepAnalysis: boolean = false, + private flavor = sqlFlavor.postgres + ) {} + + /** + * Initiates a new CREATE TABLE query. + * @returns A new CreateTableQuery instance with a build method that respects the deepAnalysis setting. + */ + public get create() { + const query = new tables.CreateTableQuery(); + query.sqlFlavor(this.flavor); + query.build = (deepAnalysis: boolean = this.deepAnalysis) => { + return tables.CreateTableQuery.prototype.build.call(query, deepAnalysis); + } + return query; + } + + /** + * Initiates a new DROP TABLE query. + * @returns A new DropTableQuery instance with a build method that respects the deepAnalysis setting. + */ + public get drop() { + const query = new tables.DropTableQuery(); + query.sqlFlavor(this.flavor); + query.build = (deepAnalysis: boolean = this.deepAnalysis) => { + return tables.DropTableQuery.prototype.build.call(query, deepAnalysis); + } + return query; + } + + /** + * Initiates a new ALTER TABLE query. + * @returns A new AlterTableQuery instance with a build method that respects the deepAnalysis setting. + */ + public get alter() { + const query = new tables.AlterTableQuery(); + query.sqlFlavor(this.flavor); + query.build = (deepAnalysis: boolean = this.deepAnalysis) => { + return tables.AlterTableQuery.prototype.build.call(query, deepAnalysis); + } + return query; + } +} diff --git a/src/queryKinds/ddl/table/Alter.ts b/src/queryKinds/ddl/table/Alter.ts new file mode 100644 index 0000000..3d06c57 --- /dev/null +++ b/src/queryKinds/ddl/table/Alter.ts @@ -0,0 +1,223 @@ +import SqlEscaper from "../../../sqlEscaper.js"; +import { ColumnDefinition } from "../../../types/Column.js"; +import QueryKind from "../../../types/QueryKind.js"; +import TableQueryDefinition from "./tableColumnDefinition.js"; + +/** + * Class representing an ALTER TABLE SQL query. + * This class allows you to define and build an ALTER TABLE SQL query + * with specified table name, columns to add, alter, or drop. + */ +export default class AlterTableQuery extends TableQueryDefinition { + + /** The columns to be added to the table. */ + columnsToAdd: ColumnDefinition[] = []; + /** The columns to be altered in the table, mapped by column name. */ + columnsToAlter: Map = new Map(); + /** The names of the columns to be dropped from the table. */ + columnsToDrop: string[] = []; + + constructor(tableName: string | null = null) { + super(); + this.tableName = tableName ? SqlEscaper.escapeTableName(tableName, this.flavor) : ''; + } + + /** + * Adds columns to be added to the table. + * @param columns - A single Column instance or an array of Column instances. + * @returns The current instance for method chaining. + */ + public addColumnsToAdd(columns: ColumnDefinition | ColumnDefinition[]): this { + if (Array.isArray(columns)) { + this.columnsToAdd.push(...columns); + } else { + this.columnsToAdd.push(columns); + } + return this; + } + + /** + * Sets the columns to be added to the table, replacing any existing ones. + * @param columns - A single Column instance or an array of Column instances. + * @returns The current instance for method chaining. + */ + public setColumnsToAdd(columns: ColumnDefinition | ColumnDefinition[]): this { + this.columnsToAdd = []; + return this.addColumnsToAdd(columns); + } + + /** + * Adds columns to be altered in the table. + * @param alteration - An object or array of objects containing the column name and the Column instance with alterations. + * @returns The current instance for method chaining. + */ + public addColumnsToAlter( + alteration: { name: string, columns: ColumnDefinition } + | { name: string, columns: ColumnDefinition }[] + ): this { + if (Array.isArray(alteration)) { + alteration.forEach(({ name, columns }) => { + if (!this.columnsToAlter.has(name)) { + this.columnsToAlter.set(name, []); + } + this.columnsToAlter.get(name)?.push(columns); + }); + } else { + const { name, columns } = alteration; + if (!this.columnsToAlter.has(name)) { + this.columnsToAlter.set(name, []); + } + this.columnsToAlter.get(name)?.push(columns); + } + return this; + } + + /** + * Sets the columns to be altered in the table, replacing any existing ones. + * @param alteration - An object or array of objects containing the column name and the Column instance with alterations. + * @returns The current instance for method chaining. + */ + public setColumnsToAlter( + alteration: { name: string, columns: ColumnDefinition } + | { name: string, columns: ColumnDefinition }[] + ): this { + this.columnsToAlter.clear(); + return this.addColumnsToAlter(alteration); + } + + /** + * Adds column names to be dropped from the table. + * @param columnNames - A single column name or an array of column names. + * @returns The current instance for method chaining. + */ + public dropColumnsByName(columnNames: string | string[]): this { + if (Array.isArray(columnNames)) { + this.columnsToDrop.push(...columnNames); + } else { + this.columnsToDrop.push(columnNames); + } + return this; + } + + /** + * Sets the column names to be dropped from the table, replacing any existing ones. + * @param columnNames - A single column name or an array of column names. + * @returns The current instance for method chaining. + */ + public setColumnsToDrop(columnNames: string | string[]): this { + this.columnsToDrop = []; + return this.dropColumnsByName(columnNames); + } + + /** + * Generates the SQL fragment for dropping a column. + * @param columnName - The name of the column to drop. + * @returns The SQL fragment for dropping the specified column. + */ + public static dropColumn(columnName: string): string { + return `DROP COLUMN ${columnName}`; + } + + /** + * Generates the SQL fragments for adding and altering columns. + * @returns An array of SQL fragments for adding and altering columns. + */ + private alterColumns(): string[][] { + const toAdd = this.columnsToAdd.map(col => col.buildToAdd(this.tableName)); + const toAlter = Array.from(this.columnsToAlter.entries()).map(([colName, alterations]) => { + return alterations.map(alteration => alteration.buildToAlter(this.tableName, colName)); + }).flat(); + return [...toAdd, ...toAlter]; + } + + /** + * Generates the SQL fragments for dropping columns. + * @returns An array of SQL fragments for dropping columns. + */ + private dropColumns(): string[] { + return this.columnsToDrop.map(colName => AlterTableQuery.dropColumn(colName)); + } + + /** + * Gets the kind of query. + * @returns The kind of query, which is 'ALTER_TABLE' for this class. + */ + public get kind() { + return QueryKind.ALTER_TABLE + } + + /** + * Builds the ALTER TABLE SQL query strings. + * @param _deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). + * @returns An array of constructed ALTER TABLE SQL query strings. + * @throws Error if the table name is not provided or if no alterations are specified. + */ + public build(_deepAnalysis?: boolean): string[] { + + if (!this.tableName) { + throw new Error('Table name is required to build ALTER TABLE query.'); + } + + const queries: string[] = []; + const alterCols = this.alterColumns(); + const dropCols = this.dropColumns(); + + alterCols.forEach(colParts => { + queries.push(...colParts); + }); + + dropCols.forEach(dropPart => { + let query = `ALTER TABLE ${this.tableName} ${dropPart}`; + queries.push(query); + }); + + if (queries.length === 0) { + throw new Error('No alterations specified for ALTER TABLE query.'); + } + + this.builtQuery = []; + queries.forEach(query => { + (this.builtQuery as any as string[])?.push(SqlEscaper.appendSchemas(`${query};`, this.schemas)); + }); + return this.builtQuery; + } + + /** + * Returns the SQL string representation of the ALTER TABLE query. + * @returns The SQL string representation of the ALTER TABLE query. + * @throws Error if the query has not been built yet. + */ + public toSQL(): string | string[] { + if(this.builtQuery) this.build(); + if(!this.builtQuery) throw new Error('No built query available. Please build the query first.'); + return this.builtQuery; + } + + /** + * Creates a clone of the current AlterTableQuery instance. + * @returns A new AlterTableQuery instance with the same properties as the current instance. + */ + public clone(): AlterTableQuery { + const cloned = new AlterTableQuery(this.tableName); + cloned.flavor = this.flavor; + cloned.columnsToAdd = [...this.columnsToAdd]; + cloned.columnsToAlter = new Map(this.columnsToAlter); + cloned.columnsToDrop = [...this.columnsToDrop]; + return cloned; + } + + /** + * Resets the AlterTableQuery instance to its initial state. + * This method clears the table name, columns to add, alter, drop, and the built query. + * @returns The current instance for method chaining. + */ + public reset(): this { + this.tableName = ''; + this.builtQuery = null; + this.columnsToAdd = []; + this.columnsToAlter.clear(); + this.columnsToDrop = []; + return this; + } + +} diff --git a/src/queryKinds/ddl/table/Create.ts b/src/queryKinds/ddl/table/Create.ts new file mode 100644 index 0000000..4f79315 --- /dev/null +++ b/src/queryKinds/ddl/table/Create.ts @@ -0,0 +1,120 @@ +import SqlEscaper from "../../../sqlEscaper.js"; +import { ColumnDefinition } from "../../../types/Column.js"; +import QueryKind from "../../../types/QueryKind.js"; +import TableQueryDefinition from "./tableColumnDefinition.js"; + + +/** + * Class representing a CREATE TABLE SQL query. + * This class allows you to define and build a CREATE TABLE SQL query + * with specified table name and columns. + */ +export default class CreateTableQuery extends TableQueryDefinition { + + /** The name of the table to be created. */ + private tableColumns: ColumnDefinition[] = []; + + constructor( + tableName?: string, + ...columns: ColumnDefinition[] + ) { + super(); + this.tableName = tableName ? SqlEscaper.escapeTableName(tableName, this.flavor) : ''; + this.tableColumns = columns ?? []; + } + + /** + * Gets the columns defined for the table. + * @returns An array of Column instances representing the table's columns. + */ + public get columns(): ColumnDefinition[] { + return this.tableColumns; + } + + /** + * Sets the columns for the table. + * @param columns - A single Column instance or an array of Column instances. + * @returns The current instance for method chaining. + */ + public setColumns(columns: ColumnDefinition | ColumnDefinition[]): this { + this.tableColumns = []; + return this.addColumns(columns); + } + + /** + * Adds columns to the table. + * @param columns - A single Column instance or an array of Column instances. + * @returns The current instance for method chaining. + */ + public addColumns(columns: ColumnDefinition | ColumnDefinition[]): this { + if (Array.isArray(columns)) { + this.tableColumns.push(...columns); + } else { + this.tableColumns.push(columns); + } + return this; + } + + /** + * Builds the CREATE TABLE SQL query string. + * @param _deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). + * @returns The constructed CREATE TABLE SQL query string. + * @throws Error if the table name is not set or if no columns are defined. + */ + public build(_deepAnalysis?: boolean): string { + if (!this.tableName) { + throw new Error('Table name is not set.'); + } + + if (this.tableColumns.length === 0) { + throw new Error('No columns defined for the table.'); + } + + const columnsDef = this.tableColumns.map(col => ` ${col.build()}`).join(',\n'); + const ifNotExists = this.ifNotExistsFlag ? 'IF NOT EXISTS ' : ''; + this.builtQuery = `CREATE TABLE ${ifNotExists}${this.tableName} (\n${columnsDef}\n);`; + this.builtQuery = SqlEscaper.appendSchemas(this.builtQuery, this.schemas); + return this.builtQuery; + } + + /** + * Creates a clone of the current CreateTableQuery instance. + * @returns A new instance of CreateTableQuery with the same properties as the current instance. + */ + public clone(): CreateTableQuery { + const cloned = new CreateTableQuery(this.tableName ?? undefined, ...this.tableColumns); + cloned.flavor = this.flavor; + cloned.ifNotExistsFlag = this.ifNotExistsFlag; + return cloned; + } + + /** + * Resets the state of the CreateTableQuery instance. + * This method clears the table name, columns, and built query. + * @returns The current instance for method chaining. + */ + public reset(): this { + this.tableName = ''; + this.tableColumns = []; + this.builtQuery = null; + this.ifNotExistsFlag = false; + return this; + } + + /** + * Returns the SQL string representation of the CREATE TABLE query. + * @returns The SQL string representation of the CREATE TABLE query. + */ + public toSQL(): string { + return this.build(); + } + + /** + * Gets the kind of the query. + * @returns The kind of the query, which is QueryKind.CREATE_TABLE. + */ + public get kind() { + return QueryKind.CREATE_TABLE; + } + +} diff --git a/src/queryKinds/ddl/table/Drop.ts b/src/queryKinds/ddl/table/Drop.ts new file mode 100644 index 0000000..cb57638 --- /dev/null +++ b/src/queryKinds/ddl/table/Drop.ts @@ -0,0 +1,72 @@ +import SqlEscaper from "../../../sqlEscaper.js"; +import QueryKind from "../../../types/QueryKind.js"; +import TableQueryDefinition from "./tableColumnDefinition.js"; + + +/** + * Class representing a DROP TABLE SQL query. + * This class allows you to define and build a DROP TABLE SQL query + * with specified table name and options like IF EXISTS. + */ +export default class DropTableQuery extends TableQueryDefinition { + + constructor(tableName: string | null = null) { + super(); + this.tableName = tableName ? SqlEscaper.escapeTableName(tableName, this.flavor) : ''; + } + + /** + * Builds the DROP TABLE SQL query string. + * @param _deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). + * @returns The constructed DROP TABLE SQL query string. + * @throws Error if the table name is not provided. + */ + public build(_deepAnalysis: boolean = false): string { + if (!this.tableName) { + throw new Error('Table name is required to build DROP TABLE query.'); + } + let query = 'DROP TABLE '; + if (this.ifNotExistsFlag) { + query += 'IF EXISTS '; + } + query += `${this.tableName};`; + this.builtQuery = SqlEscaper.appendSchemas(query, this.schemas); + return this.builtQuery; + } + + /** + * Creates a clone of the current DropTableQuery instance. + * @returns A new DropTableQuery instance with the same properties as the current instance. + */ + public clone(): DropTableQuery { + const cloned = new DropTableQuery(this.tableName); + cloned.flavor = this.flavor; + cloned.ifNotExistsFlag = this.ifNotExistsFlag; + return cloned; + } + + /** + * Resets the DropTableQuery instance to its initial state. + * This method clears the table name, built query, and IF NOT EXISTS flag. + * @returns The current instance for method chaining. + */ + public reset(): this { + this.tableName = ''; + this.builtQuery = null; + this.ifNotExistsFlag = false; + return this; + } + + /** + * Returns the SQL string representation of the DROP TABLE query. + * @returns The SQL string representation of the DROP TABLE query. + */ + public toSQL(): string { + return this.build(); + } + + /** Getter for the kind of query. */ + public get kind() { + return QueryKind.DROP_TABLE; + } +} diff --git a/src/queryKinds/ddl/table/index.ts b/src/queryKinds/ddl/table/index.ts new file mode 100644 index 0000000..a683680 --- /dev/null +++ b/src/queryKinds/ddl/table/index.ts @@ -0,0 +1,8 @@ +export * from './tableColumnDefinition.js'; +export { default as TableColumnDefinition } from './tableColumnDefinition.js'; +export * from './Create.js'; +export { default as CreateTableQuery } from './Create.js'; +export * from './Drop.js'; +export { default as DropTableQuery } from './Drop.js'; +export * from './Alter.js'; +export { default as AlterTableQuery } from './Alter.js'; diff --git a/src/queryKinds/ddl/table/tableColumnDefinition.ts b/src/queryKinds/ddl/table/tableColumnDefinition.ts new file mode 100644 index 0000000..e9b6580 --- /dev/null +++ b/src/queryKinds/ddl/table/tableColumnDefinition.ts @@ -0,0 +1,24 @@ +import DdlQueryDefinition from "../ddlQueryDefinition.js"; + +export default abstract class TableQueryDefinition extends DdlQueryDefinition { + /** Flag indicating whether to include IF NOT EXISTS clause. */ + protected ifNotExistsFlag: boolean = false; + + /** + * Marks the table creation to include IF NOT EXISTS clause. + * @returns The current instance for method chaining. + */ + public ifNotExists(): this { + this.ifNotExistsFlag = true; + return this; + } + + /** + * Resets the IF NOT EXISTS clause for the table creation. + * @returns The current instance for method chaining. + */ + public resetIfNotExists(): this { + this.ifNotExistsFlag = false; + return this; + } +} From 029414def0823057b0841d9fac7e2fb39303f73d Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Thu, 2 Oct 2025 02:58:37 -0300 Subject: [PATCH 36/74] feat/added tables to the queryMaker --- src/queryMaker.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/queryMaker.ts b/src/queryMaker.ts index 8f36cac..49f120e 100644 --- a/src/queryMaker.ts +++ b/src/queryMaker.ts @@ -1,4 +1,5 @@ import { Cte } from "./cteMaker.js"; +import { Table } from "./queryKinds/ddl/index.js"; import DeleteQuery from "./queryKinds/dml/delete.js"; import InsertQuery from "./queryKinds/dml/insert.js"; import SelectQuery from "./queryKinds/dml/select.js"; @@ -99,6 +100,16 @@ class Query { return unionQuery; } + /** + * Initiates a new Statement instance for building complex SQL statements. + * This can be used to create WHERE clauses, JOIN conditions, etc. + * @returns A new Statement instance. + */ + public get table() { + const table = new Table(this.deepAnalysisDefault, this.flavor); + return table; + } + /** * Initiates a new SELECT query. * @returns A new SelectQuery instance. @@ -157,6 +168,15 @@ class Query { return new Union(); } + /** + * Initiates a new Table instance for DDL operations. + * This can be used to create tables and other DDL statements. + * @returns A new Table instance. + */ + public static get table() { + return new Table(); + } + } export default Query; From e3fbd1fb032938fdd486d3dabac7af1bc580c75f Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Thu, 2 Oct 2025 03:08:35 -0300 Subject: [PATCH 37/74] fix/configured exports correctly --- package.json | 16 ++++++++++++++++ src/queryKinds/ddl/index.ts | 1 - tsup.config.ts | 3 ++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 609f690..161b3da 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,22 @@ ".": { "import": "./dist/index.mjs", "require": "./dist/index.js" + }, + "./types": { + "import": "./dist/types/index.mjs", + "require": "./dist/types/index.js" + }, + "./ddl": { + "import": "./dist/queryKinds/ddl/index.mjs", + "require": "./dist/queryKinds/ddl/index.js" + }, + "./dml": { + "import": "./dist/queryKinds/dml/index.mjs", + "require": "./dist/queryKinds/dml/index.js" + }, + "./ddl/table": { + "import": "./dist/queryKinds/ddl/table/index.mjs", + "require": "./dist/queryKinds/ddl/table/index.js" } }, "scripts": { diff --git a/src/queryKinds/ddl/index.ts b/src/queryKinds/ddl/index.ts index 9e32728..350bb1f 100644 --- a/src/queryKinds/ddl/index.ts +++ b/src/queryKinds/ddl/index.ts @@ -1,7 +1,6 @@ import sqlFlavor from '../../types/sqlFlavor.js'; import * as tables from './table/index.js'; -export * as table from './table/index.js'; export * from './ddlQueryDefinition.js'; export { default as DdlQueryDefinition } from './ddlQueryDefinition.js'; diff --git a/tsup.config.ts b/tsup.config.ts index 7882013..6d0073c 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -5,7 +5,8 @@ export default defineConfig({ 'src/index.ts', 'src/types/index.ts', 'src/queryKinds/dml/index.ts', - 'src/queryKinds/ddl/index.ts' + 'src/queryKinds/ddl/index.ts', + 'src/queryKinds/ddl/table/index.ts', ], target: ['esnext', 'node21'], format: ['cjs', 'esm'], From c2ffb5fe8e12ce7b46222c6c414522eb9e8cc6dd Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Sun, 5 Oct 2025 04:10:14 -0300 Subject: [PATCH 38/74] feat/added some more commands for development and updated typescript to its latest version --- package-lock.json | 8 ++++---- package.json | 8 ++++++-- pnpm-lock.yaml | 16 ++++++++-------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8040297..7283588 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "terser": "^5.44.0", "tslib": "^2.8.1", "tsup": "^8.5.0", - "typescript": "^5.9.2", + "typescript": "^5.9.3", "vitest": "^3.2.4", "wait-on": "^9.0.1", "zod": "^4.1.11" @@ -3790,9 +3790,9 @@ } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "peer": true, diff --git a/package.json b/package.json index 161b3da..a38e444 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,11 @@ "build": "tsup --config tsup.config.ts --format esm", "build:dev": "tsup --config tsup.config.ts --watch --format esm", "start:dev": "nodemon --watch 'dist' --exec 'wait-on dist/index.mjs && node dist/index.mjs'", - "dev": "concurrently \"npm run build:dev\" \"npm run start:dev\"", + "start:dev:deno": "nodemon --watch 'dist' --exec 'wait-on dist/index.mjs && deno run --allow-read dist/index.mjs'", + "start:dev:bun": "nodemon --watch 'dist' --exec 'wait-on dist/index.mjs && bun run dist/index.mjs'", + "dev": "concurrently \"pnpm run build:dev\" \"pnpm run start:dev\"", + "dev:deno": "concurrently \"pnpm run build:dev\" \"pnpm run start:dev:deno\"", + "dev:bun": "concurrently \"pnpm run build:dev\" \"pnpm run start:dev:bun\"", "start": "node dist/index.js", "build:prod": "tsup --config tsup.config.ts", "test": "vitest run", @@ -74,7 +78,7 @@ "terser": "^5.44.0", "tslib": "^2.8.1", "tsup": "^8.5.0", - "typescript": "^5.9.2", + "typescript": "^5.9.3", "vitest": "^3.2.4", "wait-on": "^9.0.1", "zod": "^4.1.11" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62df6ad..a10e650 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,10 +38,10 @@ importers: version: 2.8.1 tsup: specifier: ^8.5.0 - version: 8.5.0(@swc/core@1.13.5)(postcss@8.5.6)(typescript@5.9.2) + version: 8.5.0(@swc/core@1.13.5)(postcss@8.5.6)(typescript@5.9.3) typescript: - specifier: ^5.9.2 - version: 5.9.2 + specifier: ^5.9.3 + version: 5.9.3 vitest: specifier: ^3.2.4 version: 3.2.4(terser@5.44.0) @@ -1215,8 +1215,8 @@ packages: typescript: optional: true - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true @@ -2357,7 +2357,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.0(@swc/core@1.13.5)(postcss@8.5.6)(typescript@5.9.2): + tsup@8.5.0(@swc/core@1.13.5)(postcss@8.5.6)(typescript@5.9.3): dependencies: bundle-require: 5.1.0(esbuild@0.25.9) cac: 6.7.14 @@ -2379,14 +2379,14 @@ snapshots: optionalDependencies: '@swc/core': 1.13.5 postcss: 8.5.6 - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - jiti - supports-color - tsx - yaml - typescript@5.9.2: {} + typescript@5.9.3: {} ufo@1.6.1: {} From bc260b63a26a9cb942f61d9f271c452d88d02317 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Sun, 5 Oct 2025 04:10:55 -0300 Subject: [PATCH 39/74] fix/disabled not so helpful option so I couldn't compare optional parameters with nothing --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index a974335..58086f5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,7 +17,7 @@ "declarationMap": true, "noUncheckedIndexedAccess": true, - "exactOptionalPropertyTypes": true, + "exactOptionalPropertyTypes": false, // Style Options "noUnusedLocals": true, From 8337b97fdd60ff45c1539ee69e237709c5a681bd Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Sun, 5 Oct 2025 04:17:37 -0300 Subject: [PATCH 40/74] feat/refact/refactored Column to a new folder called queryUtils and added ForeignKey support Added a new folder called queryUtils to add all utilities used by queries, such as the (now moved to there) Column class. Also created a new type called ForeignKey used to represent Foreign Keys in databases. --- src/{types => queryUtils}/Column.ts | 61 +++++++++++++++++++++++++++-- src/queryUtils/index.ts | 2 + src/types/ColumnTypes.ts | 2 +- src/types/ForeignKey.ts | 17 ++++++++ src/types/index.ts | 4 +- 5 files changed, 80 insertions(+), 6 deletions(-) rename src/{types => queryUtils}/Column.ts (79%) create mode 100644 src/queryUtils/index.ts create mode 100644 src/types/ForeignKey.ts diff --git a/src/types/Column.ts b/src/queryUtils/Column.ts similarity index 79% rename from src/types/Column.ts rename to src/queryUtils/Column.ts index 069a5bf..07ff49e 100644 --- a/src/types/Column.ts +++ b/src/queryUtils/Column.ts @@ -1,4 +1,5 @@ -import { ColumnTypes } from "./ColumnTypes.js"; +import { ColumnTypes } from "../types/ColumnTypes.js"; +import ForeignKey, { Actions } from "../types/ForeignKey.js"; /** * Class representing a column type. @@ -87,6 +88,8 @@ export class ColumnDefinition { private defaultValue?: string; /** The check condition for the column, if any. */ private checkCondition?: string; + /** Foreign key constraint details, if any. */ + private foreignKey: ForeignKey | null = null; constructor( name: string | null = null, @@ -186,6 +189,29 @@ export class ColumnDefinition { return this; } + /** + * Sets a foreign key constraint for the column. + * @param foreignTable - The name of the foreign table. + * @param foreignColumn - The name of the foreign column. + * @param onDeleteAction - Optional action to take on delete (e.g., CASCADE, SET NULL). + * @param onUpdateAction - Optional action to take on update (e.g., CASCADE, SET NULL). + * @returns The current instance for method chaining. + */ + public references( + foreignTable: string, + foreignColumn: string, + onDeleteAction?: Actions, + onUpdateAction?: Actions + ): this { + this.foreignKey = { + table: foreignTable, + column: foreignColumn, + onDelete: onDeleteAction, + onUpdate: onUpdateAction + }; + return this; + } + /** * Builds the string representation of the column definition. * @returns The string representation of the column definition. @@ -224,6 +250,17 @@ export class ColumnDefinition { parts.push(`CHECK (${this.checkCondition})`); } + if (this.foreignKey) { + let fkPart = `REFERENCES ${this.foreignKey.table}(${this.foreignKey.column})`; + if (this.foreignKey.onDelete) { + fkPart += ` ON DELETE ${this.foreignKey.onDelete}`; + } + if (this.foreignKey.onUpdate) { + fkPart += ` ON UPDATE ${this.foreignKey.onUpdate}`; + } + parts.push(fkPart); + } + return parts.join(' '); } @@ -244,6 +281,11 @@ export class ColumnDefinition { let addColumnUnique = this.isUnique ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_unique UNIQUE (${this.name})` : ''; + let addForeignKey = this.foreignKey + ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_fkey FOREIGN KEY (${this.name}) REFERENCES ${this.foreignKey.table}(${this.foreignKey.column})` + + (this.foreignKey.onDelete ? ` ON DELETE ${this.foreignKey.onDelete}` : '') + + (this.foreignKey.onUpdate ? ` ON UPDATE ${this.foreignKey.onUpdate}` : '') + : ''; let additions = [ addColumn, @@ -251,7 +293,8 @@ export class ColumnDefinition { addColumnDefault, addColumnCheck, addColumnPrimaryKey, - addColumnUnique + addColumnUnique, + addForeignKey ].filter(part => part !== ''); return additions; @@ -278,6 +321,11 @@ export class ColumnDefinition { let alterColumnUnique = this.isUnique ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_unique UNIQUE (${this.name})` : ''; + let alterForeignKey = this.foreignKey + ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_fkey FOREIGN KEY (${this.name}) REFERENCES ${this.foreignKey.table}(${this.foreignKey.column})` + + (this.foreignKey.onDelete ? ` ON DELETE ${this.foreignKey.onDelete}` : '') + + (this.foreignKey.onUpdate ? ` ON UPDATE ${this.foreignKey.onUpdate}` : '') + : ''; let alterations = [ alterColumnName, @@ -286,7 +334,8 @@ export class ColumnDefinition { alterColumnDefault, alterColumnCheck, alterColumnPrimaryKey, - alterColumnUnique + alterColumnUnique, + alterForeignKey ].filter(part => part !== ''); return alterations; @@ -303,6 +352,12 @@ export class ColumnDefinition { } +/** + * Factory function to create a new ColumnDefinition instance. + * @param name - The name of the column (optional). + * @param type - The type of the column, either as a ColumnType instance or a string (optional). + * @returns A new ColumnDefinition instance. + */ export default function Column( name: string | null = null, type: ColumnType | string | null = null diff --git a/src/queryUtils/index.ts b/src/queryUtils/index.ts new file mode 100644 index 0000000..6bf5ec7 --- /dev/null +++ b/src/queryUtils/index.ts @@ -0,0 +1,2 @@ +export * from './Column.js'; +export { default as Column } from './Column.js'; diff --git a/src/types/ColumnTypes.ts b/src/types/ColumnTypes.ts index 56c6188..fc60620 100644 --- a/src/types/ColumnTypes.ts +++ b/src/types/ColumnTypes.ts @@ -1,4 +1,4 @@ -import { ColumnType } from "./Column.js"; +import { ColumnType } from "../queryUtils/Column.js"; /** * Contain all the possible column types in PostgreSQL diff --git a/src/types/ForeignKey.ts b/src/types/ForeignKey.ts new file mode 100644 index 0000000..0a03da2 --- /dev/null +++ b/src/types/ForeignKey.ts @@ -0,0 +1,17 @@ + +/** + * Represents a foreign key relationship in a database schema. + * It includes the referenced table and column, as well as optional actions + * to take on delete or update events. + */ +type ForeignKey = { + table: string; + column: string; + onDelete?: Actions; + onUpdate?: Actions; +}; + +/** Possible actions for foreign key constraints. */ +export type Actions = 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | 'SET DEFAULT'; + +export default ForeignKey; diff --git a/src/types/index.ts b/src/types/index.ts index e6c007e..ecaad9e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -11,6 +11,6 @@ export * from './sqlFlavor.js'; export { default as sqlFlavor } from './sqlFlavor.js'; export * from './UsingTable.js'; export type { default as UsingTable } from './UsingTable.js'; -export * from './Column.js'; -export { default as Column } from './Column.js'; export * from './ColumnTypes.js'; +export type * from './ForeignKey.js'; +export type { default as ForeignKey } from './ForeignKey.js'; From ba85f4e1dbb79c6814bf7285892bceab3e3a3aef Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Sun, 5 Oct 2025 04:18:51 -0300 Subject: [PATCH 41/74] feat/refact/updated path name from types to queryUtils for Column and made a better implementation for clone in CreateTableQuery --- src/queryKinds/ddl/table/Alter.ts | 2 +- src/queryKinds/ddl/table/Create.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/queryKinds/ddl/table/Alter.ts b/src/queryKinds/ddl/table/Alter.ts index 3d06c57..20d71fb 100644 --- a/src/queryKinds/ddl/table/Alter.ts +++ b/src/queryKinds/ddl/table/Alter.ts @@ -1,5 +1,5 @@ import SqlEscaper from "../../../sqlEscaper.js"; -import { ColumnDefinition } from "../../../types/Column.js"; +import { ColumnDefinition } from "../../../queryUtils/Column.js"; import QueryKind from "../../../types/QueryKind.js"; import TableQueryDefinition from "./tableColumnDefinition.js"; diff --git a/src/queryKinds/ddl/table/Create.ts b/src/queryKinds/ddl/table/Create.ts index 4f79315..9c7b194 100644 --- a/src/queryKinds/ddl/table/Create.ts +++ b/src/queryKinds/ddl/table/Create.ts @@ -1,5 +1,5 @@ import SqlEscaper from "../../../sqlEscaper.js"; -import { ColumnDefinition } from "../../../types/Column.js"; +import { ColumnDefinition } from "../../../queryUtils/Column.js"; import QueryKind from "../../../types/QueryKind.js"; import TableQueryDefinition from "./tableColumnDefinition.js"; @@ -82,7 +82,9 @@ export default class CreateTableQuery extends TableQueryDefinition { * @returns A new instance of CreateTableQuery with the same properties as the current instance. */ public clone(): CreateTableQuery { - const cloned = new CreateTableQuery(this.tableName ?? undefined, ...this.tableColumns); + const cloned = new CreateTableQuery(); + cloned.tableName = this.tableName; + cloned.tableColumns = [...this.tableColumns]; cloned.flavor = this.flavor; cloned.ifNotExistsFlag = this.ifNotExistsFlag; return cloned; From 68d9f9b0ea935cc155d510382d7cfa8a6c2cf24a Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Sun, 5 Oct 2025 04:22:50 -0300 Subject: [PATCH 42/74] feat/refact/made validate method better and added getMany and getOne Added some auxiliary method for a better way to get just one thing from the query, also added getMany that is just execute being run. Also added better support for validation that before just didn't worked correctly in the type system, it returned an instance of `DmlQueryDefinition` without considering that extended classes existed, so you couldn't use the `select` method for the `SelectQuery` after you used the `validate` method before, now it works correctly. --- src/queryKinds/dml/dmlQueryDefinition.test.ts | 123 ++++++++++- src/queryKinds/dml/dmlQueryDefinition.ts | 200 +++++++++++++++--- 2 files changed, 290 insertions(+), 33 deletions(-) diff --git a/src/queryKinds/dml/dmlQueryDefinition.test.ts b/src/queryKinds/dml/dmlQueryDefinition.test.ts index 5e38926..d0baec9 100644 --- a/src/queryKinds/dml/dmlQueryDefinition.test.ts +++ b/src/queryKinds/dml/dmlQueryDefinition.test.ts @@ -9,7 +9,7 @@ import { IsDefined, IsNumber, IsString } from "class-validator"; import { Transform } from "class-transformer"; -describe('Query Definition', () => { +describe('DML Query Definition', () => { it('should be able to set sql flavor', () => { const query = new SelectQuery() .from('users') @@ -200,4 +200,125 @@ describe('Query Definition', () => { await queryClass.execute(executorFunction); }).rejects.toThrowError(); }); + + it('should handle execution with strange runner outputs', async () => { + const query = new InsertQuery('users') + .values({ name: 'John', age: 30 }) + .returning(['id', 'name', 'age']); + + + const executorFunctionUndefined = async (text: string, values: any[]) => { + expect(text).toBe('INSERT INTO "users" ("name", "age") VALUES ($1, $2)\nRETURNING "id", "name", "age"'); + expect(values).toEqual(['John', 30]); + return [ + [{ id: 1, name: 'John', age: 30 }], 1 + ]; + } + + const resultUndefined = await query.execute(executorFunctionUndefined); + expect(resultUndefined).toEqual([{ id: 1, name: 'John', age: 30 }]); + + const executorFunctionNoArray = async (text: string, values: any[]): Promise => { + expect(text).toBe('INSERT INTO "users" ("name", "age") VALUES ($1, $2)\nRETURNING "id", "name", "age"'); + expect(values).toEqual(['John', 30]); + return { id: 1, name: 'John', age: 30 }; + } + + const resultNoArray = await query.execute(executorFunctionNoArray); + expect(resultNoArray).toEqual([{ id: 1, name: 'John', age: 30 }]); + + const executorFunctionNull = async (text: string, values: any[]): Promise => { + expect(text).toBe('INSERT INTO "users" ("name", "age") VALUES ($1, $2)\nRETURNING "id", "name", "age"'); + expect(values).toEqual(['John', 30]); + return null; + } + + const resultNull = await query.execute(executorFunctionNull); + expect(resultNull).toEqual([]); + + const executorFunctionRowsUndefined = async (text: string, values: any[]): Promise => { + expect(text).toBe('INSERT INTO "users" ("name", "age") VALUES ($1, $2)\nRETURNING "id", "name", "age"'); + expect(values).toEqual(['John', 30]); + return { rows: undefined }; + } + + const resultRowsUndefined = await query.execute(executorFunctionRowsUndefined); + expect(resultRowsUndefined).toEqual([]); + + const executorFunctionRowsNoArray = async (text: string, values: any[]): Promise => { + expect(text).toBe('INSERT INTO "users" ("name", "age") VALUES ($1, $2)\nRETURNING "id", "name", "age"'); + expect(values).toEqual(['John', 30]); + return { rows: { id: 1, name: 'John', age: 30 } }; + } + + const resultRowsNoArray = await query.execute(executorFunctionRowsNoArray); + expect(resultRowsNoArray).toEqual([ + { id: 1, name: 'John', age: 30 } + ]); + + const executorFunctionRows1 = async (text: string, values: any[]): Promise => { + expect(text).toBe('INSERT INTO "users" ("name", "age") VALUES ($1, $2)\nRETURNING "id", "name", "age"'); + expect(values).toEqual(['John', 30]); + return { rows: 1 }; + } + + await expect(async () => { + await query.execute(executorFunctionRows1); + }).rejects.toThrowError('Invalid rows property in result from query executor function.'); + + const executorFunction1 = async (text: string, values: any[]): Promise => { + expect(text).toBe('INSERT INTO "users" ("name", "age") VALUES ($1, $2)\nRETURNING "id", "name", "age"'); + expect(values).toEqual(['John', 30]); + return 1; + } + + await expect(async () => { + await query.execute(executorFunction1); + }).rejects.toThrowError('Invalid result from query executor function.'); + }); + + it('should support getting one and getting many', async () => { + const query = new InsertQuery('users') + .values({ name: 'John', age: 30 }) + .returning(['id', 'name', 'age']); + + const executorFunction = async (text: string, values: any[]) => { + expect(text).toBe('INSERT INTO "users" ("name", "age") VALUES ($1, $2)\nRETURNING "id", "name", "age"'); + expect(values).toEqual(['John', 30]); + return [{ id: 1, name: 'John', age: 30 }, { id: 2, name: 'Jane', age: 25 }]; + } + + const resultMany = await query.getMany(executorFunction); + expect(resultMany).toEqual([{ id: 1, name: 'John', age: 30 }, { id: 2, name: 'Jane', age: 25 }]); + + const resultOne = await query.getOne(executorFunction); + expect(resultOne).toEqual({ id: 1, name: 'John', age: 30 }); + + const executorFunctionEmpty = async (text: string, values: any[]) => { + expect(text).toBe('INSERT INTO "users" ("name", "age") VALUES ($1, $2)\nRETURNING "id", "name", "age"'); + expect(values).toEqual(['John', 30]); + return []; + } + + const resultManyEmpty = await query.getMany(executorFunctionEmpty); + expect(resultManyEmpty).toEqual([]); + + const resultOneEmpty = await query.getOne(executorFunctionEmpty); + expect(resultOneEmpty).toBeNull(); + + // In select queries it should add limit of 1 + const selectQuery = new SelectQuery() + .from('users') + .select(['id', 'name', 'age']) + .where('age > ?', 18); + + const executorFunctionSelect = async (text: string, values: any[]) => { + expect(text).toBe('SELECT\n "id",\n "name",\n "age"\nFROM "users"\nWHERE (age > $1)\nLIMIT 1'); + expect(values).toEqual([18]); + return [{ id: 1, name: 'John', age: 30 }, { id: 2, name: 'Jane', age: 25 }]; + } + + const resultOneSelect = await selectQuery.getOne(executorFunctionSelect); + expect(resultOneSelect).toEqual({ id: 1, name: 'John', age: 30 }); + }); }); diff --git a/src/queryKinds/dml/dmlQueryDefinition.ts b/src/queryKinds/dml/dmlQueryDefinition.ts index 55f6fa1..799d9a1 100644 --- a/src/queryKinds/dml/dmlQueryDefinition.ts +++ b/src/queryKinds/dml/dmlQueryDefinition.ts @@ -18,12 +18,20 @@ import SqlEscaper from "../../sqlEscaper.js"; */ const functionNames = ['execute', 'query', 'run', 'all', 'get'] as const; +/** + * FunctionDeclarationReturnType type defines the possible return types for functions that execute SQL queries. + * It can be an array of results, an object containing a rows property with the results, + * or a tuple containing an array of results and a number (e.g., for affected rows). + */ +type FunctionDeclarationReturnType = T[] | { rows: T[] } | [T[], number]; + /** * FunctionDeclaration type defines the signature for functions that execute SQL queries. * It takes a query string and an array of parameters, and returns a promise that resolves - * to either an array of results or an object containing a rows property with the results. + * with a result of type FunctionDeclarationReturnType or directly a result of that type. */ -type FunctionDeclaration = (query: string, params: any[]) => Promise; +type FunctionDeclaration = + (query: string, params: any[]) => Promise> | FunctionDeclarationReturnType; /** * QueryExecutorObject interface defines the structure for an object that can execute SQL queries. @@ -41,7 +49,7 @@ interface QueryExecutorObject { /** * QueryExecutor type can be either a QueryExecutorObject or a function that executes a query. */ -type QueryExecutor = +export type QueryExecutor = QueryExecutorObject | FunctionDeclaration; @@ -54,6 +62,13 @@ type SchemaType = S extends { new(): infer U } ? U : never; +/** + * OmittingReturnFromValidate is a utility type that modifies the type T by omitting + * the methods 'execute', 'getOne', and 'getMany', and adding the DmlQueryDefinition with the inferred schema type. + */ +type ReturnFromValidate = + DmlQueryDefinition> & This; + /** * Abstract class DmlQueryDefinition serves as a blueprint for different types of SQL query definitions. * It defines the essential methods and properties that any concrete query class must implement. @@ -260,17 +275,17 @@ export default abstract class DmlQueryDefinition { * @returns A promise that resolves if the schema is valid, or rejects with validation errors. * @throws An error if no validation library is available. */ - public validate any)>( - schema: T - ): DmlQueryDefinition> { - if ((schema as any).safeParse) { + public validate< + T extends { safeParse: Function } | (new (...args: any[]) => any) + >(schema: T): ReturnFromValidate { + if ("safeParse" in schema) { this.isZodSchema = true; } else { this.isClassValidatorSchema = true; } this.validatorSchema = schema; - return this; + return this as ReturnFromValidate; } /** @@ -292,22 +307,46 @@ export default abstract class DmlQueryDefinition { * @returns A promise that resolves if the data is valid, or rejects with validation errors. * @throws An error if no validation library is available. */ - private async handleValidation(input: any) { - if (this.validatorSchema) { - if (this.isZodSchema) { - const zod = await getZod(); - return await zod.array(this.validatorSchema).parseAsync(input); - } else if (this.isClassValidatorSchema) { - const { classValidator, classTransformer } = await getClassValidator(); - const { validateOrReject } = classValidator; - const transformed = classTransformer.plainToInstance(this.validatorSchema, input); - for (const item of transformed) { - // Use class-validator to validate each item - await validateOrReject(item as any, this.classValidatorOptions); + private async handleValidation(input: any): Promise { + input = Array.isArray(input) ? input : [input]; + + input = input !== null + ? Array.isArray(input) && input.length === 2 + && Array.isArray(input?.[0]) + && typeof input?.[1] === 'number' + ? input[0] ?? null + : input ?? null + : input; + + try { + if (this.validatorSchema) { + if (this.isZodSchema) { + const zod = await getZod(); + input = await zod.array(this.validatorSchema).parseAsync(input); + } else if (this.isClassValidatorSchema) { + const { classValidator, classTransformer } = await getClassValidator(); + const { validateOrReject } = classValidator; + input = classTransformer.plainToInstance(this.validatorSchema, input); + for (const item of input as any[]) { + // Use class-validator to validate each item + await validateOrReject(item as any, this.classValidatorOptions); + } } - - return transformed; } + } catch (error) { + console.group('Validation Error'); + console.error('Error during validation:', error); + console.debug('Input data:', input); + console.debug('Validator schema:', this.validatorSchema); + console.groupEnd(); + + throw new Error('Validation failed. See console for details.', { + cause: { + originalError: error, + input, + schema: this.validatorSchema + } + }); } return input; @@ -339,11 +378,29 @@ export default abstract class DmlQueryDefinition { ): Promise { if (typeof queryExecutor === 'function') { const builtQuery = this.build(); - const result = await queryExecutor(builtQuery.text, builtQuery.values); - if ((result as any)?.rows) { - return await this.handleValidation((result as any).rows); + let result = await queryExecutor(builtQuery.text, builtQuery.values); + if ( + result === undefined + || result === null + ) return []; + + if (typeof result !== 'object') { + throw new Error('Invalid result from query executor function.'); + } + + if ("rows" in result) { + if ( + result.rows === undefined + || result.rows === null + ) return []; + + if (typeof result.rows !== 'object') { + throw new Error('Invalid rows property in result from query executor function.'); + } + + return await this.handleValidation(result.rows); } else { - return await this.handleValidation(result); + return await this.handleValidation(result); } } @@ -352,10 +409,28 @@ export default abstract class DmlQueryDefinition { if (typeof queryExecutor.manager[functionName] === 'function') { const builtQuery = this.build(); const result = await queryExecutor.manager[functionName]!(builtQuery.text, builtQuery.values); - if ((result as any)?.rows) { - return await this.handleValidation((result as any).rows); + if ( + result === undefined + || result === null + ) return []; + + if (typeof result !== 'object') { + throw new Error('Invalid result from query executor manager function.'); + } + + if ("rows" in result) { + if ( + result.rows === undefined + || result.rows === null + ) return []; + + if (typeof result.rows !== 'object') { + throw new Error('Invalid rows property in result from query executor manager function.'); + } + + return await this.handleValidation(result.rows); } else { - return await this.handleValidation(result); + return await this.handleValidation(result); } } } @@ -364,10 +439,25 @@ export default abstract class DmlQueryDefinition { if (typeof queryExecutor[functionName] === 'function') { const builtQuery = this.build(); const result = await queryExecutor[functionName]!(builtQuery.text, builtQuery.values); - if ((result as any)?.rows) { - return await this.handleValidation((result as any).rows); + if (!result) return []; + + if (typeof result !== 'object') { + throw new Error('Invalid result from query executor object function.'); + } + + if ("rows" in result) { + if ( + result.rows === undefined + || result.rows === null + ) return []; + + if (typeof result.rows !== 'object') { + throw new Error('Invalid rows property in result from query executor manager function.'); + } + + return await this.handleValidation(result.rows); } else { - return await this.handleValidation(result); + return await this.handleValidation(result); } } } @@ -376,6 +466,52 @@ export default abstract class DmlQueryDefinition { throw new Error('Invalid query executor provided.'); } + /** + * Executes the built SQL query and returns a single result or null if no result is found. + * This method ensures that only one result is returned by applying a limit if necessary. + * @param queryExecutor The executor to run the SQL query. + * @param noManager If true, bypasses the manager property of the executor object. + * @returns A promise that resolves with a single result or null. + * @throws An error if the provided query executor is invalid or if validation fails. + */ + public async getOne( + queryExecutor: QueryExecutor, + noManager: boolean = false + ): Promise { + if ( + "limit" in this + && typeof this['limit'] === 'function' + && this['limit'] instanceof Function + && this['limit'](1) !== this + ) { + this.limit(1); + } + + const result = await (this.execute(queryExecutor, noManager)); + + return result !== null + ? Array.isArray(result) && result.length === 2 + && Array.isArray(result?.[0]) + && typeof result?.[1] === 'number' + ? result[0][0] as T ?? null + : result[0] as T ?? null + : null; + } + + /** + * Executes the built SQL query and returns multiple results. + * @param queryExecutor The executor to run the SQL query. + * @param noManager If true, bypasses the manager property of the executor object. + * @returns A promise that resolves with an array of results. + * @throws An error if the provided query executor is invalid or if validation fails. + */ + public async getMany( + queryExecutor: QueryExecutor, + noManager: boolean = false + ): Promise { + return (this.execute(queryExecutor, noManager)); + } + /** * Builds the SQL query with EXPLAIN ANALYZE prefix for performance analysis. * This method is useful for debugging and optimizing SQL queries. From 3753c4b751c70463fa3c410edf17dffbd34a6ffe Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Sun, 5 Oct 2025 04:23:28 -0300 Subject: [PATCH 43/74] feat/added src/queryUtils/index.ts to tsup entry points --- tsup.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tsup.config.ts b/tsup.config.ts index 6d0073c..96bf0e4 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -4,6 +4,7 @@ export default defineConfig({ entry: [ 'src/index.ts', 'src/types/index.ts', + 'src/queryUtils/index.ts', 'src/queryKinds/dml/index.ts', 'src/queryKinds/ddl/index.ts', 'src/queryKinds/ddl/table/index.ts', From 3093a5d95917dffef00b8d7342e76ca69342fbad Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Sun, 5 Oct 2025 04:23:52 -0300 Subject: [PATCH 44/74] ongoing/feat/started adding testing to CreateTableQuery class --- src/queryKinds/ddl/table/Create.test.ts | 104 ++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/queryKinds/ddl/table/Create.test.ts diff --git a/src/queryKinds/ddl/table/Create.test.ts b/src/queryKinds/ddl/table/Create.test.ts new file mode 100644 index 0000000..a385c69 --- /dev/null +++ b/src/queryKinds/ddl/table/Create.test.ts @@ -0,0 +1,104 @@ +import { describe, expect, it } from "vitest"; +import CreateTableQuery from "./Create.js"; +import Column from "../../../queryUtils/Column.js"; +import { Decimal, Varchar } from "../../../types/ColumnTypes.js"; + + +describe('Create Table Query', () => { + it('should create a basic CREATE TABLE query', () => { + const query = new CreateTableQuery('users') + .addColumns([ + Column('id', 'INT').primaryKey().notNull(), + Column('name', Varchar(100)).notNull(), + Column('email', Varchar(100)).unique() + ]) + .build(); + + expect(query).toBe( + 'CREATE TABLE "users" (\n id INT PRIMARY KEY,\n name VARCHAR(100) NOT NULL,\n email VARCHAR(100) UNIQUE\n);' + ); + }); + + it('should create a CREATE TABLE query with IF NOT EXISTS', () => { + const query = new CreateTableQuery('users') + .ifNotExists() + .addColumns([ + Column('id', 'INT').primaryKey().notNull(), + Column('name', Varchar(100)).notNull(), + Column('email', Varchar(100)).unique() + ]) + .build(); + + expect(query).toBe( + 'CREATE TABLE IF NOT EXISTS "users" (\n id INT PRIMARY KEY,\n name VARCHAR(100) NOT NULL,\n email VARCHAR(100) UNIQUE\n);' + ); + }); + + it('should throw an error if table name is not set', () => { + const query = new CreateTableQuery() + .addColumns([ + Column('id', 'INT').primaryKey().notNull(), + Column('name', Varchar(100)).notNull() + ]); + + expect(() => query.build()).toThrow('Table name is not set.'); + }); + + it('should work with foreign keys', () => { + const query = new CreateTableQuery('orders') + .addColumns([ + Column('id', 'INT').primaryKey().notNull(), + Column('user_id', 'INT').notNull().references('users', 'id'), + Column('total', Decimal(10, 2)).notNull() + ]) + .build(); + + expect(query).toBe( + 'CREATE TABLE "orders" (\n id INT PRIMARY KEY,\n user_id INT NOT NULL REFERENCES users(id),\n total DECIMAL(10, 2) NOT NULL\n);' + ); + }); + + it('should clone the query correctly', () => { + const original = new CreateTableQuery('products') + .ifNotExists() + .addColumns([ + Column('id', 'INT').primaryKey().notNull(), + Column('name', Varchar(100)).notNull() + ]); + + const cloned = original.clone(); + cloned.addColumns([ + Column('price', Decimal(10, 2)).notNull() + ]); + + const originalQuery = original.build(); + const clonedQuery = cloned.build(); + + expect(originalQuery).toBe( + 'CREATE TABLE IF NOT EXISTS "products" (\n id INT PRIMARY KEY,\n name VARCHAR(100) NOT NULL\n);' + ); + + expect(clonedQuery).toBe( + 'CREATE TABLE IF NOT EXISTS "products" (\n id INT PRIMARY KEY,\n name VARCHAR(100) NOT NULL,\n price DECIMAL(10, 2) NOT NULL\n);' + ); + }); + + it('should support schemas', () => { + const query = new CreateTableQuery('$schema.users') + .schema('public') + .addColumns([ + Column('id', 'INT').primaryKey().notNull(), + Column('name', Varchar(100)).notNull() + ]) + .build(); + + expect(query).toBe( + 'CREATE TABLE public."users" (\n id INT PRIMARY KEY,\n name VARCHAR(100) NOT NULL\n);' + ); + }); + + it('should throw an error if no columns are defined', () => { + const query = new CreateTableQuery('empty_table'); + expect(() => query.build()).toThrow('No columns defined for the table.'); + }); +}); From 850e38a01f1abe3f152ca818d99bd7341cf8536f Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 6 Oct 2025 00:14:36 -0300 Subject: [PATCH 45/74] feat/added scripts folder for storing some useful scripts especially for workflow operations (actions) --- .gitignore | 1 + .npmignore | 1 + scripts/update-jsr-version.sh | 31 +++++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100755 scripts/update-jsr-version.sh diff --git a/.gitignore b/.gitignore index 0e75fe5..397e09c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules dist +dist* coverage diff --git a/.npmignore b/.npmignore index 85056a0..e8b628c 100644 --- a/.npmignore +++ b/.npmignore @@ -6,4 +6,5 @@ package-lock.json .vscode .idea .DS_Store +scripts UPDATE-STRATEGY.md diff --git a/scripts/update-jsr-version.sh b/scripts/update-jsr-version.sh new file mode 100755 index 0000000..432ccb4 --- /dev/null +++ b/scripts/update-jsr-version.sh @@ -0,0 +1,31 @@ +#!/usr/bin/bash + +# This script updates the JSR version in the jsr.json file. +# Usage: ./update-jsr-version.sh + + +if [ "$#" -ne 1 ]; then + echo "Usage: $0 " + exit 1 +fi + +NEW_VERSION=$1 + +# Check if the version format is valid (basic check), 0.1.0 or 1.0.0-beta or 0.7.0-.20251002002 +if ! [[ "$NEW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then + echo "Error: Invalid version format. Expected format: X.Y.Z or X.Y.Z-suffix" + exit 1 +fi + +JSR_FILE="jsr.json" + +if [ ! -f "$JSR_FILE" ]; then + echo "Error: $JSR_FILE not found!" + exit 1 +fi + +# Update the version in jsr.json +sed -i.bak -E "s/\"version\": \"[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?\"/\"version\": \"$NEW_VERSION\"/" "$JSR_FILE" +rm "$JSR_FILE.bak" + +echo "Updated JSR version to $NEW_VERSION in $JSR_FILE" From f92b2f8eee41fa97be9dcc8be399df1c84b84486 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 6 Oct 2025 00:14:58 -0300 Subject: [PATCH 46/74] feat/added jsr support --- jsr.json | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 jsr.json diff --git a/jsr.json b/jsr.json new file mode 100644 index 0000000..fe5ac05 --- /dev/null +++ b/jsr.json @@ -0,0 +1,36 @@ +{ + "name": "@fragmenta/sqm", + "version": "0.7.0", + "description": "A lightweight and flexible query maker library for building raw SQL queries in JavaScript.", + "license": "Apache-2.0", + "exports": { + ".": "./src/index.ts", + "./queryUtils": "./src/queryUtils/index.ts", + "./types": "./src/types/index.ts", + "./ddl": "./src/queryKinds/ddl/index.ts", + "./dml": "./src/queryKinds/dml/index.ts", + "./ddl/table": "./src/queryKinds/ddl/table/index.ts" + }, + "publish": { + "include": [ + "src", + "README.md", + "LICENSE.md" + ], + "exclude": [ + "node_modules", + "dist", + "dist-qjs", + "**/*.test.ts", + "**/*.spec.ts", + "scripts", + "tsup.config.ts", + "tsup.config.qjs.ts", + "vitest.config.ts", + ".gitignore", + ".npmignore", + "package-lock.json", + "pnpm-lock.yaml" + ] + } +} From 2f0b60e1d70ef2cb01b936ec8d2ef72a8cfcb8a2 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 6 Oct 2025 00:16:34 -0300 Subject: [PATCH 47/74] feat/fix/added support for QuickJS and removed node as target since the target must be all popular runtimes and also added biome for linting --- biome.json | 48 ++++++++++++++++++ package.json | 16 ++++-- pnpm-lock.yaml | 124 +++++++++++++++++++++++++++++++++++++++++---- tsup.config.qjs.ts | 23 +++++++++ tsup.config.ts | 30 ++++++----- 5 files changed, 213 insertions(+), 28 deletions(-) create mode 100644 biome.json create mode 100644 tsup.config.qjs.ts diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..5bd63e8 --- /dev/null +++ b/biome.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.2.5/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false, + "includes": ["**/*.ts", "!**/*.test.ts", "!**/*.spec.ts"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "off", + "useIterableCallbackReturn": "off" + }, + "style": { + "noNonNullAssertion": "off" + }, + "complexity": { + "noBannedTypes": "off", + "noStaticOnlyClass": "off", + "useLiteralKeys": "off" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/package.json b/package.json index a38e444..c653e25 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,10 @@ "import": "./dist/index.mjs", "require": "./dist/index.js" }, + "./queryUtils": { + "import": "./dist/queryUtils/index.mjs", + "require": "./dist/queryUtils/index.js" + }, "./types": { "import": "./dist/types/index.mjs", "require": "./dist/types/index.js" @@ -37,10 +41,13 @@ "dev:deno": "concurrently \"pnpm run build:dev\" \"pnpm run start:dev:deno\"", "dev:bun": "concurrently \"pnpm run build:dev\" \"pnpm run start:dev:bun\"", "start": "node dist/index.js", - "build:prod": "tsup --config tsup.config.ts", + "build:prod:everything": "tsup --config tsup.config.ts", + "build:prod:qjs": "tsup --config tsup.config.qjs.ts", + "build:prod": "concurrently \"pnpm run build:prod:everything\" \"pnpm run build:prod:qjs\"", "test": "vitest run", "test:watch": "vitest", - "test:coverage": "vitest run --coverage" + "test:coverage": "vitest run --coverage", + "publish:jsr:dry": "jsr publish --dry-run --allow-dirty" }, "files": [ "dist", @@ -69,11 +76,13 @@ "author": "Nicolas R. M. Dias ", "license": "Apache-2.0", "devDependencies": { + "@biomejs/biome": "^2.2.5", "@swc/core": "^1.13.5", "@vitest/coverage-v8": "^3.2.4", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "concurrently": "^9.2.1", + "jsr": "^0.13.5", "nodemon": "^3.1.10", "terser": "^5.44.0", "tslib": "^2.8.1", @@ -83,9 +92,6 @@ "wait-on": "^9.0.1", "zod": "^4.1.11" }, - "dependencies": { - "ts-pattern": "^5.8.0" - }, "peerDependencies": { "class-transformer": ">=0.5.0", "class-validator": ">=0.14.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a10e650..83503b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,11 +7,10 @@ settings: importers: .: - dependencies: - ts-pattern: - specifier: ^5.8.0 - version: 5.8.0 devDependencies: + '@biomejs/biome': + specifier: ^2.2.5 + version: 2.2.5 '@swc/core': specifier: ^1.13.5 version: 1.13.5 @@ -27,6 +26,9 @@ importers: concurrently: specifier: ^9.2.1 version: 9.2.1 + jsr: + specifier: ^0.13.5 + version: 0.13.5 nodemon: specifier: ^3.1.10 version: 3.1.10 @@ -79,6 +81,59 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@biomejs/biome@2.2.5': + resolution: {integrity: sha512-zcIi+163Rc3HtyHbEO7CjeHq8DjQRs40HsGbW6vx2WI0tg8mYQOPouhvHSyEnCBAorfYNnKdR64/IxO7xQ5faw==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.2.5': + resolution: {integrity: sha512-MYT+nZ38wEIWVcL5xLyOhYQQ7nlWD0b/4mgATW2c8dvq7R4OQjt/XGXFkXrmtWmQofaIM14L7V8qIz/M+bx5QQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.2.5': + resolution: {integrity: sha512-FLIEl73fv0R7dI10EnEiZLw+IMz3mWLnF95ASDI0kbx6DDLJjWxE5JxxBfmG+udz1hIDd3fr5wsuP7nwuTRdAg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.2.5': + resolution: {integrity: sha512-5Ov2wgAFwqDvQiESnu7b9ufD1faRa+40uwrohgBopeY84El2TnBDoMNXx6iuQdreoFGjwW8vH6k68G21EpNERw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.2.5': + resolution: {integrity: sha512-5DjiiDfHqGgR2MS9D+AZ8kOfrzTGqLKywn8hoXpXXlJXIECGQ32t+gt/uiS2XyGBM2XQhR6ztUvbjZWeccFMoQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.2.5': + resolution: {integrity: sha512-AVqLCDb/6K7aPNIcxHaTQj01sl1m989CJIQFQEaiQkGr2EQwyOpaATJ473h+nXDUuAcREhccfRpe/tu+0wu0eQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.2.5': + resolution: {integrity: sha512-fq9meKm1AEXeAWan3uCg6XSP5ObA6F/Ovm89TwaMiy1DNIwdgxPkNwxlXJX8iM6oRbFysYeGnT0OG8diCWb9ew==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.2.5': + resolution: {integrity: sha512-xaOIad4wBambwJa6mdp1FigYSIF9i7PCqRbvBqtIi9y29QtPVQ13sDGtUnsRoe6SjL10auMzQ6YAe+B3RpZXVg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.2.5': + resolution: {integrity: sha512-F/jhuXCssPFAuciMhHKk00xnCAxJRS/pUzVfXYmOMUp//XW7mO6QeCjsjvnm8L4AO/dG2VOB0O+fJPiJ2uXtIw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + '@esbuild/aix-ppc64@0.25.9': resolution: {integrity: sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==} engines: {node: '>=18'} @@ -867,6 +922,10 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + jsr@0.13.5: + resolution: {integrity: sha512-qQP20ZcG28pYes7bCq3uuvixl1TL1EpJzwLPfoQadSyWk9j2AID66qhW8+aXpRDRFDvDkXFnONsSRhpnnQAupg==} + hasBin: true + libphonenumber-js@1.12.17: resolution: {integrity: sha512-bsxi8FoceAYR/bjHcLYc2ShJ/aVAzo5jaxAYiMHF0BD+NTp47405CGuPNKYpw+lHadN9k/ClFGc9X5vaZswIrA==} @@ -943,6 +1002,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-stream-zip@1.15.0: + resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} + engines: {node: '>=0.12.0'} + nodemon@3.1.10: resolution: {integrity: sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==} engines: {node: '>=10'} @@ -1048,6 +1111,10 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + semiver@1.1.0: + resolution: {integrity: sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==} + engines: {node: '>=6'} + semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -1190,9 +1257,6 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - ts-pattern@5.8.0: - resolution: {integrity: sha512-kIjN2qmWiHnhgr5DAkAafF9fwb0T5OhMVSWrm8XEdTFnX6+wfXwYOFjeF86UZ54vduqiR7BfqScFmXSzSaH8oA==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1369,6 +1433,41 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@biomejs/biome@2.2.5': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.2.5 + '@biomejs/cli-darwin-x64': 2.2.5 + '@biomejs/cli-linux-arm64': 2.2.5 + '@biomejs/cli-linux-arm64-musl': 2.2.5 + '@biomejs/cli-linux-x64': 2.2.5 + '@biomejs/cli-linux-x64-musl': 2.2.5 + '@biomejs/cli-win32-arm64': 2.2.5 + '@biomejs/cli-win32-x64': 2.2.5 + + '@biomejs/cli-darwin-arm64@2.2.5': + optional: true + + '@biomejs/cli-darwin-x64@2.2.5': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.2.5': + optional: true + + '@biomejs/cli-linux-arm64@2.2.5': + optional: true + + '@biomejs/cli-linux-x64-musl@2.2.5': + optional: true + + '@biomejs/cli-linux-x64@2.2.5': + optional: true + + '@biomejs/cli-win32-arm64@2.2.5': + optional: true + + '@biomejs/cli-win32-x64@2.2.5': + optional: true + '@esbuild/aix-ppc64@0.25.9': optional: true @@ -2052,6 +2151,11 @@ snapshots: js-tokens@9.0.1: {} + jsr@0.13.5: + dependencies: + node-stream-zip: 1.15.0 + semiver: 1.1.0 + libphonenumber-js@1.12.17: {} lilconfig@3.1.3: {} @@ -2119,6 +2223,8 @@ snapshots: nanoid@3.3.11: {} + node-stream-zip@1.15.0: {} + nodemon@3.1.10: dependencies: chokidar: 3.6.0 @@ -2222,6 +2328,8 @@ snapshots: dependencies: tslib: 2.8.1 + semiver@1.1.0: {} + semver@7.7.2: {} shebang-command@2.0.0: @@ -2353,8 +2461,6 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-pattern@5.8.0: {} - tslib@2.8.1: {} tsup@8.5.0(@swc/core@1.13.5)(postcss@8.5.6)(typescript@5.9.3): diff --git a/tsup.config.qjs.ts b/tsup.config.qjs.ts new file mode 100644 index 0000000..c4ddd91 --- /dev/null +++ b/tsup.config.qjs.ts @@ -0,0 +1,23 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + name: "For QuickJS", + entry: [ + "src/index.ts", + "src/types/index.ts", + "src/queryUtils/index.ts", + "src/queryKinds/dml/index.ts", + "src/queryKinds/ddl/index.ts", + "src/queryKinds/ddl/table/index.ts", + ], + target: ["es2023"], + platform: "neutral", + format: ["esm"], + ignoreWatch: ["**/*.test.ts", "**/*.spec.ts"], + dts: true, + outDir: "dist-qjs", + clean: true, + treeshake: "smallest", + minifyIdentifiers: true, + minifySyntax: true, +}); diff --git a/tsup.config.ts b/tsup.config.ts index 96bf0e4..a2d3700 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,21 +1,23 @@ -import { defineConfig } from 'tsup' +import { defineConfig } from "tsup"; export default defineConfig({ + name: "For everything else", entry: [ - 'src/index.ts', - 'src/types/index.ts', - 'src/queryUtils/index.ts', - 'src/queryKinds/dml/index.ts', - 'src/queryKinds/ddl/index.ts', - 'src/queryKinds/ddl/table/index.ts', + "src/index.ts", + "src/types/index.ts", + "src/queryUtils/index.ts", + "src/queryKinds/dml/index.ts", + "src/queryKinds/ddl/index.ts", + "src/queryKinds/ddl/table/index.ts", ], - target: ['esnext', 'node21'], - format: ['cjs', 'esm'], - ignoreWatch: ['**/*.test.ts', '**/*.spec.ts'], + target: ["esnext"], + platform: "neutral", + format: ["cjs", "esm"], + ignoreWatch: ["**/*.test.ts", "**/*.spec.ts"], dts: true, - outDir: 'dist', + outDir: "dist", clean: true, - treeshake: 'smallest', + treeshake: "smallest", minifyIdentifiers: true, - minifySyntax: true -}) + minifySyntax: true, +}); From 2798678162e1b677d98bb645f55e9ad945605cfa Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 6 Oct 2025 00:17:47 -0300 Subject: [PATCH 48/74] fix/added some configs to remove errors from when overriding or return implicitly from functions --- tsconfig.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tsconfig.json b/tsconfig.json index 58086f5..85f74a3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,11 @@ "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": false, + // Needs explicit return types on functions and class methods + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, // Style Options "noUnusedLocals": true, From b950e03f25759f3807e34c750c49e531c7ecc377 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 6 Oct 2025 00:18:41 -0300 Subject: [PATCH 49/74] refact/passed biome check and removed slow types --- src/cteMaker.ts | 124 ++-- src/deepEqual.ts | 67 +- src/getOptionalPackages.ts | 67 +- src/index.ts | 10 +- src/queryKinds/ddl/ddlQueryDefinition.ts | 227 +++---- src/queryKinds/ddl/index.ts | 47 +- src/queryKinds/ddl/table/Alter.ts | 178 ++--- src/queryKinds/ddl/table/Create.ts | 92 ++- src/queryKinds/ddl/table/Drop.ts | 52 +- src/queryKinds/ddl/table/index.ts | 16 +- .../ddl/table/tableColumnDefinition.ts | 12 +- src/queryKinds/dml/delete.ts | 273 ++++---- src/queryKinds/dml/dmlQueryDefinition.ts | 568 ++++++++-------- src/queryKinds/dml/index.ts | 24 +- src/queryKinds/dml/insert.ts | 258 ++++---- src/queryKinds/dml/select.ts | 575 +++++++++-------- src/queryKinds/dml/union.ts | 457 ++++++------- src/queryKinds/dml/update.ts | 398 ++++++------ src/queryMaker.ts | 136 ++-- src/queryUtils/Column.ts | 290 +++++---- src/queryUtils/index.ts | 4 +- src/searchModule.ts | 141 ++-- src/signal.ts | 110 ++-- src/sqlEscaper.ts | 182 +++--- src/statementMaker.ts | 606 +++++++++--------- src/types/ColumnTypes.ts | 105 ++- src/types/ColumnValue.ts | 7 +- src/types/ForeignKey.ts | 16 +- src/types/Join.ts | 19 +- src/types/OrderBy.ts | 19 +- src/types/QueryKind.ts | 2 - src/types/SetValue.ts | 9 +- src/types/UsingTable.ts | 5 +- src/types/index.ts | 32 +- src/types/sqlFlavor.ts | 11 +- vitest.config.ts | 28 +- 36 files changed, 2641 insertions(+), 2526 deletions(-) diff --git a/src/cteMaker.ts b/src/cteMaker.ts index 19efa72..f7c73c6 100644 --- a/src/cteMaker.ts +++ b/src/cteMaker.ts @@ -1,133 +1,133 @@ -import QueryDefinition from "./queryKinds/dml/dmlQueryDefinition.js"; +import type QueryDefinition from "./queryKinds/dml/dmlQueryDefinition.js"; import SelectQuery from "./queryKinds/dml/select.js"; /** - * Cte represents a Common Table Expression (CTE) in SQL. - * It allows defining a named subquery that can be referenced within other queries. - */ + * Cte represents a Common Table Expression (CTE) in SQL. + * It allows defining a named subquery that can be referenced within other queries. + */ export class Cte { /** - * The name of the CTE. - */ + * The name of the CTE. + */ private name: string; - + /** - * The query that defines the CTE. - */ + * The query that defines the CTE. + */ private query: QueryDefinition; /** - * Indicates whether the CTE is recursive. - */ + * Indicates whether the CTE is recursive. + */ private recursiveCte: boolean; /** - * Creates an instance of Cte. - * @param name - The name of the CTE. - * @param query - The query that defines the CTE. - * @param recursive - Whether the CTE is recursive (default is false). - */ + * Creates an instance of Cte. + * @param name - The name of the CTE. + * @param query - The query that defines the CTE. + * @param recursive - Whether the CTE is recursive (default is false). + */ constructor( - name?: string, + name?: string, query?: QueryDefinition, - recursive: boolean = false + recursive: boolean = false, ) { - this.name = name || ''; + this.name = name || ""; this.query = query || new SelectQuery(); this.recursiveCte = recursive; } /** - * Marks the CTE as recursive. - * @returns The current Cte instance for method chaining. - */ + * Marks the CTE as recursive. + * @returns The current Cte instance for method chaining. + */ public recursive(): this { this.recursiveCte = true; return this; } /** - * Sets the name of the CTE. - * The name should be a valid SQL identifier. - * @param name - The name of the CTE. - * @returns The current Cte instance for method chaining. - */ + * Sets the name of the CTE. + * The name should be a valid SQL identifier. + * @param name - The name of the CTE. + * @returns The current Cte instance for method chaining. + */ public as(name: string): this { this.name = name; return this; } /** - * Sets the query that defines the CTE. - * The query should be an instance of a class that extends QueryDefinition (e.g., SelectQuery). - * @param query - The query defining the CTE. - * @returns The current Cte instance for method chaining. - */ + * Sets the query that defines the CTE. + * The query should be an instance of a class that extends QueryDefinition (e.g., SelectQuery). + * @param query - The query defining the CTE. + * @returns The current Cte instance for method chaining. + */ public withQuery(query: SelectQuery): this { this.query = query; return this; } /** - * Builds the SQL string for the CTE, including its name and query. - * Returns an object containing the SQL text and associated parameter values. - * @returns An object with the SQL text and parameter values. - */ + * Builds the SQL string for the CTE, including its name and query. + * Returns an object containing the SQL text and associated parameter values. + * @returns An object with the SQL text and parameter values. + */ public build(): { text: string; values: any[] } { - const recursiveStr = this.recursiveCte ? 'RECURSIVE ' : ''; + const recursiveStr = this.recursiveCte ? "RECURSIVE " : ""; const query = this.query.build(); return { text: `${recursiveStr}${this.name} AS (\n${query.text}\n)`, - values: query.values + values: query.values, }; } } /** - * CteMaker helps in constructing SQL queries with multiple Common Table Expressions (CTEs). - * It manages a list of CTEs and builds the final SQL string with proper parameter indexing. - */ + * CteMaker helps in constructing SQL queries with multiple Common Table Expressions (CTEs). + * It manages a list of CTEs and builds the final SQL string with proper parameter indexing. + */ export default class CteMaker { /** - * The list of CTEs to be included in the SQL query. - */ + * The list of CTEs to be included in the SQL query. + */ private ctes: Cte[] = []; /** - * Creates an instance of CteMaker. - * @param ctes - An optional array of CTEs to initialize the CteMaker with. - */ + * Creates an instance of CteMaker. + * @param ctes - An optional array of CTEs to initialize the CteMaker with. + */ constructor(...ctes: Cte[]) { this.ctes = ctes; } /** - * Adds a new CTE to the list. - * @param cte - The CTE to be added. - * @returns The current CteMaker instance for method chaining. - */ + * Adds a new CTE to the list. + * @param cte - The CTE to be added. + * @returns The current CteMaker instance for method chaining. + */ public addCte(cte: Cte): this { this.ctes.push(cte); return this; } /** - * Adds multiple CTEs to the list. - * @param ctes - An array of CTEs to be added. - * @returns The current CteMaker instance for method chaining. - */ + * Adds multiple CTEs to the list. + * @param ctes - An array of CTEs to be added. + * @returns The current CteMaker instance for method chaining. + */ public addCtes(ctes: Cte[]): this { this.ctes.push(...ctes); return this; } /** - * Builds the SQL string for all CTEs, renumbering parameters to ensure uniqueness. - * @returns An object with the SQL text and parameter values. - */ + * Builds the SQL string for all CTEs, renumbering parameters to ensure uniqueness. + * @returns An object with the SQL text and parameter values. + */ public build(): { text: string; values: any[] } { if (this.ctes.length === 0) { - return { text: '', values: [] }; + return { text: "", values: [] }; } const cteResults: Array<{ text: string; values: any[] }> = []; @@ -136,7 +136,7 @@ export default class CteMaker { // Build each CTE and renumber its parameters for (const cte of this.ctes) { const builtCte = cte.build(); - + // Renumber parameters in this CTE's text const renumberedText = builtCte.text.replace(/\$(\d+)/g, () => { return `$${paramIndex++}`; @@ -144,13 +144,13 @@ export default class CteMaker { cteResults.push({ text: renumberedText, - values: builtCte.values + values: builtCte.values, }); } return { - text: `WITH ${cteResults.map(r => r.text).join(', ')}`, - values: cteResults.flatMap(r => r.values) + text: `WITH ${cteResults.map((r) => r.text).join(", ")}`, + values: cteResults.flatMap((r) => r.values), }; } } diff --git a/src/deepEqual.ts b/src/deepEqual.ts index f3287aa..797f1a7 100644 --- a/src/deepEqual.ts +++ b/src/deepEqual.ts @@ -1,41 +1,42 @@ - /** - * This function extracts parameter names from a function definition. - * Useful for comparing function signatures. - * @param func - The function from which to extract parameter names. - * @returns An array of parameter names. - */ + * This function extracts parameter names from a function definition. + * Useful for comparing function signatures. + * @param func - The function from which to extract parameter names. + * @returns An array of parameter names. + */ function getFunctionParameters(func: Function) { - const funcStr = func.toString(); - const match = funcStr.match(/\(([^)]*)\)/); - if (!match) return []; - - return match[1] - ?.split(',') - .map(param => param?.trim()?.split('=')?.[0]?.trim()) - .filter(param => param !== '') || []; + const funcStr = func.toString(); + const match = funcStr.match(/\(([^)]*)\)/); + if (!match) return []; + + return ( + match[1] + ?.split(",") + .map((param) => param?.trim()?.split("=")?.[0]?.trim()) + .filter((param) => param !== "") || [] + ); } /** - * Compares the parameters of two functions to see if they match. - * @param fn1 - The first function to compare. - * @param fn2 - The second function to compare. - * @returns True if the functions have the same parameters, false otherwise. - */ + * Compares the parameters of two functions to see if they match. + * @param fn1 - The first function to compare. + * @param fn2 - The second function to compare. + * @returns True if the functions have the same parameters, false otherwise. + */ function compareParameters(fn1: Function, fn2: Function): boolean { - const params1 = getFunctionParameters(fn1); - const params2 = getFunctionParameters(fn2); - - return JSON.stringify(params1) === JSON.stringify(params2); + const params1 = getFunctionParameters(fn1); + const params2 = getFunctionParameters(fn2); + + return JSON.stringify(params1) === JSON.stringify(params2); } /** - * Deeply compares two values for equality. - * Handles primitives, arrays, objects, and functions (by comparing their string representations and parameters). - * @param a - The first value to compare. - * @param b - The second value to compare. - * @returns True if the values are deeply equal, false otherwise. - */ + * Deeply compares two values for equality. + * Handles primitives, arrays, objects, and functions (by comparing their string representations and parameters). + * @param a - The first value to compare. + * @param b - The second value to compare. + * @returns True if the values are deeply equal, false otherwise. + */ export default function deepEqual(a: any, b: any): boolean { if (a === b) return true; @@ -45,13 +46,13 @@ export default function deepEqual(a: any, b: any): boolean { if (typeof a !== typeof b) return false; - if (typeof a === 'function' && typeof b === 'function') { - const funcA = a.toString().replace(/,line:\d+/g, ''); - const funcB = b.toString().replace(/,line:\d+/g, ''); + if (typeof a === "function" && typeof b === "function") { + const funcA = a.toString().replace(/,line:\d+/g, ""); + const funcB = b.toString().replace(/,line:\d+/g, ""); return funcA === funcB && compareParameters(a, b); } - if (typeof a !== 'object' || typeof b !== 'object') { + if (typeof a !== "object" || typeof b !== "object") { return a === b; } diff --git a/src/getOptionalPackages.ts b/src/getOptionalPackages.ts index f57a512..680206c 100644 --- a/src/getOptionalPackages.ts +++ b/src/getOptionalPackages.ts @@ -1,16 +1,15 @@ - /** - * Type definition for the object returned by getClassValidator function. - * Contains the class-validator and class-transformer modules. - */ + * Type definition for the object returned by getClassValidator function. + * Contains the class-validator and class-transformer modules. + */ export type ClassValidatorModule = { /** - * The class-validator module. - */ + * The class-validator module. + */ classValidator: typeof import("class-validator"); /** - * The class-transformer module. - */ + * The class-transformer module. + */ classTransformer: typeof import("class-transformer"); }; @@ -18,52 +17,50 @@ export type ClassValidatorModule = { // Avoids costly re-imports. /** - * Cache for the Zod module. - */ -let zodCache: typeof import('zod') | null = null; + * Cache for the Zod module. + */ +let zodCache: typeof import("zod") | null = null; /** - * Cache for the class-validator and class-transformer modules. - */ + * Cache for the class-validator and class-transformer modules. + */ let classValidatorCache: ClassValidatorModule | null = null; /** - * Dynamically imports the Zod library. - * @returns The Zod module. - * @throws if Zod is not installed. - */ -async function getZod(): Promise { + * Dynamically imports the Zod library. + * @returns The Zod module. + * @throws if Zod is not installed. + */ +async function getZod(): Promise { try { - zodCache = await import('zod'); + zodCache = await import("zod"); return zodCache; } catch { throw new Error( 'Zod is not installed. Please install it with "npm install zod" or "yarn add zod".', - ) + ); } } /** - * Dynamically imports the class-validator and class-transformer libraries. - * @returns An object containing the class-validator and class-transformer modules. - * @throws if either class-validator or class-transformer is not installed. - */ + * Dynamically imports the class-validator and class-transformer libraries. + * @returns An object containing the class-validator and class-transformer modules. + * @throws if either class-validator or class-transformer is not installed. + */ async function getClassValidator(): Promise { try { - const classValidator = await import('class-validator'); - const classTransformer = await import('class-transformer'); + const classValidator = await import("class-validator"); + const classTransformer = await import("class-transformer"); classValidatorCache = { classValidator, classTransformer }; return classValidatorCache; } catch { - throw new Error([ - 'class-validator and class-transformer are not installed.', - 'Please install them with "npm install class-validator class-transformer" or "yarn add class-validator class-transformer".' - ].join(' ')); + throw new Error( + [ + "class-validator and class-transformer are not installed.", + 'Please install them with "npm install class-validator class-transformer" or "yarn add class-validator class-transformer".', + ].join(" "), + ); } } - -export { - getZod, - getClassValidator, -} +export { getZod, getClassValidator }; diff --git a/src/index.ts b/src/index.ts index 9bb1d29..c9b29d2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,7 @@ import Query from "./queryMaker.js"; import Statement from "./statementMaker.js"; -export * from './cteMaker.js' -export { default as CteMaker } from './cteMaker.js' -export { - Query, - Statement -} +export * from "./cteMaker.js"; +export { default as CteMaker } from "./cteMaker.js"; + +export { Query, Statement }; diff --git a/src/queryKinds/ddl/ddlQueryDefinition.ts b/src/queryKinds/ddl/ddlQueryDefinition.ts index 1fe46a4..05487d8 100644 --- a/src/queryKinds/ddl/ddlQueryDefinition.ts +++ b/src/queryKinds/ddl/ddlQueryDefinition.ts @@ -1,24 +1,24 @@ import SqlEscaper from "../../sqlEscaper.js"; -import QueryKind from "../../types/QueryKind"; +import type QueryKind from "../../types/QueryKind"; import sqlFlavor from "../../types/sqlFlavor.js"; /** - * An array of function names that can be used to execute SQL queries. - * These functions are commonly found in database client libraries. - */ -const functionNames = ['execute', 'query', 'run', 'all', 'get'] as const; + * An array of function names that can be used to execute SQL queries. + * These functions are commonly found in database client libraries. + */ +const functionNames = ["execute", "query", "run", "all", "get"] as const; /** - * FunctionDeclaration type defines the signature for functions that execute SQL queries. - * It takes a query string and an array of parameters, and returns a promise that resolves - * with any result. - */ + * FunctionDeclaration type defines the signature for functions that execute SQL queries. + * It takes a query string and an array of parameters, and returns a promise that resolves + * with any result. + */ type FunctionDeclaration = (query: string, params?: any[]) => Promise; /** - * QueryExecutorObject interface defines the structure for an object that can execute SQL queries. - * It includes optional methods for executing queries in different ways, as well as an optional manager property. - */ + * QueryExecutorObject interface defines the structure for an object that can execute SQL queries. + * It includes optional methods for executing queries in different ways, as well as an optional manager property. + */ interface QueryExecutorObject { execute?: FunctionDeclaration; query?: FunctionDeclaration; @@ -29,175 +29,176 @@ interface QueryExecutorObject { } /** - * QueryExecutor type can be either a QueryExecutorObject or a function that executes a query. - */ -type QueryExecutor = - QueryExecutorObject - | FunctionDeclaration; + * QueryExecutor type can be either a QueryExecutorObject or a function that executes a query. + */ +type QueryExecutor = QueryExecutorObject | FunctionDeclaration; /** - * Abstract class DdlQueryDefinition serves as a blueprint for defining DDL (Data Definition Language) query structures. - * It is intended to be extended by specific DDL query classes such as CreateTableQuery, AlterTableQuery, and DropTableQuery. - * This class will provide common properties and methods that are shared among all DDL query types and also some abstract methods - * that must be implemented by the subclasses to ensure they adhere to a consistent interface for building DDL queries. - */ + * Abstract class DdlQueryDefinition serves as a blueprint for defining DDL (Data Definition Language) query structures. + * It is intended to be extended by specific DDL query classes such as CreateTableQuery, AlterTableQuery, and DropTableQuery. + * This class will provide common properties and methods that are shared among all DDL query types and also some abstract methods + * that must be implemented by the subclasses to ensure they adhere to a consistent interface for building DDL queries. + */ export default abstract class DdlQueryDefinition { /** The name of the table involved in the DDL operation. */ - protected tableName: string = ''; + protected tableName: string = ""; /** The built DDL query string, initialized to null. */ protected builtQuery: string | string[] | null = null; /** - * Sets the name of the table for the DDL operation. - * @param name - The name of the table. - * @returns The current instance for method chaining. - */ + * Sets the name of the table for the DDL operation. + * @param name - The name of the table. + * @returns The current instance for method chaining. + */ public table(name: string | null = null): this { - this.tableName = name ? SqlEscaper.escapeTableName(name, this.flavor) : ''; + this.tableName = name ? SqlEscaper.escapeTableName(name, this.flavor) : ""; return this; } /** - * Checks if the DDL query has been built. - * @returns True if the query has been built, false otherwise. - */ + * Checks if the DDL query has been built. + * @returns True if the query has been built, false otherwise. + */ public isDone(): boolean { return this.builtQuery !== null; } /** - * Abstract method to build the DDL query string. - * This method must be implemented by subclasses to generate the appropriate SQL statement. - * @param deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). - * @returns The constructed DDL query string. - */ + * Abstract method to build the DDL query string. + * This method must be implemented by subclasses to generate the appropriate SQL statement. + * @param deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). + * @returns The constructed DDL query string. + */ public abstract build(deepAnalysis?: boolean): string | string[]; /** - * Builds an EXPLAIN query for the DDL operation. - * This method prefixes the built DDL query with "EXPLAIN". - * @param deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). - * @returns The constructed EXPLAIN query string. - */ + * Builds an EXPLAIN query for the DDL operation. + * This method prefixes the built DDL query with "EXPLAIN". + * @param deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). + * @returns The constructed EXPLAIN query string. + */ public buildExplain(deepAnalysis?: boolean): string { return `EXPLAIN ${this.build(deepAnalysis)}`; } /** - * Builds an EXPLAIN ANALYZE query for the DDL operation. - * This method prefixes the built DDL query with "EXPLAIN ANALYZE". - * @param deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). - * @returns The constructed EXPLAIN ANALYZE query string. - */ + * Builds an EXPLAIN ANALYZE query for the DDL operation. + * This method prefixes the built DDL query with "EXPLAIN ANALYZE". + * @param deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). + * @returns The constructed EXPLAIN ANALYZE query string. + */ public buildExplainAnalyze(deepAnalysis?: boolean): string { return `EXPLAIN ANALYZE ${this.build(deepAnalysis)}`; } /** - * Utility method to add indentation to each line of a given string. - * This is useful for formatting multi-line SQL queries for better readability. - * @param str - The input string to be indented. - * @param spaces - The number of spaces to indent each line (default is 0). - * @returns The indented string. - */ + * Utility method to add indentation to each line of a given string. + * This is useful for formatting multi-line SQL queries for better readability. + * @param str - The input string to be indented. + * @param spaces - The number of spaces to indent each line (default is 0). + * @returns The indented string. + */ protected spaceLines(str: string, spaces: number = 0): string { - const space = ' '.repeat(spaces); - return str.split('\n').map(line => space + line).join('\n'); + const space = " ".repeat(spaces); + return str + .split("\n") + .map((line) => space + line) + .join("\n"); } /** - * Abstract method to clone the current DDL query definition instance. - * This method must be implemented by subclasses to return a new instance - * that is a copy of the current instance. - * @returns A new instance of the DDL query definition. - */ + * Abstract method to clone the current DDL query definition instance. + * This method must be implemented by subclasses to return a new instance + * that is a copy of the current instance. + * @returns A new instance of the DDL query definition. + */ public abstract clone(): DdlQueryDefinition; /** - * Abstract method to reset the state of the DDL query definition. - * This method must be implemented by subclasses to clear any set properties - * and return the instance to its initial state. - * @returns The current instance for method chaining. - */ + * Abstract method to reset the state of the DDL query definition. + * This method must be implemented by subclasses to clear any set properties + * and return the instance to its initial state. + * @returns The current instance for method chaining. + */ public abstract reset(): this; /** - * Abstract method to convert the DDL query definition to its SQL string representation. - * This method must be implemented by subclasses to return the SQL string - * that represents the DDL operation defined by the instance. - * @returns The SQL string representation of the DDL query. - */ + * Abstract method to convert the DDL query definition to its SQL string representation. + * This method must be implemented by subclasses to return the SQL string + * that represents the DDL operation defined by the instance. + * @returns The SQL string representation of the DDL query. + */ public abstract toSQL(): string | string[]; /** - * Abstract getter to retrieve the kind of DDL query. - * This property must be implemented by subclasses to return the specific - * type of DDL operation (e.g., 'CREATE', 'ALTER', 'DROP'). - */ + * Abstract getter to retrieve the kind of DDL query. + * This property must be implemented by subclasses to return the specific + * type of DDL operation (e.g., 'CREATE', 'ALTER', 'DROP'). + */ public abstract get kind(): QueryKind; /** - * The SQL flavor to use for escaping identifiers. - * Default is PostgreSQL. - */ + * The SQL flavor to use for escaping identifiers. + * Default is PostgreSQL. + */ protected flavor: sqlFlavor = sqlFlavor.postgres; /** - * Schemas to be used in the query. - * This is useful for databases that support multiple schemas. - * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. - */ + * Schemas to be used in the query. + * This is useful for databases that support multiple schemas. + * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. + */ protected schemas: string[] = []; /** - * Sets the SQL flavor for escaping identifiers. - * @param flavor The SQL flavor to set. - * @returns The current DmlQueryDefinition instance for chaining. - */ + * Sets the SQL flavor for escaping identifiers. + * @param flavor The SQL flavor to set. + * @returns The current DmlQueryDefinition instance for chaining. + */ public sqlFlavor(flavor: sqlFlavor) { this.flavor = flavor; return this; } /** - * Set schemas to be used in the query. - * This is useful for databases that support multiple schemas. - * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. - * @param schemas The schemas to set. - * @returns The current SelectQuery instance for chaining. - */ + * Set schemas to be used in the query. + * This is useful for databases that support multiple schemas. + * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. + * @param schemas The schemas to set. + * @returns The current SelectQuery instance for chaining. + */ public schema(...schemas: string[]): this { this.schemas = schemas; return this; } /** - * Adds schemas to the existing list of schemas. - * This is useful for databases that support multiple schemas. - * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. - * @param schemas The schemas to add. - * @returns The current SelectQuery instance for chaining. - */ + * Adds schemas to the existing list of schemas. + * This is useful for databases that support multiple schemas. + * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. + * @param schemas The schemas to add. + * @returns The current SelectQuery instance for chaining. + */ public addSchema(...schemas: string[]): this { this.schemas.push(...schemas); return this; } /** - * Executes the built SQL query using the provided query executor. - * The query executor can be a function or an object with methods to execute the query. - * The optional noManager parameter can be used to bypass the manager property if present. - * @param queryExecutor The executor to run the SQL query. - * @param noManager If true, bypasses the manager property of the executor object. - * @returns A promise that resolves when the query execution is complete. - * @throws An error if the provided query executor is invalid. - */ + * Executes the built SQL query using the provided query executor. + * The query executor can be a function or an object with methods to execute the query. + * The optional noManager parameter can be used to bypass the manager property if present. + * @param queryExecutor The executor to run the SQL query. + * @param noManager If true, bypasses the manager property of the executor object. + * @returns A promise that resolves when the query execution is complete. + * @throws An error if the provided query executor is invalid. + */ public async execute( queryExecutor: QueryExecutor, - noManager: boolean = false + noManager: boolean = false, ): Promise { - if (typeof queryExecutor === 'function') { + if (typeof queryExecutor === "function") { const builtQuery = this.build(); if (Array.isArray(builtQuery)) { for (const query of builtQuery) { @@ -209,9 +210,13 @@ export default abstract class DdlQueryDefinition { return; } - if (!noManager && 'manager' in queryExecutor && typeof queryExecutor?.manager === 'object') { + if ( + !noManager && + "manager" in queryExecutor && + typeof queryExecutor?.manager === "object" + ) { for (const functionName of functionNames) { - if (typeof queryExecutor.manager[functionName] === 'function') { + if (typeof queryExecutor.manager[functionName] === "function") { const builtQuery = this.build(); if (Array.isArray(builtQuery)) { for (const query of builtQuery) { @@ -223,9 +228,9 @@ export default abstract class DdlQueryDefinition { return; } } - } else if(typeof queryExecutor === 'object') { + } else if (typeof queryExecutor === "object") { for (const functionName of functionNames) { - if (typeof queryExecutor[functionName] === 'function') { + if (typeof queryExecutor[functionName] === "function") { const builtQuery = this.build(); if (Array.isArray(builtQuery)) { for (const query of builtQuery) { @@ -239,6 +244,6 @@ export default abstract class DdlQueryDefinition { } } - throw new Error('Invalid query executor provided.'); + throw new Error("Invalid query executor provided."); } } diff --git a/src/queryKinds/ddl/index.ts b/src/queryKinds/ddl/index.ts index 350bb1f..288f023 100644 --- a/src/queryKinds/ddl/index.ts +++ b/src/queryKinds/ddl/index.ts @@ -1,56 +1,55 @@ -import sqlFlavor from '../../types/sqlFlavor.js'; -import * as tables from './table/index.js'; +import sqlFlavor from "../../types/sqlFlavor.js"; +import * as tables from "./table/index.js"; -export * from './ddlQueryDefinition.js'; -export { default as DdlQueryDefinition } from './ddlQueryDefinition.js'; +export * from "./ddlQueryDefinition.js"; +export { default as DdlQueryDefinition } from "./ddlQueryDefinition.js"; /** - * Class representing a Table for DDL operations. - * This class provides methods to initiate DDL queries such as CREATE TABLE. - */ + * Class representing a Table for DDL operations. + * This class provides methods to initiate DDL queries such as CREATE TABLE. + */ export class Table { - constructor( private deepAnalysis: boolean = false, - private flavor = sqlFlavor.postgres + private flavor = sqlFlavor.postgres, ) {} /** - * Initiates a new CREATE TABLE query. - * @returns A new CreateTableQuery instance with a build method that respects the deepAnalysis setting. - */ - public get create() { + * Initiates a new CREATE TABLE query. + * @returns A new CreateTableQuery instance with a build method that respects the deepAnalysis setting. + */ + public get create(): tables.CreateTableQuery { const query = new tables.CreateTableQuery(); query.sqlFlavor(this.flavor); query.build = (deepAnalysis: boolean = this.deepAnalysis) => { return tables.CreateTableQuery.prototype.build.call(query, deepAnalysis); - } + }; return query; } /** - * Initiates a new DROP TABLE query. - * @returns A new DropTableQuery instance with a build method that respects the deepAnalysis setting. - */ - public get drop() { + * Initiates a new DROP TABLE query. + * @returns A new DropTableQuery instance with a build method that respects the deepAnalysis setting. + */ + public get drop(): tables.DropTableQuery { const query = new tables.DropTableQuery(); query.sqlFlavor(this.flavor); query.build = (deepAnalysis: boolean = this.deepAnalysis) => { return tables.DropTableQuery.prototype.build.call(query, deepAnalysis); - } + }; return query; } /** - * Initiates a new ALTER TABLE query. - * @returns A new AlterTableQuery instance with a build method that respects the deepAnalysis setting. - */ - public get alter() { + * Initiates a new ALTER TABLE query. + * @returns A new AlterTableQuery instance with a build method that respects the deepAnalysis setting. + */ + public get alter(): tables.AlterTableQuery { const query = new tables.AlterTableQuery(); query.sqlFlavor(this.flavor); query.build = (deepAnalysis: boolean = this.deepAnalysis) => { return tables.AlterTableQuery.prototype.build.call(query, deepAnalysis); - } + }; return query; } } diff --git a/src/queryKinds/ddl/table/Alter.ts b/src/queryKinds/ddl/table/Alter.ts index 20d71fb..0574657 100644 --- a/src/queryKinds/ddl/table/Alter.ts +++ b/src/queryKinds/ddl/table/Alter.ts @@ -1,15 +1,14 @@ +import type { ColumnDefinition } from "../../../queryUtils/Column.js"; import SqlEscaper from "../../../sqlEscaper.js"; -import { ColumnDefinition } from "../../../queryUtils/Column.js"; import QueryKind from "../../../types/QueryKind.js"; import TableQueryDefinition from "./tableColumnDefinition.js"; /** - * Class representing an ALTER TABLE SQL query. - * This class allows you to define and build an ALTER TABLE SQL query - * with specified table name, columns to add, alter, or drop. - */ + * Class representing an ALTER TABLE SQL query. + * This class allows you to define and build an ALTER TABLE SQL query + * with specified table name, columns to add, alter, or drop. + */ export default class AlterTableQuery extends TableQueryDefinition { - /** The columns to be added to the table. */ columnsToAdd: ColumnDefinition[] = []; /** The columns to be altered in the table, mapped by column name. */ @@ -19,14 +18,16 @@ export default class AlterTableQuery extends TableQueryDefinition { constructor(tableName: string | null = null) { super(); - this.tableName = tableName ? SqlEscaper.escapeTableName(tableName, this.flavor) : ''; + this.tableName = tableName + ? SqlEscaper.escapeTableName(tableName, this.flavor) + : ""; } /** - * Adds columns to be added to the table. - * @param columns - A single Column instance or an array of Column instances. - * @returns The current instance for method chaining. - */ + * Adds columns to be added to the table. + * @param columns - A single Column instance or an array of Column instances. + * @returns The current instance for method chaining. + */ public addColumnsToAdd(columns: ColumnDefinition | ColumnDefinition[]): this { if (Array.isArray(columns)) { this.columnsToAdd.push(...columns); @@ -37,23 +38,24 @@ export default class AlterTableQuery extends TableQueryDefinition { } /** - * Sets the columns to be added to the table, replacing any existing ones. - * @param columns - A single Column instance or an array of Column instances. - * @returns The current instance for method chaining. - */ + * Sets the columns to be added to the table, replacing any existing ones. + * @param columns - A single Column instance or an array of Column instances. + * @returns The current instance for method chaining. + */ public setColumnsToAdd(columns: ColumnDefinition | ColumnDefinition[]): this { this.columnsToAdd = []; return this.addColumnsToAdd(columns); } /** - * Adds columns to be altered in the table. - * @param alteration - An object or array of objects containing the column name and the Column instance with alterations. - * @returns The current instance for method chaining. - */ + * Adds columns to be altered in the table. + * @param alteration - An object or array of objects containing the column name and the Column instance with alterations. + * @returns The current instance for method chaining. + */ public addColumnsToAlter( - alteration: { name: string, columns: ColumnDefinition } - | { name: string, columns: ColumnDefinition }[] + alteration: + | { name: string; columns: ColumnDefinition } + | { name: string; columns: ColumnDefinition }[], ): this { if (Array.isArray(alteration)) { alteration.forEach(({ name, columns }) => { @@ -73,23 +75,24 @@ export default class AlterTableQuery extends TableQueryDefinition { } /** - * Sets the columns to be altered in the table, replacing any existing ones. - * @param alteration - An object or array of objects containing the column name and the Column instance with alterations. - * @returns The current instance for method chaining. - */ + * Sets the columns to be altered in the table, replacing any existing ones. + * @param alteration - An object or array of objects containing the column name and the Column instance with alterations. + * @returns The current instance for method chaining. + */ public setColumnsToAlter( - alteration: { name: string, columns: ColumnDefinition } - | { name: string, columns: ColumnDefinition }[] + alteration: + | { name: string; columns: ColumnDefinition } + | { name: string; columns: ColumnDefinition }[], ): this { this.columnsToAlter.clear(); return this.addColumnsToAlter(alteration); } /** - * Adds column names to be dropped from the table. - * @param columnNames - A single column name or an array of column names. - * @returns The current instance for method chaining. - */ + * Adds column names to be dropped from the table. + * @param columnNames - A single column name or an array of column names. + * @returns The current instance for method chaining. + */ public dropColumnsByName(columnNames: string | string[]): this { if (Array.isArray(columnNames)) { this.columnsToDrop.push(...columnNames); @@ -100,103 +103,115 @@ export default class AlterTableQuery extends TableQueryDefinition { } /** - * Sets the column names to be dropped from the table, replacing any existing ones. - * @param columnNames - A single column name or an array of column names. - * @returns The current instance for method chaining. - */ + * Sets the column names to be dropped from the table, replacing any existing ones. + * @param columnNames - A single column name or an array of column names. + * @returns The current instance for method chaining. + */ public setColumnsToDrop(columnNames: string | string[]): this { this.columnsToDrop = []; return this.dropColumnsByName(columnNames); } /** - * Generates the SQL fragment for dropping a column. - * @param columnName - The name of the column to drop. - * @returns The SQL fragment for dropping the specified column. - */ + * Generates the SQL fragment for dropping a column. + * @param columnName - The name of the column to drop. + * @returns The SQL fragment for dropping the specified column. + */ public static dropColumn(columnName: string): string { return `DROP COLUMN ${columnName}`; } /** - * Generates the SQL fragments for adding and altering columns. - * @returns An array of SQL fragments for adding and altering columns. - */ + * Generates the SQL fragments for adding and altering columns. + * @returns An array of SQL fragments for adding and altering columns. + */ private alterColumns(): string[][] { - const toAdd = this.columnsToAdd.map(col => col.buildToAdd(this.tableName)); - const toAlter = Array.from(this.columnsToAlter.entries()).map(([colName, alterations]) => { - return alterations.map(alteration => alteration.buildToAlter(this.tableName, colName)); - }).flat(); + const toAdd = this.columnsToAdd.map((col) => + col.buildToAdd(this.tableName), + ); + const toAlter = Array.from(this.columnsToAlter.entries()).flatMap( + ([colName, alterations]) => { + return alterations.map((alteration) => + alteration.buildToAlter(this.tableName, colName), + ); + }, + ); return [...toAdd, ...toAlter]; } /** - * Generates the SQL fragments for dropping columns. - * @returns An array of SQL fragments for dropping columns. - */ + * Generates the SQL fragments for dropping columns. + * @returns An array of SQL fragments for dropping columns. + */ private dropColumns(): string[] { - return this.columnsToDrop.map(colName => AlterTableQuery.dropColumn(colName)); + return this.columnsToDrop.map((colName) => + AlterTableQuery.dropColumn(colName), + ); } /** - * Gets the kind of query. - * @returns The kind of query, which is 'ALTER_TABLE' for this class. - */ + * Gets the kind of query. + * @returns The kind of query, which is 'ALTER_TABLE' for this class. + */ public get kind() { - return QueryKind.ALTER_TABLE + return QueryKind.ALTER_TABLE; } /** - * Builds the ALTER TABLE SQL query strings. - * @param _deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). - * @returns An array of constructed ALTER TABLE SQL query strings. - * @throws Error if the table name is not provided or if no alterations are specified. - */ + * Builds the ALTER TABLE SQL query strings. + * @param _deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). + * @returns An array of constructed ALTER TABLE SQL query strings. + * @throws Error if the table name is not provided or if no alterations are specified. + */ public build(_deepAnalysis?: boolean): string[] { - if (!this.tableName) { - throw new Error('Table name is required to build ALTER TABLE query.'); + throw new Error("Table name is required to build ALTER TABLE query."); } const queries: string[] = []; const alterCols = this.alterColumns(); const dropCols = this.dropColumns(); - alterCols.forEach(colParts => { + alterCols.forEach((colParts) => { queries.push(...colParts); }); - dropCols.forEach(dropPart => { - let query = `ALTER TABLE ${this.tableName} ${dropPart}`; + dropCols.forEach((dropPart) => { + const query = `ALTER TABLE ${this.tableName} ${dropPart}`; queries.push(query); }); if (queries.length === 0) { - throw new Error('No alterations specified for ALTER TABLE query.'); + throw new Error("No alterations specified for ALTER TABLE query."); } this.builtQuery = []; - queries.forEach(query => { - (this.builtQuery as any as string[])?.push(SqlEscaper.appendSchemas(`${query};`, this.schemas)); + queries.forEach((query) => { + (this.builtQuery as any as string[])?.push( + SqlEscaper.appendSchemas(`${query};`, this.schemas), + ); }); return this.builtQuery; } /** - * Returns the SQL string representation of the ALTER TABLE query. - * @returns The SQL string representation of the ALTER TABLE query. - * @throws Error if the query has not been built yet. - */ + * Returns the SQL string representation of the ALTER TABLE query. + * @returns The SQL string representation of the ALTER TABLE query. + * @throws Error if the query has not been built yet. + */ public toSQL(): string | string[] { - if(this.builtQuery) this.build(); - if(!this.builtQuery) throw new Error('No built query available. Please build the query first.'); + if (this.builtQuery) this.build(); + if (!this.builtQuery) + throw new Error( + "No built query available. Please build the query first.", + ); return this.builtQuery; } /** - * Creates a clone of the current AlterTableQuery instance. - * @returns A new AlterTableQuery instance with the same properties as the current instance. - */ + * Creates a clone of the current AlterTableQuery instance. + * @returns A new AlterTableQuery instance with the same properties as the current instance. + */ public clone(): AlterTableQuery { const cloned = new AlterTableQuery(this.tableName); cloned.flavor = this.flavor; @@ -207,17 +222,16 @@ export default class AlterTableQuery extends TableQueryDefinition { } /** - * Resets the AlterTableQuery instance to its initial state. - * This method clears the table name, columns to add, alter, drop, and the built query. - * @returns The current instance for method chaining. - */ + * Resets the AlterTableQuery instance to its initial state. + * This method clears the table name, columns to add, alter, drop, and the built query. + * @returns The current instance for method chaining. + */ public reset(): this { - this.tableName = ''; + this.tableName = ""; this.builtQuery = null; this.columnsToAdd = []; this.columnsToAlter.clear(); this.columnsToDrop = []; return this; } - } diff --git a/src/queryKinds/ddl/table/Create.ts b/src/queryKinds/ddl/table/Create.ts index 9c7b194..dea910e 100644 --- a/src/queryKinds/ddl/table/Create.ts +++ b/src/queryKinds/ddl/table/Create.ts @@ -1,51 +1,48 @@ +import type { ColumnDefinition } from "../../../queryUtils/Column.js"; import SqlEscaper from "../../../sqlEscaper.js"; -import { ColumnDefinition } from "../../../queryUtils/Column.js"; import QueryKind from "../../../types/QueryKind.js"; import TableQueryDefinition from "./tableColumnDefinition.js"; - /** - * Class representing a CREATE TABLE SQL query. - * This class allows you to define and build a CREATE TABLE SQL query - * with specified table name and columns. - */ + * Class representing a CREATE TABLE SQL query. + * This class allows you to define and build a CREATE TABLE SQL query + * with specified table name and columns. + */ export default class CreateTableQuery extends TableQueryDefinition { - /** The name of the table to be created. */ private tableColumns: ColumnDefinition[] = []; - constructor( - tableName?: string, - ...columns: ColumnDefinition[] - ) { + constructor(tableName?: string, ...columns: ColumnDefinition[]) { super(); - this.tableName = tableName ? SqlEscaper.escapeTableName(tableName, this.flavor) : ''; + this.tableName = tableName + ? SqlEscaper.escapeTableName(tableName, this.flavor) + : ""; this.tableColumns = columns ?? []; } /** - * Gets the columns defined for the table. - * @returns An array of Column instances representing the table's columns. - */ + * Gets the columns defined for the table. + * @returns An array of Column instances representing the table's columns. + */ public get columns(): ColumnDefinition[] { return this.tableColumns; } /** - * Sets the columns for the table. - * @param columns - A single Column instance or an array of Column instances. - * @returns The current instance for method chaining. - */ + * Sets the columns for the table. + * @param columns - A single Column instance or an array of Column instances. + * @returns The current instance for method chaining. + */ public setColumns(columns: ColumnDefinition | ColumnDefinition[]): this { this.tableColumns = []; return this.addColumns(columns); } /** - * Adds columns to the table. - * @param columns - A single Column instance or an array of Column instances. - * @returns The current instance for method chaining. - */ + * Adds columns to the table. + * @param columns - A single Column instance or an array of Column instances. + * @returns The current instance for method chaining. + */ public addColumns(columns: ColumnDefinition | ColumnDefinition[]): this { if (Array.isArray(columns)) { this.tableColumns.push(...columns); @@ -56,31 +53,33 @@ export default class CreateTableQuery extends TableQueryDefinition { } /** - * Builds the CREATE TABLE SQL query string. - * @param _deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). - * @returns The constructed CREATE TABLE SQL query string. - * @throws Error if the table name is not set or if no columns are defined. - */ + * Builds the CREATE TABLE SQL query string. + * @param _deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). + * @returns The constructed CREATE TABLE SQL query string. + * @throws Error if the table name is not set or if no columns are defined. + */ public build(_deepAnalysis?: boolean): string { if (!this.tableName) { - throw new Error('Table name is not set.'); + throw new Error("Table name is not set."); } if (this.tableColumns.length === 0) { - throw new Error('No columns defined for the table.'); + throw new Error("No columns defined for the table."); } - const columnsDef = this.tableColumns.map(col => ` ${col.build()}`).join(',\n'); - const ifNotExists = this.ifNotExistsFlag ? 'IF NOT EXISTS ' : ''; + const columnsDef = this.tableColumns + .map((col) => ` ${col.build()}`) + .join(",\n"); + const ifNotExists = this.ifNotExistsFlag ? "IF NOT EXISTS " : ""; this.builtQuery = `CREATE TABLE ${ifNotExists}${this.tableName} (\n${columnsDef}\n);`; this.builtQuery = SqlEscaper.appendSchemas(this.builtQuery, this.schemas); return this.builtQuery; } /** - * Creates a clone of the current CreateTableQuery instance. - * @returns A new instance of CreateTableQuery with the same properties as the current instance. - */ + * Creates a clone of the current CreateTableQuery instance. + * @returns A new instance of CreateTableQuery with the same properties as the current instance. + */ public clone(): CreateTableQuery { const cloned = new CreateTableQuery(); cloned.tableName = this.tableName; @@ -91,12 +90,12 @@ export default class CreateTableQuery extends TableQueryDefinition { } /** - * Resets the state of the CreateTableQuery instance. - * This method clears the table name, columns, and built query. - * @returns The current instance for method chaining. - */ + * Resets the state of the CreateTableQuery instance. + * This method clears the table name, columns, and built query. + * @returns The current instance for method chaining. + */ public reset(): this { - this.tableName = ''; + this.tableName = ""; this.tableColumns = []; this.builtQuery = null; this.ifNotExistsFlag = false; @@ -104,19 +103,18 @@ export default class CreateTableQuery extends TableQueryDefinition { } /** - * Returns the SQL string representation of the CREATE TABLE query. - * @returns The SQL string representation of the CREATE TABLE query. - */ + * Returns the SQL string representation of the CREATE TABLE query. + * @returns The SQL string representation of the CREATE TABLE query. + */ public toSQL(): string { return this.build(); } /** - * Gets the kind of the query. - * @returns The kind of the query, which is QueryKind.CREATE_TABLE. - */ + * Gets the kind of the query. + * @returns The kind of the query, which is QueryKind.CREATE_TABLE. + */ public get kind() { return QueryKind.CREATE_TABLE; } - } diff --git a/src/queryKinds/ddl/table/Drop.ts b/src/queryKinds/ddl/table/Drop.ts index cb57638..fe29cbd 100644 --- a/src/queryKinds/ddl/table/Drop.ts +++ b/src/queryKinds/ddl/table/Drop.ts @@ -2,32 +2,32 @@ import SqlEscaper from "../../../sqlEscaper.js"; import QueryKind from "../../../types/QueryKind.js"; import TableQueryDefinition from "./tableColumnDefinition.js"; - /** - * Class representing a DROP TABLE SQL query. - * This class allows you to define and build a DROP TABLE SQL query - * with specified table name and options like IF EXISTS. - */ + * Class representing a DROP TABLE SQL query. + * This class allows you to define and build a DROP TABLE SQL query + * with specified table name and options like IF EXISTS. + */ export default class DropTableQuery extends TableQueryDefinition { - constructor(tableName: string | null = null) { super(); - this.tableName = tableName ? SqlEscaper.escapeTableName(tableName, this.flavor) : ''; + this.tableName = tableName + ? SqlEscaper.escapeTableName(tableName, this.flavor) + : ""; } /** - * Builds the DROP TABLE SQL query string. - * @param _deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). - * @returns The constructed DROP TABLE SQL query string. - * @throws Error if the table name is not provided. - */ + * Builds the DROP TABLE SQL query string. + * @param _deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). + * @returns The constructed DROP TABLE SQL query string. + * @throws Error if the table name is not provided. + */ public build(_deepAnalysis: boolean = false): string { if (!this.tableName) { - throw new Error('Table name is required to build DROP TABLE query.'); + throw new Error("Table name is required to build DROP TABLE query."); } - let query = 'DROP TABLE '; + let query = "DROP TABLE "; if (this.ifNotExistsFlag) { - query += 'IF EXISTS '; + query += "IF EXISTS "; } query += `${this.tableName};`; this.builtQuery = SqlEscaper.appendSchemas(query, this.schemas); @@ -35,9 +35,9 @@ export default class DropTableQuery extends TableQueryDefinition { } /** - * Creates a clone of the current DropTableQuery instance. - * @returns A new DropTableQuery instance with the same properties as the current instance. - */ + * Creates a clone of the current DropTableQuery instance. + * @returns A new DropTableQuery instance with the same properties as the current instance. + */ public clone(): DropTableQuery { const cloned = new DropTableQuery(this.tableName); cloned.flavor = this.flavor; @@ -46,21 +46,21 @@ export default class DropTableQuery extends TableQueryDefinition { } /** - * Resets the DropTableQuery instance to its initial state. - * This method clears the table name, built query, and IF NOT EXISTS flag. - * @returns The current instance for method chaining. - */ + * Resets the DropTableQuery instance to its initial state. + * This method clears the table name, built query, and IF NOT EXISTS flag. + * @returns The current instance for method chaining. + */ public reset(): this { - this.tableName = ''; + this.tableName = ""; this.builtQuery = null; this.ifNotExistsFlag = false; return this; } /** - * Returns the SQL string representation of the DROP TABLE query. - * @returns The SQL string representation of the DROP TABLE query. - */ + * Returns the SQL string representation of the DROP TABLE query. + * @returns The SQL string representation of the DROP TABLE query. + */ public toSQL(): string { return this.build(); } diff --git a/src/queryKinds/ddl/table/index.ts b/src/queryKinds/ddl/table/index.ts index a683680..514b140 100644 --- a/src/queryKinds/ddl/table/index.ts +++ b/src/queryKinds/ddl/table/index.ts @@ -1,8 +1,8 @@ -export * from './tableColumnDefinition.js'; -export { default as TableColumnDefinition } from './tableColumnDefinition.js'; -export * from './Create.js'; -export { default as CreateTableQuery } from './Create.js'; -export * from './Drop.js'; -export { default as DropTableQuery } from './Drop.js'; -export * from './Alter.js'; -export { default as AlterTableQuery } from './Alter.js'; +export * from "./Alter.js"; +export { default as AlterTableQuery } from "./Alter.js"; +export * from "./Create.js"; +export { default as CreateTableQuery } from "./Create.js"; +export * from "./Drop.js"; +export { default as DropTableQuery } from "./Drop.js"; +export * from "./tableColumnDefinition.js"; +export { default as TableColumnDefinition } from "./tableColumnDefinition.js"; diff --git a/src/queryKinds/ddl/table/tableColumnDefinition.ts b/src/queryKinds/ddl/table/tableColumnDefinition.ts index e9b6580..c6f88d3 100644 --- a/src/queryKinds/ddl/table/tableColumnDefinition.ts +++ b/src/queryKinds/ddl/table/tableColumnDefinition.ts @@ -5,18 +5,18 @@ export default abstract class TableQueryDefinition extends DdlQueryDefinition { protected ifNotExistsFlag: boolean = false; /** - * Marks the table creation to include IF NOT EXISTS clause. - * @returns The current instance for method chaining. - */ + * Marks the table creation to include IF NOT EXISTS clause. + * @returns The current instance for method chaining. + */ public ifNotExists(): this { this.ifNotExistsFlag = true; return this; } /** - * Resets the IF NOT EXISTS clause for the table creation. - * @returns The current instance for method chaining. - */ + * Resets the IF NOT EXISTS clause for the table creation. + * @returns The current instance for method chaining. + */ public resetIfNotExists(): this { this.ifNotExistsFlag = false; return this; diff --git a/src/queryKinds/dml/delete.ts b/src/queryKinds/dml/delete.ts index a36d60c..afe12d2 100644 --- a/src/queryKinds/dml/delete.ts +++ b/src/queryKinds/dml/delete.ts @@ -1,16 +1,16 @@ -import CteMaker, { Cte } from "../../cteMaker.js"; +import CteMaker, { type Cte } from "../../cteMaker.js"; import SqlEscaper from "../../sqlEscaper.js"; import Statement from "../../statementMaker.js"; import QueryKind from "../../types/QueryKind.js"; -import UsingTable from "../../types/UsingTable.js"; +import type UsingTable from "../../types/UsingTable.js"; import DmlQueryDefinition from "./dmlQueryDefinition.js"; /** - * DeleteQuery class represents a SQL DELETE query. - * It provides methods to build and manipulate the query, including specifying the table to delete from, - * adding USING clauses, WHERE conditions, RETURNING fields, and Common Table Expressions (CTEs). - * The class supports cloning, resetting, and building the final SQL query string with parameters. - */ + * DeleteQuery class represents a SQL DELETE query. + * It provides methods to build and manipulate the query, including specifying the table to delete from, + * adding USING clauses, WHERE conditions, RETURNING fields, and Common Table Expressions (CTEs). + * The class supports cloning, resetting, and building the final SQL query string with parameters. + */ export default class DeleteQuery extends DmlQueryDefinition { /** The table from which records will be deleted. */ private deletingFrom: string; @@ -21,23 +21,25 @@ export default class DeleteQuery extends DmlQueryDefinition { /** The fields to be returned after the delete operation. */ private returningFields: string[] = []; - /** - * Creates an instance of DeleteQuery. - * @param from - The name of the table from which records will be deleted. - * @param alias - An optional alias for the table. - */ + /** + * Creates an instance of DeleteQuery. + * @param from - The name of the table from which records will be deleted. + * @param alias - An optional alias for the table. + */ constructor(from?: string, alias: string | null = null) { super(); - this.deletingFrom = from ? SqlEscaper.escapeTableName(from, this.flavor) : ''; + this.deletingFrom = from + ? SqlEscaper.escapeTableName(from, this.flavor) + : ""; this.deletingFromAlias = alias; } - /** - * Adds Common Table Expressions (CTEs) to the query. - * Accepts a CteMaker instance, a single Cte, or an array of Ctes. - * @param ctes - The CTEs to be added to the query. - * @returns The current DeleteQuery instance for method chaining. - */ + /** + * Adds Common Table Expressions (CTEs) to the query. + * Accepts a CteMaker instance, a single Cte, or an array of Ctes. + * @param ctes - The CTEs to be added to the query. + * @returns The current DeleteQuery instance for method chaining. + */ public with(ctes: CteMaker | Cte | Cte[]): this { if (ctes instanceof CteMaker) { this.ctes = ctes; @@ -49,131 +51,147 @@ export default class DeleteQuery extends DmlQueryDefinition { return this; } - /** - * Specifies the table from which records will be deleted, with an optional alias. - * @param table - The name of the table. - * @param alias - An optional alias for the table. - * @returns The current DeleteQuery instance for method chaining. - */ + /** + * Specifies the table from which records will be deleted, with an optional alias. + * @param table - The name of the table. + * @param alias - An optional alias for the table. + * @returns The current DeleteQuery instance for method chaining. + */ public from(table: string, alias: string | null = null): this { this.deletingFrom = SqlEscaper.escapeTableName(table, this.flavor); this.deletingFromAlias = alias; return this; } - /** - * Adds tables to the USING clause. Accepts a string, a UsingTable object, or an array of UsingTable objects. - * @param tables - The table(s) to be added to the USING clause. - * @returns The current DeleteQuery instance for method chaining. - * @throws Error if an invalid table name is provided in string format. - */ + /** + * Adds tables to the USING clause. Accepts a string, a UsingTable object, or an array of UsingTable objects. + * @param tables - The table(s) to be added to the USING clause. + * @returns The current DeleteQuery instance for method chaining. + * @throws Error if an invalid table name is provided in string format. + */ public using(tables: string | UsingTable | UsingTable[]): this { if (Array.isArray(tables)) { - this.usingTables.push(...tables.map(t => ({ - table: SqlEscaper.escapeTableName(t.table, this.flavor), - alias: t.alias || null - }))); - } else if (typeof tables === 'string') { - const tableParts = tables.split(' '); - if (tableParts[0] && tableParts[0]?.trim() !== '') { + this.usingTables.push( + ...tables.map((t) => ({ + table: SqlEscaper.escapeTableName(t.table, this.flavor), + alias: t.alias || null, + })), + ); + } else if (typeof tables === "string") { + const tableParts = tables.split(" "); + if (tableParts[0] && tableParts[0]?.trim() !== "") { this.usingTables.push({ table: SqlEscaper.escapeTableName(tableParts[0], this.flavor), - alias: tableParts[1] || null + alias: tableParts[1] || null, }); } else { - throw new Error('Invalid table name provided to USING clause.'); + throw new Error("Invalid table name provided to USING clause."); } } else { this.usingTables.push({ table: SqlEscaper.escapeTableName(tables.table, this.flavor), - alias: tables.alias || null + alias: tables.alias || null, }); } return this; } /** - * Specifies the WHERE clause for the DELETE query. - * Accepts either a Statement object or a raw SQL string with optional parameters. - * @param statement - The WHERE clause as a Statement or raw SQL string. - * @param values - Optional parameters for the raw SQL string. - * @returns The current DeleteQuery instance for method chaining. - */ + * Specifies the WHERE clause for the DELETE query. + * Accepts either a Statement object or a raw SQL string with optional parameters. + * @param statement - The WHERE clause as a Statement or raw SQL string. + * @param values - Optional parameters for the raw SQL string. + * @returns The current DeleteQuery instance for method chaining. + */ public where(statement: Statement | string, ...values: any[]): this { - if (typeof statement === 'string') { - statement = new Statement().raw('', statement, ...values); + if (typeof statement === "string") { + statement = new Statement().raw("", statement, ...values); } - + this.whereStatement = statement; return this; } /** - * Allows building the WHERE clause using a callback function that receives a Statement object. - * This provides a more fluent interface for constructing complex WHERE conditions. - * @param statement - A callback function that takes a Statement object and returns a modified Statement. - * @returns The current DeleteQuery instance for method chaining. - */ - public useStatement(statement: (stmt: Statement) => Statement | void): this { + * Allows building the WHERE clause using a callback function that receives a Statement object. + * This provides a more fluent interface for constructing complex WHERE conditions. + * @param statement - A callback function that takes a Statement object and returns a modified Statement. + * @returns The current DeleteQuery instance for method chaining. + */ + public useStatement( + statement: (stmt: Statement) => Statement | undefined, + ): this { const stmt = new Statement(); const newStmt = statement(stmt) || stmt; return this.where(newStmt); } - /** - * Specifies the fields to be returned after the delete operation. - * Accepts a string or an array of strings. - * @param fields - The field(s) to be returned. - * @returns The current DeleteQuery instance for method chaining. - */ + /** + * Specifies the fields to be returned after the delete operation. + * Accepts a string or an array of strings. + * @param fields - The field(s) to be returned. + * @returns The current DeleteQuery instance for method chaining. + */ public returning(fields: string | string[]): this { if (Array.isArray(fields)) { - this.returningFields = SqlEscaper.escapeSelectIdentifiers(fields, this.flavor); + this.returningFields = SqlEscaper.escapeSelectIdentifiers( + fields, + this.flavor, + ); } else { - this.returningFields = SqlEscaper.escapeSelectIdentifiers([fields], this.flavor); + this.returningFields = SqlEscaper.escapeSelectIdentifiers( + [fields], + this.flavor, + ); } return this; } - /** - * Adds fields to the existing RETURNING clause. - * Accepts a string or an array of strings. - * @param fields - The field(s) to be returned. - * @returns The current DeleteQuery instance for method chaining. - */ + /** + * Adds fields to the existing RETURNING clause. + * Accepts a string or an array of strings. + * @param fields - The field(s) to be returned. + * @returns The current DeleteQuery instance for method chaining. + */ public addReturning(fields: string | string[]): this { if (Array.isArray(fields)) { - this.returningFields.push(...SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); + this.returningFields.push( + ...SqlEscaper.escapeSelectIdentifiers(fields, this.flavor), + ); } else { - this.returningFields.push(...SqlEscaper.escapeSelectIdentifiers([fields], this.flavor)); + this.returningFields.push( + ...SqlEscaper.escapeSelectIdentifiers([fields], this.flavor), + ); } return this; } /** - * Creates a deep clone of the current DeleteQuery instance. - * This is useful for creating variations of the query without modifying the original. - * @returns A new DeleteQuery instance with the same properties as the original. - */ + * Creates a deep clone of the current DeleteQuery instance. + * This is useful for creating variations of the query without modifying the original. + * @returns A new DeleteQuery instance with the same properties as the original. + */ public clone(): DeleteQuery { const cloned = new DeleteQuery(); cloned.schemas = [...this.schemas]; cloned.deletingFrom = this.deletingFrom; cloned.deletingFromAlias = this.deletingFromAlias; cloned.usingTables = JSON.parse(JSON.stringify(this.usingTables)); - cloned.whereStatement = this.whereStatement ? this.whereStatement.clone() : null; + cloned.whereStatement = this.whereStatement + ? this.whereStatement.clone() + : null; cloned.returningFields = [...this.returningFields]; - cloned.ctes = this.ctes ? new CteMaker(...this.ctes['ctes']) : null; + cloned.ctes = this.ctes ? new CteMaker(...this.ctes["ctes"]) : null; return cloned; } /** - * Resets the state of the DeleteQuery instance, clearing all configurations. - * This allows reusing the instance for building a new query from scratch. - * @returns void - */ + * Resets the state of the DeleteQuery instance, clearing all configurations. + * This allows reusing the instance for building a new query from scratch. + * @returns void + */ public reset(): void { - this.deletingFrom = ''; + this.deletingFrom = ""; this.schemas = []; this.deletingFromAlias = null; this.usingTables = []; @@ -184,43 +202,43 @@ export default class DeleteQuery extends DmlQueryDefinition { } /** - * This a DELETE query. - * @returns The kind of SQL operation, which is 'DELETE' for this class. - */ + * This a DELETE query. + * @returns The kind of SQL operation, which is 'DELETE' for this class. + */ public get kind() { return QueryKind.DELETE; } /** - * Invalidates the current state of the query, forcing a rebuild on the next operation. - * @returns void - */ - public invalidate(): void { + * Invalidates the current state of the query, forcing a rebuild on the next operation. + * @returns void + */ + public override invalidate(): void { this.builtQuery = null; if (this.whereStatement) this.whereStatement.invalidate(); if (this.ctes) { - for (const cte of this.ctes['ctes']) { - cte['query'].invalidate(); + for (const cte of this.ctes["ctes"]) { + cte["query"].invalidate(); } } } /** - * Builds the SQL DELETE query and returns an object containing the query text and its parameters. - * The optional deepAnalysis parameter can be used to control the depth of analysis during the build process. - * @param deepAnalysis - If true, performs a deeper analysis for duplicate parameters. - * @returns An object containing the query text and its parameters. - * @throws Error if no table is specified for the DELETE query. - */ + * Builds the SQL DELETE query and returns an object containing the query text and its parameters. + * The optional deepAnalysis parameter can be used to control the depth of analysis during the build process. + * @param deepAnalysis - If true, performs a deeper analysis for duplicate parameters. + * @returns An object containing the query text and its parameters. + * @throws Error if no table is specified for the DELETE query. + */ public build(deepAnalysis: boolean = false): { text: string; values: any[] } { if (!this.deletingFrom.trim()) { - throw new Error('No table specified for DELETE query.'); + throw new Error("No table specified for DELETE query."); } this.whereStatement = this.whereStatement || new Statement(); this.whereStatement.enableWhere(); - let ctesClause = ''; + let ctesClause = ""; if (this.ctes) { const ctesBuilt = this.ctes.build(); ctesClause = ctesBuilt.text; @@ -232,13 +250,15 @@ export default class DeleteQuery extends DmlQueryDefinition { deleteClause += ` AS ${this.deletingFromAlias}`; } - let usingClause = ''; + let usingClause = ""; if (this.usingTables.length > 0) { - const usingParts = this.usingTables.map(t => t.alias ? `${t.table} AS ${t.alias}` : t.table); - usingClause = `USING ${usingParts.join(',\n ')}`; + const usingParts = this.usingTables.map((t) => + t.alias ? `${t.table} AS ${t.alias}` : t.table, + ); + usingClause = `USING ${usingParts.join(",\n ")}`; } - let whereClause = ''; + let whereClause = ""; let values: any[] = []; if (this.whereStatement) { const stmt = this.whereStatement.build(false); @@ -246,9 +266,9 @@ export default class DeleteQuery extends DmlQueryDefinition { values = stmt.values; } - let returningClause = ''; + let returningClause = ""; if (this.returningFields.length > 0) { - returningClause = `RETURNING ${this.returningFields.join(', ')}`; + returningClause = `RETURNING ${this.returningFields.join(", ")}`; } this.builtQuery = [ @@ -256,39 +276,42 @@ export default class DeleteQuery extends DmlQueryDefinition { deleteClause, usingClause, whereClause, - returningClause - ].filter(part => part !== '') - .join('\n '); + returningClause, + ] + .filter((part) => part !== "") + .join("\n "); - this.builtQuery = SqlEscaper.appendSchemas( - this.builtQuery, this.schemas - ); + this.builtQuery = SqlEscaper.appendSchemas(this.builtQuery, this.schemas); - const analyzed = this.reAnalyzeParsedQueryForDuplicateParams(this.builtQuery, values, deepAnalysis); + const analyzed = this.reAnalyzeParsedQueryForDuplicateParams( + this.builtQuery, + values, + deepAnalysis, + ); this.builtQuery = analyzed.text; this.builtParams = analyzed.values; return { text: this.builtQuery, values: this.builtParams }; } /** - * Builds the SQL DELETE query and returns the query text as a string. - * This method is useful for obtaining the raw SQL query without parameters. - * @returns The SQL DELETE query as a string. - */ + * Builds the SQL DELETE query and returns the query text as a string. + * This method is useful for obtaining the raw SQL query without parameters. + * @returns The SQL DELETE query as a string. + */ public toSQL(): string { - if(!this.builtQuery) this.build(); - if(!this.builtQuery) throw new Error('Failed to build query.'); + if (!this.builtQuery) this.build(); + if (!this.builtQuery) throw new Error("Failed to build query."); return this.builtQuery; } /** - * Builds the SQL DELETE query and returns the parameters as an array. - * This method is useful for obtaining the parameters to be used with the SQL query. - * @returns An array of parameters for the SQL DELETE query. - */ + * Builds the SQL DELETE query and returns the parameters as an array. + * This method is useful for obtaining the parameters to be used with the SQL query. + * @returns An array of parameters for the SQL DELETE query. + */ public getParams(): any[] { - if(!this.builtQuery) this.build(); - if(!this.builtQuery) throw new Error('Failed to build query.'); + if (!this.builtQuery) this.build(); + if (!this.builtQuery) throw new Error("Failed to build query."); return this.builtParams || []; } } diff --git a/src/queryKinds/dml/dmlQueryDefinition.ts b/src/queryKinds/dml/dmlQueryDefinition.ts index 799d9a1..1e8fc90 100644 --- a/src/queryKinds/dml/dmlQueryDefinition.ts +++ b/src/queryKinds/dml/dmlQueryDefinition.ts @@ -1,42 +1,47 @@ -import { ValidatorOptions } from "class-validator"; -import deepEqual from "../../deepEqual.js"; -import { getClassValidator, getZod } from "../../getOptionalPackages.js"; +import type { ValidatorOptions } from "class-validator"; // Import types only since they are used for type checking only // and zod is optional peer dependency import type z from "zod"; import type { ZodObject } from "zod"; -import sqlFlavor from "../../types/sqlFlavor.js"; -import CteMaker from "../../cteMaker.js"; -import Statement from "../../statementMaker.js"; -import QueryKind from "../../types/QueryKind.js"; -import Join, { isJoinTable } from "../../types/Join.js"; +import type CteMaker from "../../cteMaker.js"; +import deepEqual from "../../deepEqual.js"; +import { getClassValidator, getZod } from "../../getOptionalPackages.js"; import SqlEscaper from "../../sqlEscaper.js"; +import type Statement from "../../statementMaker.js"; +import type Join from "../../types/Join.js"; +import { isJoinTable } from "../../types/Join.js"; +import type QueryKind from "../../types/QueryKind.js"; +import sqlFlavor from "../../types/sqlFlavor.js"; /** - * An array of function names that can be used to execute SQL queries. - * These functions are commonly found in database client libraries. - */ -const functionNames = ['execute', 'query', 'run', 'all', 'get'] as const; + * An array of function names that can be used to execute SQL queries. + * These functions are commonly found in database client libraries. + */ +const functionNames = ["execute", "query", "run", "all", "get"] as const; /** - * FunctionDeclarationReturnType type defines the possible return types for functions that execute SQL queries. - * It can be an array of results, an object containing a rows property with the results, - * or a tuple containing an array of results and a number (e.g., for affected rows). - */ + * FunctionDeclarationReturnType type defines the possible return types for functions that execute SQL queries. + * It can be an array of results, an object containing a rows property with the results, + * or a tuple containing an array of results and a number (e.g., for affected rows). + */ type FunctionDeclarationReturnType = T[] | { rows: T[] } | [T[], number]; /** - * FunctionDeclaration type defines the signature for functions that execute SQL queries. - * It takes a query string and an array of parameters, and returns a promise that resolves - * with a result of type FunctionDeclarationReturnType or directly a result of that type. - */ -type FunctionDeclaration = - (query: string, params: any[]) => Promise> | FunctionDeclarationReturnType; + * FunctionDeclaration type defines the signature for functions that execute SQL queries. + * It takes a query string and an array of parameters, and returns a promise that resolves + * with a result of type FunctionDeclarationReturnType or directly a result of that type. + */ +type FunctionDeclaration = ( + query: string, + params: any[], +) => + | Promise> + | FunctionDeclarationReturnType; /** - * QueryExecutorObject interface defines the structure for an object that can execute SQL queries. - * It includes optional methods for executing queries in different ways, as well as an optional manager property. - */ + * QueryExecutorObject interface defines the structure for an object that can execute SQL queries. + * It includes optional methods for executing queries in different ways, as well as an optional manager property. + */ interface QueryExecutorObject { execute?: FunctionDeclaration; query?: FunctionDeclaration; @@ -47,51 +52,49 @@ interface QueryExecutorObject { } /** - * QueryExecutor type can be either a QueryExecutorObject or a function that executes a query. - */ -export type QueryExecutor = - QueryExecutorObject - | FunctionDeclaration; + * QueryExecutor type can be either a QueryExecutorObject or a function that executes a query. + */ +export type QueryExecutor = QueryExecutorObject | FunctionDeclaration; /** - * SchemaType is a conditional type that infers the type of data based on the provided schema. - * It supports both Zod schemas and class-validator classes. - */ -type SchemaType = - S extends ZodObject ? z.infer : - S extends { new(): infer U } ? U : - never; + * SchemaType is a conditional type that infers the type of data based on the provided schema. + * It supports both Zod schemas and class-validator classes. + */ +type SchemaType = S extends ZodObject + ? z.infer + : S extends { new (): infer U } + ? U + : never; /** - * OmittingReturnFromValidate is a utility type that modifies the type T by omitting - * the methods 'execute', 'getOne', and 'getMany', and adding the DmlQueryDefinition with the inferred schema type. - */ -type ReturnFromValidate = - DmlQueryDefinition> & This; + * OmittingReturnFromValidate is a utility type that modifies the type T by omitting + * the methods 'execute', 'getOne', and 'getMany', and adding the DmlQueryDefinition with the inferred schema type. + */ +type ReturnFromValidate = DmlQueryDefinition> & + This; /** - * Abstract class DmlQueryDefinition serves as a blueprint for different types of SQL query definitions. - * It defines the essential methods and properties that any concrete query class must implement. - * This includes methods for building the SQL query, executing it, cloning the query definition, - * resetting its state, and checking if the query is complete. - * The class also provides a method to re-analyze the query for duplicate parameters to optimize parameter usage. - */ + * Abstract class DmlQueryDefinition serves as a blueprint for different types of SQL query definitions. + * It defines the essential methods and properties that any concrete query class must implement. + * This includes methods for building the SQL query, executing it, cloning the query definition, + * resetting its state, and checking if the query is complete. + * The class also provides a method to re-analyze the query for duplicate parameters to optimize parameter usage. + */ export default abstract class DmlQueryDefinition { - /** - * Converts the query definition to its SQL string representation. - */ + * Converts the query definition to its SQL string representation. + */ public abstract toSQL(): string; /** - * Retrieves the parameters associated with the query. - */ + * Retrieves the parameters associated with the query. + */ public abstract getParams(): any[]; /** - * Builds the SQL query and returns an object containing the query text and its parameters. - * The optional deepAnalysis parameter can be used to control the depth of analysis during the build process. - */ + * Builds the SQL query and returns an object containing the query text and its parameters. + * The optional deepAnalysis parameter can be used to control the depth of analysis during the build process. + */ public abstract build(deepAnalysis?: boolean): { /** The SQL query text. */ text: string; @@ -100,91 +103,94 @@ export default abstract class DmlQueryDefinition { }; /** - * Creates a deep copy of the current query definition. - */ + * Creates a deep copy of the current query definition. + */ public abstract clone(): DmlQueryDefinition; /** - * Resets the query definition to its initial state. - */ + * Resets the query definition to its initial state. + */ public abstract reset(): void; /** - * Indicates whether the query is complete and ready for execution. - * @returns True if the query has been built and has parameters, false otherwise. - */ + * Indicates whether the query is complete and ready for execution. + * @returns True if the query has been built and has parameters, false otherwise. + */ public get isDone(): boolean { return this.builtQuery !== null && this.builtParams !== null; } /** - * Parses a Join object to ensure proper escaping and cloning of subqueries. - * @param join The Join object to parse. - * @returns The parsed Join object. - */ + * Parses a Join object to ensure proper escaping and cloning of subqueries. + * @param join The Join object to parse. + * @returns The parsed Join object. + */ protected parseJoinObject(join: Join): Join { if (isJoinTable(join)) { return { ...join, table: SqlEscaper.escapeTableName(join.table, this.flavor), - } + }; } else { return { ...join, - subQuery: join.subQuery.clone() - } + subQuery: join.subQuery.clone(), + }; } } /** - * The kind of SQL operation represented by the query definition. - * It can be one of 'INSERT', 'UPDATE', 'DELETE', or 'SELECT'. - */ + * The kind of SQL operation represented by the query definition. + * It can be one of 'INSERT', 'UPDATE', 'DELETE', or 'SELECT'. + */ public abstract get kind(): QueryKind; /** - * Provides access to the current query definition instance. - * @returns The current DmlQueryDefinition instance. - */ + * Provides access to the current query definition instance. + * @returns The current DmlQueryDefinition instance. + */ public get query(): DmlQueryDefinition { return this; } /** - * Utility method to add spaces to each line of a given string. - * This is useful for formatting SQL queries for better readability. - * @param str The string to format. - * @param spaces The number of spaces to add to the beginning of each line (default is 0). - * @returns The formatted string with added spaces. - */ + * Utility method to add spaces to each line of a given string. + * This is useful for formatting SQL queries for better readability. + * @param str The string to format. + * @param spaces The number of spaces to add to the beginning of each line (default is 0). + * @returns The formatted string with added spaces. + */ protected spaceLines(str: string, spaces: number = 0): string { - const space = ' '.repeat(spaces); - return str.split('\n').map(line => space + line).join('\n'); + const space = " ".repeat(spaces); + return str + .split("\n") + .map((line) => space + line) + .join("\n"); } /** - * The SQL flavor to use for escaping identifiers. - * Default is PostgreSQL. - */ + * The SQL flavor to use for escaping identifiers. + * Default is PostgreSQL. + */ protected flavor: sqlFlavor = sqlFlavor.postgres; /** - * Schemas to be used in the query. - * This is useful for databases that support multiple schemas. - * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. - */ + * Schemas to be used in the query. + * This is useful for databases that support multiple schemas. + * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. + */ protected schemas: string[] = []; /** - * The built SQL query string. - * Null if the query has not been built yet or has been invalidated. - */ + * The built SQL query string. + * Null if the query has not been built yet or has been invalidated. + */ protected builtQuery: string | null = null; /** - * The parameters for the built SQL query. - * Null if the query has not been built yet or has been invalidated. - */ + * The parameters for the built SQL query. + * Null if the query has not been built yet or has been invalidated. + */ protected builtParams: any[] | null = null; /** Optional Common Table Expressions (CTEs) for the query. */ @@ -194,89 +200,89 @@ export default abstract class DmlQueryDefinition { protected whereStatement: Statement | null = null; /** - * Invalidates the current state of the query, forcing a rebuild on the next operation. - * @returns void - */ + * Invalidates the current state of the query, forcing a rebuild on the next operation. + * @returns void + */ public invalidate(): void { this.builtQuery = null; this.builtParams = null; if (this.whereStatement) this.whereStatement.invalidate(); if (this.ctes) { - for (const cte of this.ctes['ctes']) { - cte['query'].invalidate(); + for (const cte of this.ctes["ctes"]) { + cte["query"].invalidate(); } } } /** - * Sets the SQL flavor for escaping identifiers. - * @param flavor The SQL flavor to set. - * @returns The current DmlQueryDefinition instance for chaining. - */ - public sqlFlavor(flavor: sqlFlavor) { + * Sets the SQL flavor for escaping identifiers. + * @param flavor The SQL flavor to set. + * @returns The current DmlQueryDefinition instance for chaining. + */ + public sqlFlavor(flavor: sqlFlavor): this { this.flavor = flavor; return this; } /** - * Set schemas to be used in the query. - * This is useful for databases that support multiple schemas. - * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. - * @param schemas The schemas to set. - * @returns The current SelectQuery instance for chaining. - */ + * Set schemas to be used in the query. + * This is useful for databases that support multiple schemas. + * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. + * @param schemas The schemas to set. + * @returns The current SelectQuery instance for chaining. + */ public schema(...schemas: string[]): this { this.schemas = schemas; return this; } /** - * Adds schemas to the existing list of schemas. - * This is useful for databases that support multiple schemas. - * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. - * @param schemas The schemas to add. - * @returns The current SelectQuery instance for chaining. - */ + * Adds schemas to the existing list of schemas. + * This is useful for databases that support multiple schemas. + * NOTICE: SQL Injection is not checked in schema names. Be sure to use only trusted schema names. + * @param schemas The schemas to add. + * @returns The current SelectQuery instance for chaining. + */ public addSchema(...schemas: string[]): this { this.schemas.push(...schemas); return this; } /** - * True if the schema to validate against is a Zod schema, false otherwise. - */ + * True if the schema to validate against is a Zod schema, false otherwise. + */ private isZodSchema: boolean = false; /** - * True if the schema to validate against is a class-validator schema, false otherwise. - */ + * True if the schema to validate against is a class-validator schema, false otherwise. + */ private isClassValidatorSchema: boolean = false; /** - * The schema to validate against, can be either a Zod schema or a class-validator class. - * Null if no schema is set. - */ + * The schema to validate against, can be either a Zod schema or a class-validator class. + * Null if no schema is set. + */ private validatorSchema: any = null; /** - * Options for class-validator validation. - * Default options are set to whitelist properties, forbid non-whitelisted properties, - * and exclude the target object from validation errors. - */ + * Options for class-validator validation. + * Default options are set to whitelist properties, forbid non-whitelisted properties, + * and exclude the target object from validation errors. + */ private classValidatorOptions: ValidatorOptions = { whitelist: true, forbidNonWhitelisted: false, - validationError: { target: false } + validationError: { target: false }, }; /** - * Use zod or class-validator + class-transformer to validate the input schema. - * @param schema The schema to validate against. - * @returns A promise that resolves if the schema is valid, or rejects with validation errors. - * @throws An error if no validation library is available. - */ + * Use zod or class-validator + class-transformer to validate the input schema. + * @param schema The schema to validate against. + * @returns A promise that resolves if the schema is valid, or rejects with validation errors. + * @throws An error if no validation library is available. + */ public validate< - T extends { safeParse: Function } | (new (...args: any[]) => any) + T extends { safeParse: Function } | (new (...args: any[]) => any), >(schema: T): ReturnFromValidate { if ("safeParse" in schema) { this.isZodSchema = true; @@ -289,34 +295,34 @@ export default abstract class DmlQueryDefinition { } /** - * Configures options for class-validator validation. - * @param config The configuration options for class-validator. - * @returns The current DmlQueryDefinition instance for method chaining. - */ - public classValidatorConfig( - config: ValidatorOptions - ) { + * Configures options for class-validator validation. + * @param config The configuration options for class-validator. + * @returns The current DmlQueryDefinition instance for method chaining. + */ + public classValidatorConfig(config: ValidatorOptions): this { this.classValidatorOptions = config; return this; } /** - * Handles validation of the input data against the set schema. - * Supports both Zod schemas and class-validator classes. - * @param input The data to validate. - * @returns A promise that resolves if the data is valid, or rejects with validation errors. - * @throws An error if no validation library is available. - */ + * Handles validation of the input data against the set schema. + * Supports both Zod schemas and class-validator classes. + * @param input The data to validate. + * @returns A promise that resolves if the data is valid, or rejects with validation errors. + * @throws An error if no validation library is available. + */ private async handleValidation(input: any): Promise { input = Array.isArray(input) ? input : [input]; - input = input !== null - ? Array.isArray(input) && input.length === 2 - && Array.isArray(input?.[0]) - && typeof input?.[1] === 'number' - ? input[0] ?? null - : input ?? null - : input; + input = + input !== null + ? Array.isArray(input) && + input.length === 2 && + Array.isArray(input?.[0]) && + typeof input?.[1] === "number" + ? (input[0] ?? null) + : (input ?? null) + : input; try { if (this.validatorSchema) { @@ -324,7 +330,8 @@ export default abstract class DmlQueryDefinition { const zod = await getZod(); input = await zod.array(this.validatorSchema).parseAsync(input); } else if (this.isClassValidatorSchema) { - const { classValidator, classTransformer } = await getClassValidator(); + const { classValidator, classTransformer } = + await getClassValidator(); const { validateOrReject } = classValidator; input = classTransformer.plainToInstance(this.validatorSchema, input); for (const item of input as any[]) { @@ -334,18 +341,18 @@ export default abstract class DmlQueryDefinition { } } } catch (error) { - console.group('Validation Error'); - console.error('Error during validation:', error); - console.debug('Input data:', input); - console.debug('Validator schema:', this.validatorSchema); + console.group("Validation Error"); + console.error("Error during validation:", error); + console.debug("Input data:", input); + console.debug("Validator schema:", this.validatorSchema); console.groupEnd(); - throw new Error('Validation failed. See console for details.', { + throw new Error("Validation failed. See console for details.", { cause: { originalError: error, input, - schema: this.validatorSchema - } + schema: this.validatorSchema, + }, }); } @@ -353,49 +360,48 @@ export default abstract class DmlQueryDefinition { } /** - * Builds the SQL query and re-analyzes it for duplicate parameters. - * This method ensures that the query is optimized by removing redundant parameters. - * @returns An object containing the optimized query text and its parameters. - * @throws An error if the build process fails. - */ + * Builds the SQL query and re-analyzes it for duplicate parameters. + * This method ensures that the query is optimized by removing redundant parameters. + * @returns An object containing the optimized query text and its parameters. + * @throws An error if the build process fails. + */ public buildReanalyze(): { text: string; values: any[] } { const query = this.build(); - return this.reAnalyzeParsedQueryForDuplicateParams(query.text, query.values); + return this.reAnalyzeParsedQueryForDuplicateParams( + query.text, + query.values, + ); } /** - * Executes the built SQL query using the provided query executor. - * The query executor can be a function or an object with methods to execute the query. - * The optional noManager parameter can be used to bypass the manager property if present. - * @param queryExecutor The executor to run the SQL query. - * @param noManager If true, bypasses the manager property of the executor object. - * @returns A promise that resolves with the result of the query execution. - * @throws An error if the provided query executor is invalid or if validation fails. - */ + * Executes the built SQL query using the provided query executor. + * The query executor can be a function or an object with methods to execute the query. + * The optional noManager parameter can be used to bypass the manager property if present. + * @param queryExecutor The executor to run the SQL query. + * @param noManager If true, bypasses the manager property of the executor object. + * @returns A promise that resolves with the result of the query execution. + * @throws An error if the provided query executor is invalid or if validation fails. + */ public async execute( queryExecutor: QueryExecutor, - noManager: boolean = false + noManager: boolean = false, ): Promise { - if (typeof queryExecutor === 'function') { + if (typeof queryExecutor === "function") { const builtQuery = this.build(); - let result = await queryExecutor(builtQuery.text, builtQuery.values); - if ( - result === undefined - || result === null - ) return []; - - if (typeof result !== 'object') { - throw new Error('Invalid result from query executor function.'); + const result = await queryExecutor(builtQuery.text, builtQuery.values); + if (result === undefined || result === null) return []; + + if (typeof result !== "object") { + throw new Error("Invalid result from query executor function."); } if ("rows" in result) { - if ( - result.rows === undefined - || result.rows === null - ) return []; + if (result.rows === undefined || result.rows === null) return []; - if (typeof result.rows !== 'object') { - throw new Error('Invalid rows property in result from query executor function.'); + if (typeof result.rows !== "object") { + throw new Error( + "Invalid rows property in result from query executor function.", + ); } return await this.handleValidation(result.rows); @@ -404,28 +410,33 @@ export default abstract class DmlQueryDefinition { } } - if (!noManager && queryExecutor?.manager && typeof queryExecutor?.manager === 'object') { + if ( + !noManager && + queryExecutor?.manager && + typeof queryExecutor?.manager === "object" + ) { for (const functionName of functionNames) { - if (typeof queryExecutor.manager[functionName] === 'function') { + if (typeof queryExecutor.manager[functionName] === "function") { const builtQuery = this.build(); - const result = await queryExecutor.manager[functionName]!(builtQuery.text, builtQuery.values); - if ( - result === undefined - || result === null - ) return []; - - if (typeof result !== 'object') { - throw new Error('Invalid result from query executor manager function.'); + const result = await queryExecutor.manager[functionName]!( + builtQuery.text, + builtQuery.values, + ); + if (result === undefined || result === null) return []; + + if (typeof result !== "object") { + throw new Error( + "Invalid result from query executor manager function.", + ); } if ("rows" in result) { - if ( - result.rows === undefined - || result.rows === null - ) return []; + if (result.rows === undefined || result.rows === null) return []; - if (typeof result.rows !== 'object') { - throw new Error('Invalid rows property in result from query executor manager function.'); + if (typeof result.rows !== "object") { + throw new Error( + "Invalid rows property in result from query executor manager function.", + ); } return await this.handleValidation(result.rows); @@ -436,23 +447,27 @@ export default abstract class DmlQueryDefinition { } } else { for (const functionName of functionNames) { - if (typeof queryExecutor[functionName] === 'function') { + if (typeof queryExecutor[functionName] === "function") { const builtQuery = this.build(); - const result = await queryExecutor[functionName]!(builtQuery.text, builtQuery.values); + const result = await queryExecutor[functionName]!( + builtQuery.text, + builtQuery.values, + ); if (!result) return []; - if (typeof result !== 'object') { - throw new Error('Invalid result from query executor object function.'); + if (typeof result !== "object") { + throw new Error( + "Invalid result from query executor object function.", + ); } if ("rows" in result) { - if ( - result.rows === undefined - || result.rows === null - ) return []; + if (result.rows === undefined || result.rows === null) return []; - if (typeof result.rows !== 'object') { - throw new Error('Invalid rows property in result from query executor manager function.'); + if (typeof result.rows !== "object") { + throw new Error( + "Invalid rows property in result from query executor manager function.", + ); } return await this.handleValidation(result.rows); @@ -463,111 +478,116 @@ export default abstract class DmlQueryDefinition { } } - throw new Error('Invalid query executor provided.'); + throw new Error("Invalid query executor provided."); } /** - * Executes the built SQL query and returns a single result or null if no result is found. - * This method ensures that only one result is returned by applying a limit if necessary. - * @param queryExecutor The executor to run the SQL query. - * @param noManager If true, bypasses the manager property of the executor object. - * @returns A promise that resolves with a single result or null. - * @throws An error if the provided query executor is invalid or if validation fails. - */ + * Executes the built SQL query and returns a single result or null if no result is found. + * This method ensures that only one result is returned by applying a limit if necessary. + * @param queryExecutor The executor to run the SQL query. + * @param noManager If true, bypasses the manager property of the executor object. + * @returns A promise that resolves with a single result or null. + * @throws An error if the provided query executor is invalid or if validation fails. + */ public async getOne( queryExecutor: QueryExecutor, - noManager: boolean = false + noManager: boolean = false, ): Promise { if ( - "limit" in this - && typeof this['limit'] === 'function' - && this['limit'] instanceof Function - && this['limit'](1) !== this + "limit" in this && + typeof this.limit === "function" && + this.limit instanceof Function && + this.limit(1) !== this ) { this.limit(1); } - const result = await (this.execute(queryExecutor, noManager)); + const result = await this.execute(queryExecutor, noManager); return result !== null - ? Array.isArray(result) && result.length === 2 - && Array.isArray(result?.[0]) - && typeof result?.[1] === 'number' - ? result[0][0] as T ?? null - : result[0] as T ?? null + ? Array.isArray(result) && + result.length === 2 && + Array.isArray(result?.[0]) && + typeof result?.[1] === "number" + ? ((result[0][0] as T) ?? null) + : ((result[0] as T) ?? null) : null; } /** - * Executes the built SQL query and returns multiple results. - * @param queryExecutor The executor to run the SQL query. - * @param noManager If true, bypasses the manager property of the executor object. - * @returns A promise that resolves with an array of results. - * @throws An error if the provided query executor is invalid or if validation fails. - */ + * Executes the built SQL query and returns multiple results. + * @param queryExecutor The executor to run the SQL query. + * @param noManager If true, bypasses the manager property of the executor object. + * @returns A promise that resolves with an array of results. + * @throws An error if the provided query executor is invalid or if validation fails. + */ public async getMany( queryExecutor: QueryExecutor, - noManager: boolean = false + noManager: boolean = false, ): Promise { - return (this.execute(queryExecutor, noManager)); + return this.execute(queryExecutor, noManager); } /** - * Builds the SQL query with EXPLAIN ANALYZE prefix for performance analysis. - * This method is useful for debugging and optimizing SQL queries. - */ - public buildExplainAnalyze() { + * Builds the SQL query with EXPLAIN ANALYZE prefix for performance analysis. + * This method is useful for debugging and optimizing SQL queries. + */ + public buildExplainAnalyze(): { text: string; values: any[] } { const query = this.build(); return { text: `EXPLAIN ANALYZE ${query.text}`, - values: query.values - } + values: query.values, + }; } /** - * Builds the SQL query with EXPLAIN prefix for query plan analysis. - * This method is useful for understanding how the database will execute the query. - */ - public buildExplain() { + * Builds the SQL query with EXPLAIN prefix for query plan analysis. + * This method is useful for understanding how the database will execute the query. + */ + public buildExplain(): { text: string; values: any[] } { const query = this.build(); return { text: `EXPLAIN ${query.text}`, - values: query.values - } + values: query.values, + }; } - + /** - * Re-analyzes the parsed SQL query to identify and consolidate duplicate parameters. - * This method helps optimize the query by reducing the number of parameters used. - * It can perform deep equality checks if specified. - */ + * Re-analyzes the parsed SQL query to identify and consolidate duplicate parameters. + * This method helps optimize the query by reducing the number of parameters used. + * It can perform deep equality checks if specified. + */ protected reAnalyzeParsedQueryForDuplicateParams( query: string, values: any[], - useDeepEqual: boolean = false + useDeepEqual: boolean = false, ): { text: string; values: any[] } { - return DmlQueryDefinition.reAnalyzeParsedQueryForDuplicateParams(query, values, useDeepEqual); + return DmlQueryDefinition.reAnalyzeParsedQueryForDuplicateParams( + query, + values, + useDeepEqual, + ); } /** - * Static method to re-analyze a parsed SQL query for duplicate parameters. - * This method can be used independently of any instance of DmlQueryDefinition. - */ + * Static method to re-analyze a parsed SQL query for duplicate parameters. + * This method can be used independently of any instance of DmlQueryDefinition. + */ public static reAnalyzeParsedQueryForDuplicateParams( query: string, values: any[], - useDeepEqual: boolean = false + useDeepEqual: boolean = false, ): { text: string; values: any[] } { const valueMap: Map = new Map(); let paramIndex = 1; - let newValues: any[] = []; + const newValues: any[] = []; const newQuery = query.replace(/\$(\d+)/g, (_, p1) => { - const originalValue = values[parseInt(p1) - 1]; + const originalValue = values[parseInt(p1, 10) - 1]; let foundKey: any = null; if (useDeepEqual) { - for (let [key] of valueMap) { + for (const [key] of valueMap) { if (deepEqual(key, originalValue)) { foundKey = key; break; diff --git a/src/queryKinds/dml/index.ts b/src/queryKinds/dml/index.ts index a1b331b..8d72849 100644 --- a/src/queryKinds/dml/index.ts +++ b/src/queryKinds/dml/index.ts @@ -1,12 +1,12 @@ -export * from './delete.js'; -export { default as DeleteQuery } from './delete.js'; -export * from './insert.js'; -export { default as InsertQuery } from './insert.js'; -export * from './select.js'; -export { default as SelectQuery } from './select.js'; -export * from './update.js'; -export { default as UpdateQuery } from './update.js'; -export * from './union.js'; -export { default as Union } from './union.js'; -export * from './dmlQueryDefinition.js'; -export { default as DmlQueryDefinition } from './dmlQueryDefinition.js'; +export * from "./delete.js"; +export { default as DeleteQuery } from "./delete.js"; +export * from "./dmlQueryDefinition.js"; +export { default as DmlQueryDefinition } from "./dmlQueryDefinition.js"; +export * from "./insert.js"; +export { default as InsertQuery } from "./insert.js"; +export * from "./select.js"; +export { default as SelectQuery } from "./select.js"; +export * from "./union.js"; +export { default as Union } from "./union.js"; +export * from "./update.js"; +export { default as UpdateQuery } from "./update.js"; diff --git a/src/queryKinds/dml/insert.ts b/src/queryKinds/dml/insert.ts index fdcab75..7e8696c 100644 --- a/src/queryKinds/dml/insert.ts +++ b/src/queryKinds/dml/insert.ts @@ -1,15 +1,15 @@ -import CteMaker, { Cte } from "../../cteMaker.js"; +import CteMaker, { type Cte } from "../../cteMaker.js"; import SqlEscaper from "../../sqlEscaper.js"; -import ColumnValue from "../../types/ColumnValue.js"; +import type ColumnValue from "../../types/ColumnValue.js"; import QueryKind from "../../types/QueryKind.js"; import DmlQueryDefinition from "./dmlQueryDefinition.js"; -import SelectQuery from "./select.js"; +import type SelectQuery from "./select.js"; /** - * InsertQuery helps in constructing SQL INSERT queries. - * It supports inserting values directly or from a SELECT query. - * It also supports Common Table Expressions (CTEs) and RETURNING clauses. - */ + * InsertQuery helps in constructing SQL INSERT queries. + * It supports inserting values directly or from a SELECT query. + * It also supports Common Table Expressions (CTEs) and RETURNING clauses. + */ export default class InsertQuery extends DmlQueryDefinition { /** The table into which records will be inserted. */ private table: string; @@ -21,20 +21,20 @@ export default class InsertQuery extends DmlQueryDefinition { private returningFields: string[] = []; /** - * Creates an instance of InsertQuery. - * @param table - The name of the table into which records will be inserted. - */ + * Creates an instance of InsertQuery. + * @param table - The name of the table into which records will be inserted. + */ constructor(table?: string) { super(); - this.table = table ? SqlEscaper.escapeTableName(table, this.flavor) : ''; + this.table = table ? SqlEscaper.escapeTableName(table, this.flavor) : ""; } /** - * Adds Common Table Expressions (CTEs) to the query. - * Accepts a CteMaker instance, a single Cte, or an array of Ctes. - * @param ctes - The CTEs to be added to the query. - * @returns The current InsertQuery instance for method chaining. - */ + * Adds Common Table Expressions (CTEs) to the query. + * Accepts a CteMaker instance, a single Cte, or an array of Ctes. + * @param ctes - The CTEs to be added to the query. + * @returns The current InsertQuery instance for method chaining. + */ public with(ctes: CteMaker | Cte | Cte[]): this { if (ctes instanceof CteMaker) { this.ctes = ctes; @@ -46,96 +46,108 @@ export default class InsertQuery extends DmlQueryDefinition { return this; } - /** - * Specifies the table into which records will be inserted. - * @param table - The name of the table. - * @returns The current InsertQuery instance for method chaining. - */ + /** + * Specifies the table into which records will be inserted. + * @param table - The name of the table. + * @returns The current InsertQuery instance for method chaining. + */ public into(table: string): this { this.table = SqlEscaper.escapeTableName(table, this.flavor); return this; } /** - * Specifies the column-value pairs to be inserted. - * Accepts either an array of ColumnValue objects or an object mapping column names to values. - * @param columnValues - The column-value pairs to be inserted. - * @returns The current InsertQuery instance for method chaining. - */ + * Specifies the column-value pairs to be inserted. + * Accepts either an array of ColumnValue objects or an object mapping column names to values. + * @param columnValues - The column-value pairs to be inserted. + * @returns The current InsertQuery instance for method chaining. + */ public values(columnValues: ColumnValue[] | { [column: string]: any }): this { if (Array.isArray(columnValues)) { this.columnValues = columnValues - .filter(v => v.value !== undefined) - .map(v => ({ - column: SqlEscaper.escapeIdentifier(v.column, this.flavor), value: v.value ?? null + .filter((v) => v.value !== undefined) + .map((v) => ({ + column: SqlEscaper.escapeIdentifier(v.column, this.flavor), + value: v.value ?? null, })); } else { this.columnValues = Object.entries(columnValues) .filter(([, value]) => value !== undefined) .map(([column, value]) => ({ column: SqlEscaper.escapeIdentifier(column, this.flavor), - value: value ?? null + value: value ?? null, })); } return this; } /** - * Specifies the columns to be inserted without associated values. - * This is useful when inserting data from a SELECT query. - * @param columns - The names of the columns to be inserted. - * @returns The current InsertQuery instance for method chaining. - */ + * Specifies the columns to be inserted without associated values. + * This is useful when inserting data from a SELECT query. + * @param columns - The names of the columns to be inserted. + * @returns The current InsertQuery instance for method chaining. + */ public columns(...columns: string[]): this { - this.columnValues = columns.map(column => ({ - column: SqlEscaper.escapeIdentifier(column, this.flavor), value: undefined + this.columnValues = columns.map((column) => ({ + column: SqlEscaper.escapeIdentifier(column, this.flavor), + value: undefined, })); return this; } /** - * Specifies a SELECT query to insert data from. - * This allows inserting records based on the results of another query. - * @param query - The SELECT query to insert data from. - * @returns The current InsertQuery instance for method chaining. - */ + * Specifies a SELECT query to insert data from. + * This allows inserting records based on the results of another query. + * @param query - The SELECT query to insert data from. + * @returns The current InsertQuery instance for method chaining. + */ public fromSelect(query: SelectQuery): this { this.selectQuery = query; return this; } - /** - * Specifies the fields to be returned after the insert operation. - * @param fields - A single field or an array of fields to be returned. - * @returns The current InsertQuery instance for method chaining. - */ + /** + * Specifies the fields to be returned after the insert operation. + * @param fields - A single field or an array of fields to be returned. + * @returns The current InsertQuery instance for method chaining. + */ public returning(fields: string | string[]): this { if (Array.isArray(fields)) { - this.returningFields = SqlEscaper.escapeSelectIdentifiers(fields, this.flavor); + this.returningFields = SqlEscaper.escapeSelectIdentifiers( + fields, + this.flavor, + ); } else { - this.returningFields = SqlEscaper.escapeSelectIdentifiers([fields], this.flavor); + this.returningFields = SqlEscaper.escapeSelectIdentifiers( + [fields], + this.flavor, + ); } return this; } /** - * Adds fields to the existing RETURNING clause. - * @param fields - A single field or an array of fields to be added to the RETURNING clause. - * @returns The current InsertQuery instance for method chaining. - */ + * Adds fields to the existing RETURNING clause. + * @param fields - A single field or an array of fields to be added to the RETURNING clause. + * @returns The current InsertQuery instance for method chaining. + */ public addReturning(fields: string | string[]): this { if (Array.isArray(fields)) { - this.returningFields.push(...SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); + this.returningFields.push( + ...SqlEscaper.escapeSelectIdentifiers(fields, this.flavor), + ); } else { - this.returningFields.push(...SqlEscaper.escapeSelectIdentifiers([fields], this.flavor)); + this.returningFields.push( + ...SqlEscaper.escapeSelectIdentifiers([fields], this.flavor), + ); } return this; } - /** - * Creates a deep clone of the current InsertQuery instance. - * @returns A new InsertQuery instance with the same properties as the original. - */ + /** + * Creates a deep clone of the current InsertQuery instance. + * @returns A new InsertQuery instance with the same properties as the original. + */ public clone(): InsertQuery { const cloned = new InsertQuery(); cloned.table = this.table; @@ -143,38 +155,38 @@ export default class InsertQuery extends DmlQueryDefinition { cloned.columnValues = JSON.parse(JSON.stringify(this.columnValues)); cloned.selectQuery = this.selectQuery ? this.selectQuery.clone() : null; cloned.returningFields = [...this.returningFields]; - cloned.ctes = this.ctes ? new CteMaker(...this.ctes['ctes']) : null; + cloned.ctes = this.ctes ? new CteMaker(...this.ctes["ctes"]) : null; return cloned; } /** - * This an INSERT query. - * @returns The kind of SQL operation, which is 'INSERT' for this class. - */ + * This an INSERT query. + * @returns The kind of SQL operation, which is 'INSERT' for this class. + */ public get kind() { return QueryKind.INSERT; } - /** - * Invalidates the current state of the query, forcing a rebuild on the next operation. - * @returns void - */ - public invalidate(): void { + /** + * Invalidates the current state of the query, forcing a rebuild on the next operation. + * @returns void + */ + public override invalidate(): void { this.builtQuery = null; this.selectQuery?.invalidate(); if (this.ctes) { - for (const cte of this.ctes['ctes']) { - cte['query'].invalidate(); + for (const cte of this.ctes["ctes"]) { + cte["query"].invalidate(); } } } - /** - * Resets the query to its initial state. - * @returns void - */ + /** + * Resets the query to its initial state. + * @returns void + */ public reset(): void { - this.table = ''; + this.table = ""; this.schemas = []; this.columnValues = []; this.selectQuery = null; @@ -183,15 +195,15 @@ export default class InsertQuery extends DmlQueryDefinition { this.ctes = null; } - /** - * Retrieves the parameters associated with the query. - * @returns An array of parameters for the query. - */ + /** + * Retrieves the parameters associated with the query. + * @returns An array of parameters for the query. + */ private getInternalParams(): any[] { if (!this.builtQuery) this.build(); let params: any[] = []; if (this.columnValues.length > 0) { - params = this.columnValues.map(cv => cv.value); + params = this.columnValues.map((cv) => cv.value); } else if (this.selectQuery) { params = this.selectQuery.getParams(); } @@ -201,60 +213,66 @@ export default class InsertQuery extends DmlQueryDefinition { return params; } - /** - * Retrieves the parameters associated with the query, building the query if necessary. - * @returns An array of parameters for the query. - */ + /** + * Retrieves the parameters associated with the query, building the query if necessary. + * @returns An array of parameters for the query. + */ public getParams(): any[] { if (!this.builtQuery) this.build(); - if (!this.builtParams) throw new Error('Failed to build query parameters.'); + if (!this.builtParams) throw new Error("Failed to build query parameters."); return this.builtParams; } - /** - * Builds the SQL INSERT query and returns an object containing the query text and its parameters. - * The optional deepAnalysis parameter can be used to control the depth of analysis during the build process. - * @param deepAnalysis - Whether to perform deep analysis during the build process. - * @returns An object containing the query text and its parameters. - * @throws Error if no table is specified or if neither values nor a SELECT query is provided. - */ + /** + * Builds the SQL INSERT query and returns an object containing the query text and its parameters. + * The optional deepAnalysis parameter can be used to control the depth of analysis during the build process. + * @param deepAnalysis - Whether to perform deep analysis during the build process. + * @returns An object containing the query text and its parameters. + * @throws Error if no table is specified or if neither values nor a SELECT query is provided. + */ public build(deepAnalysis: boolean = false): { text: string; values: any[] } { if (!this.table) { - throw new Error('No table specified for INSERT query.'); + throw new Error("No table specified for INSERT query."); } if (this.columnValues.length === 0 && !this.selectQuery) { - throw new Error('No values or SELECT query specified for INSERT query.'); + throw new Error("No values or SELECT query specified for INSERT query."); } - let ctesClause = ''; + let ctesClause = ""; let cteValues: any[] = []; if (this.ctes) { const ctesBuilt = this.ctes.build(); ctesClause = ctesBuilt.text; cteValues = ctesBuilt.values; this.selectQuery?.addWhereOffset(cteValues.length); - } - - const columns = this.columnValues.map(cv => cv.column); - let insertClause = `INSERT INTO ${this.table} (${columns.join(', ')})`; + } + + const columns = this.columnValues.map((cv) => cv.column); + let insertClause = `INSERT INTO ${this.table} (${columns.join(", ")})`; if (this.columnValues.length > 0) { - if (this.columnValues.some(cv => cv.value !== undefined)) { - const valuePlaceholders = this.columnValues.map((_, idx) => `$${idx + 1}`); - insertClause += ` VALUES (${valuePlaceholders.join(', ')})`; + if (this.columnValues.some((cv) => cv.value !== undefined)) { + const valuePlaceholders = this.columnValues.map( + (_, idx) => `$${idx + 1}`, + ); + insertClause += ` VALUES (${valuePlaceholders.join(", ")})`; } } else if (this.selectQuery) { // Use columns from select query if not specified const selectColumns = this.selectQuery.columns; if (selectColumns.length > 0 && columns.length === 0) { - const parsedColumns = selectColumns.map(col => { - const regex = /^(?:(?:"?[\w$]+"?\.)?"?([\w$]+)"?(?:\s+AS\s+"?([\w$]+)"?)?)$/i; + const parsedColumns = selectColumns.map((col) => { + const regex = + /^(?:(?:"?[\w$]+"?\.)?"?([\w$]+)"?(?:\s+AS\s+"?([\w$]+)"?)?)$/i; const match = col.match(regex); if (match) { - return SqlEscaper.escapeIdentifier(match[2]! || match[1]!, this.flavor); + return SqlEscaper.escapeIdentifier( + match[2]! || match[1]!, + this.flavor, + ); } return SqlEscaper.escapeIdentifier(col, this.flavor); }); - insertClause = `INSERT INTO ${this.table} (${parsedColumns.join(', ')})`; + insertClause = `INSERT INTO ${this.table} (${parsedColumns.join(", ")})`; } } @@ -264,26 +282,26 @@ export default class InsertQuery extends DmlQueryDefinition { cteValues = [...cteValues, ...selectBuilt.values]; } - let returningClause = ''; + let returningClause = ""; if (this.returningFields.length > 0) { - returningClause = `RETURNING ${this.returningFields.join(', ')}`; + returningClause = `RETURNING ${this.returningFields.join(", ")}`; } const text = [ - ctesClause ? `${ctesClause} ` : '', + ctesClause ? `${ctesClause} ` : "", insertClause, - returningClause - ].join('\n').trim(); + returningClause, + ] + .join("\n") + .trim(); this.builtQuery = text; - this.builtQuery = SqlEscaper.appendSchemas( - this.builtQuery, this.schemas - ); + this.builtQuery = SqlEscaper.appendSchemas(this.builtQuery, this.schemas); const analyzed = this.reAnalyzeParsedQueryForDuplicateParams( this.builtQuery, [...cteValues, ...this.getInternalParams()], - deepAnalysis + deepAnalysis, ); this.builtQuery = analyzed.text; @@ -291,13 +309,13 @@ export default class InsertQuery extends DmlQueryDefinition { return { text: this.builtQuery, values: this.builtParams }; } - /** - * Converts the built SQL query to a string. - * @returns The SQL query string. - */ + /** + * Converts the built SQL query to a string. + * @returns The SQL query string. + */ public toSQL(): string { if (!this.builtQuery) this.build(); - if (!this.builtQuery) throw new Error('Failed to build query.'); + if (!this.builtQuery) throw new Error("Failed to build query."); return this.builtQuery; } diff --git a/src/queryKinds/dml/select.ts b/src/queryKinds/dml/select.ts index 7a468cd..26d07d6 100644 --- a/src/queryKinds/dml/select.ts +++ b/src/queryKinds/dml/select.ts @@ -1,111 +1,115 @@ -import CteMaker, { Cte } from "../../cteMaker.js"; +import CteMaker, { type Cte } from "../../cteMaker.js"; import SqlEscaper from "../../sqlEscaper.js"; import Statement from "../../statementMaker.js"; -import Join, { isJoinTable } from "../../types/Join.js"; +import type Join from "../../types/Join.js"; +import { isJoinTable } from "../../types/Join.js"; +import type OrderBy from "../../types/OrderBy.js"; +import { isOrderByField } from "../../types/OrderBy.js"; import QueryKind from "../../types/QueryKind.js"; -import OrderBy, { isOrderByField } from "../../types/OrderBy.js"; import DmlQueryDefinition from "./dmlQueryDefinition.js"; import Union from "./union.js"; /** - * SelectQuery class represents a SQL SELECT query. - * It includes methods to build various parts of the query such as SELECT fields, WHERE conditions, JOINs, ORDER BY, LIMIT, OFFSET, GROUP BY, and CTEs. - * The class provides functionality to build the final SQL query string and manage query parameters. - */ + * SelectQuery class represents a SQL SELECT query. + * It includes methods to build various parts of the query such as SELECT fields, WHERE conditions, JOINs, ORDER BY, LIMIT, OFFSET, GROUP BY, and CTEs. + * The class provides functionality to build the final SQL query string and manage query parameters. + */ export default class SelectQuery extends DmlQueryDefinition { /** - * The table to select from. - */ + * The table to select from. + */ private table: string; /** - * An optional alias for the table. - */ + * An optional alias for the table. + */ private tableAlias: string | null = null; /** - * Indicates whether the SELECT is DISTINCT. - */ + * Indicates whether the SELECT is DISTINCT. + */ private distinctSelect: boolean = false; /** - * The fields to select. - */ + * The fields to select. + */ private selectFields: string[]; /** - * The HAVING clause statement. - */ + * The HAVING clause statement. + */ private havingStatement: Statement | null = null; /** - * The JOIN clauses. - */ + * The JOIN clauses. + */ private joins: Join[] = []; /** - * The ORDER BY clauses. - */ + * The ORDER BY clauses. + */ private orderBys: OrderBy[] = []; /** - * The LIMIT count. - */ + * The LIMIT count. + */ private limitCount: number | null = null; /** - * The OFFSET count. - */ + * The OFFSET count. + */ private offsetCount: number | null = null; /** - * The GROUP BY fields. - */ + * The GROUP BY fields. + */ private groupBys: string[] = []; /** - * If true, automatically includes all selected fields in the GROUP BY clause. - */ + * If true, automatically includes all selected fields in the GROUP BY clause. + */ private groupBySelectFields: boolean = false; /** - * If true, disables deep analysis of the query for duplicate parameters. - */ + * If true, disables deep analysis of the query for duplicate parameters. + */ private disabledAnalysis: boolean = false; /** - * Creates a new SelectQuery instance. - * @param from The table to select from. - * @param alias An optional alias for the table. - * @param groupBySelectFields If true, automatically includes all selected fields in the GROUP BY clause. - */ + * Creates a new SelectQuery instance. + * @param from The table to select from. + * @param alias An optional alias for the table. + * @param groupBySelectFields If true, automatically includes all selected fields in the GROUP BY clause. + */ constructor( from?: string, alias: string | null = null, - groupBySelectFields: boolean = false + groupBySelectFields: boolean = false, ) { super(); - const escapedFrom = from ? SqlEscaper.escapeTableName(from, this.flavor) : ''; + const escapedFrom = from + ? SqlEscaper.escapeTableName(from, this.flavor) + : ""; this.table = escapedFrom; this.tableAlias = alias; - this.selectFields = ['*']; + this.selectFields = ["*"]; this.groupBySelectFields = groupBySelectFields; } /** - * Gets the selected columns. - * @returns A readonly array of selected column names. - */ + * Gets the selected columns. + * @returns A readonly array of selected column names. + */ public get columns(): Readonly { return this.selectFields; } /** - * Add an offset to the WHERE clause parameters. - * This is useful when combining multiple statements to ensure parameter indices are correct. - * @param offset The offset to add to the parameter indices. - * @returns The current SelectQuery instance for chaining. - */ + * Add an offset to the WHERE clause parameters. + * This is useful when combining multiple statements to ensure parameter indices are correct. + * @param offset The offset to add to the parameter indices. + * @returns The current SelectQuery instance for chaining. + */ public addWhereOffset(offset: number): this { if (this.whereStatement) { this.whereStatement.addOffset(offset); @@ -115,10 +119,10 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Resets the WHERE clause parameter offset to zero. - * This is useful when reusing the query in different contexts. - * @return The current SelectQuery instance for chaining. - */ + * Resets the WHERE clause parameter offset to zero. + * This is useful when reusing the query in different contexts. + * @return The current SelectQuery instance for chaining. + */ public resetWhereOffset(): this { if (this.whereStatement) { this.whereStatement.setOffset(1); @@ -128,20 +132,20 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Invalidates the current state of the query, forcing a rebuild on the next operation. - * @returns void - */ + * Invalidates the current state of the query, forcing a rebuild on the next operation. + * @returns void + */ public override invalidate(): void { super.invalidate(); - if(this.havingStatement) this.havingStatement.invalidate(); + if (this.havingStatement) this.havingStatement.invalidate(); } /** - * Adds CTEs to the query. - * Can accept a CteMaker instance, a single Cte object, or an array of Cte objects. - * @param ctes The CTEs to add. - * @returns The current SelectQuery instance for chaining. - */ + * Adds CTEs to the query. + * Can accept a CteMaker instance, a single Cte object, or an array of Cte objects. + * @param ctes The CTEs to add. + * @returns The current SelectQuery instance for chaining. + */ public with(ctes: CteMaker | Cte | Cte[]): this { if (ctes instanceof CteMaker) { this.ctes = ctes; @@ -154,11 +158,11 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Sets the table to select from, with an optional alias. - * @param table The table name. - * @param alias An optional alias for the table. - * @returns The current SelectQuery instance for chaining. - */ + * Sets the table to select from, with an optional alias. + * @param table The table name. + * @param alias An optional alias for the table. + * @returns The current SelectQuery instance for chaining. + */ public from(table: string, alias: string | null = null): this { const escapedTable = SqlEscaper.escapeTableName(table, this.flavor); this.table = escapedTable; @@ -167,20 +171,20 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Enables DISTINCT selection. - * @returns The current SelectQuery instance for chaining. - */ + * Enables DISTINCT selection. + * @returns The current SelectQuery instance for chaining. + */ public distinct(): this { this.distinctSelect = true; return this; } /** - * Sets the fields to select from. - * Can accept a single field as a string or multiple fields as an array of strings. - * @param fields The fields to select. - * @returns The current SelectQuery instance for chaining. - */ + * Sets the fields to select from. + * Can accept a single field as a string or multiple fields as an array of strings. + * @param fields The fields to select. + * @returns The current SelectQuery instance for chaining. + */ public select(fields: string | string[]): this { if (Array.isArray(fields)) { this.rawSelect(SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); @@ -191,11 +195,11 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Sets raw SQL fields to select from, without any escaping. - * Can accept a single field as a string or multiple fields as an array of strings. - * @param rawFields The raw SQL fields to select. - * @returns The current SelectQuery instance for chaining. - */ + * Sets raw SQL fields to select from, without any escaping. + * Can accept a single field as a string or multiple fields as an array of strings. + * @param rawFields The raw SQL fields to select. + * @returns The current SelectQuery instance for chaining. + */ public rawSelect(rawFields: string | string[]): this { if (Array.isArray(rawFields)) { this.selectFields = [...rawFields]; @@ -206,11 +210,11 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Adds raw SQL fields to the existing selection, without any escaping. - * Can accept a single field as a string or multiple fields as an array of strings. - * @param rawFields The raw SQL fields to add to the selection. - * @returns The current SelectQuery instance for chaining. - */ + * Adds raw SQL fields to the existing selection, without any escaping. + * Can accept a single field as a string or multiple fields as an array of strings. + * @param rawFields The raw SQL fields to add to the selection. + * @returns The current SelectQuery instance for chaining. + */ public addRawSelect(rawFields: string | string[]): this { if (Array.isArray(rawFields)) { this.selectFields.push(...rawFields); @@ -221,87 +225,93 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Adds fields to the existing selection. - * Can accept a single field as a string or multiple fields as an array of strings. - * @param fields The fields to add to the selection. - * @returns The current SelectQuery instance for chaining. - */ + * Adds fields to the existing selection. + * Can accept a single field as a string or multiple fields as an array of strings. + * @param fields The fields to add to the selection. + * @returns The current SelectQuery instance for chaining. + */ public addSelect(fields: string | string[]): this { if (Array.isArray(fields)) { - this.addRawSelect(SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); + this.addRawSelect( + SqlEscaper.escapeSelectIdentifiers(fields, this.flavor), + ); } else { - this.addRawSelect(SqlEscaper.escapeSelectIdentifiers([fields], this.flavor)); + this.addRawSelect( + SqlEscaper.escapeSelectIdentifiers([fields], this.flavor), + ); } return this; } /** - * Adds a Statement or raw SQL string as the WHERE clause. - * If a string is provided, it will be converted into a raw Statement. - * @param statement The WHERE clause as a Statement or raw SQL string. - * @param values Optional values for parameterized queries. - * @returns The current SelectQuery instance for chaining. - */ + * Adds a Statement or raw SQL string as the WHERE clause. + * If a string is provided, it will be converted into a raw Statement. + * @param statement The WHERE clause as a Statement or raw SQL string. + * @param values Optional values for parameterized queries. + * @returns The current SelectQuery instance for chaining. + */ public where(statement: Statement | string, ...values: any[]): this { - if (typeof statement === 'string') { - statement = new Statement().raw('', statement, ...values); + if (typeof statement === "string") { + statement = new Statement().raw("", statement, ...values); } - + this.whereStatement = statement; return this; } /** - * Uses a callback to build the WHERE clause statement. - * The callback receives a Statement instance to build upon. - * @param statement A callback function that receives a Statement instance. - * @returns The current SelectQuery instance for chaining. - */ - public useStatement(statement: (stmt: Statement) => Statement | void): this { + * Uses a callback to build the WHERE clause statement. + * The callback receives a Statement instance to build upon. + * @param statement A callback function that receives a Statement instance. + * @returns The current SelectQuery instance for chaining. + */ + public useStatement( + statement: (stmt: Statement) => Statement | undefined, + ): this { const stmt = new Statement(); const newStmt = statement(stmt) || stmt; return this.where(newStmt); } /** - * Adds a Statement or raw SQL string as the HAVING clause. - * If a string is provided, it will be converted into a raw Statement. - * @param statement The HAVING clause as a Statement or raw SQL string. - * @param values Optional values for parameterized queries. - * @returns The current SelectQuery instance for chaining. - */ + * Adds a Statement or raw SQL string as the HAVING clause. + * If a string is provided, it will be converted into a raw Statement. + * @param statement The HAVING clause as a Statement or raw SQL string. + * @param values Optional values for parameterized queries. + * @returns The current SelectQuery instance for chaining. + */ public having(statement: Statement | string, ...values: any[]): this { - if (typeof statement === 'string') { - statement = new Statement().raw('', statement, ...values); + if (typeof statement === "string") { + statement = new Statement().raw("", statement, ...values); } - + this.havingStatement = statement; return this; } /** - * Uses a callback to build the HAVING clause statement. - * The callback receives a Statement instance to build upon. - * @param statement A callback function that receives a Statement instance. - * @returns The current SelectQuery instance for chaining. - */ - public useHavingStatement(statement: (stmt: Statement) => Statement | void): this { + * Uses a callback to build the HAVING clause statement. + * The callback receives a Statement instance to build upon. + * @param statement A callback function that receives a Statement instance. + * @returns The current SelectQuery instance for chaining. + */ + public useHavingStatement( + statement: (stmt: Statement) => Statement | undefined, + ): this { const stmt = new Statement(); const newStmt = statement(stmt) || stmt; return this.having(newStmt); } /** - * Adds JOIN clauses to the query, - * either as a single Join object or an array of Join objects. - * @param join The JOIN clause(s) to add. - * @returns The current SelectQuery instance for chaining. - */ - public join( - join: Join | Join[] - ): this { + * Adds JOIN clauses to the query, + * either as a single Join object or an array of Join objects. + * @param join The JOIN clause(s) to add. + * @returns The current SelectQuery instance for chaining. + */ + public join(join: Join | Join[]): this { if (Array.isArray(join)) { - this.joins.push(...join.map(j => this.parseJoinObject(j))); + this.joins.push(...join.map((j) => this.parseJoinObject(j))); } else { this.joins.push(this.parseJoinObject(join)); } @@ -309,29 +319,25 @@ export default class SelectQuery extends DmlQueryDefinition { } public parseOrderByObject(orderBy: OrderBy): OrderBy { - let field = ''; - if(isOrderByField(orderBy)) field = orderBy.field; + let field = ""; + if (isOrderByField(orderBy)) field = orderBy.field; else field = orderBy.column; return { ...orderBy, - field: SqlEscaper.escapeSelectIdentifiers([field], this.flavor)[0]! + field: SqlEscaper.escapeSelectIdentifiers([field], this.flavor)[0]!, } as OrderBy; } /** - * Adds ORDER BY clauses to the query, - * either as a single OrderBy object or an array of OrderBy objects. - * @param orderBy The ORDER BY clause(s) to add. - * @returns The current SelectQuery instance for chaining. - */ - public orderBy( - orderBy: OrderBy | OrderBy[] - ): this { + * Adds ORDER BY clauses to the query, + * either as a single OrderBy object or an array of OrderBy objects. + * @param orderBy The ORDER BY clause(s) to add. + * @returns The current SelectQuery instance for chaining. + */ + public orderBy(orderBy: OrderBy | OrderBy[]): this { if (Array.isArray(orderBy)) { - this.orderBys.push( - ...orderBy.map(ob => this.parseOrderByObject(ob)) - ); + this.orderBys.push(...orderBy.map((ob) => this.parseOrderByObject(ob))); } else { this.orderBys.push(this.parseOrderByObject(orderBy)); } @@ -339,12 +345,12 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Sets the LIMIT for the query. - * @param count The maximum number of records to return. - * @returns The current SelectQuery instance for chaining. - */ + * Sets the LIMIT for the query. + * @param count The maximum number of records to return. + * @returns The current SelectQuery instance for chaining. + */ public limit(count: number): this { - if (typeof count !== 'number' || count < 0 || !Number.isInteger(count)) { + if (typeof count !== "number" || count < 0 || !Number.isInteger(count)) { throw new Error("Limit must be a non-negative integer."); } this.limitCount = count; @@ -352,12 +358,12 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Sets the OFFSET for the query. - * @param count The number of records to skip. - * @returns The current SelectQuery instance for chaining. - */ + * Sets the OFFSET for the query. + * @param count The number of records to skip. + * @returns The current SelectQuery instance for chaining. + */ public offset(count: number): this { - if (typeof count !== 'number' || count < 0 || !Number.isInteger(count)) { + if (typeof count !== "number" || count < 0 || !Number.isInteger(count)) { throw new Error("Offset must be a non-negative integer."); } this.offsetCount = count; @@ -365,17 +371,17 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Sets both LIMIT and OFFSET for the query. - * @param limit The maximum number of records to return. - * @param offset The number of records to skip. - * @returns The current SelectQuery instance for chaining. - */ + * Sets both LIMIT and OFFSET for the query. + * @param limit The maximum number of records to return. + * @param offset The number of records to skip. + * @returns The current SelectQuery instance for chaining. + */ public limitAndOffset(limit: number, offset: number): this { - if (typeof limit !== 'number' || limit < 0 || !Number.isInteger(limit)) { + if (typeof limit !== "number" || limit < 0 || !Number.isInteger(limit)) { throw new Error("Limit must be a non-negative integer."); } - if (typeof offset !== 'number' || offset < 0 || !Number.isInteger(offset)) { + if (typeof offset !== "number" || offset < 0 || !Number.isInteger(offset)) { throw new Error("Offset must be a non-negative integer."); } @@ -385,9 +391,9 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Resets both LIMIT and OFFSET to null. - * @returns The current SelectQuery instance for chaining. - */ + * Resets both LIMIT and OFFSET to null. + * @returns The current SelectQuery instance for chaining. + */ public resetLimitOffset(): this { this.limitCount = null; this.offsetCount = null; @@ -395,15 +401,15 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Resets the entire query to its initial state. - * This includes clearing the table, selected fields, WHERE clause, JOINs, ORDER BY, LIMIT, OFFSET, GROUP BY, CTEs, and any built query. - * @returns void - */ + * Resets the entire query to its initial state. + * This includes clearing the table, selected fields, WHERE clause, JOINs, ORDER BY, LIMIT, OFFSET, GROUP BY, CTEs, and any built query. + * @returns void + */ public reset(): void { - this.table = ''; + this.table = ""; this.tableAlias = null; this.distinctSelect = false; - this.selectFields = ['*']; + this.selectFields = ["*"]; this.whereStatement = null; this.joins = []; this.orderBys = []; @@ -420,54 +426,58 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Adds fields to the GROUP BY clause, - * either as a single field or an array of fields. - * @param fields The field(s) to add to the GROUP BY clause. - * @returns The current SelectQuery instance for chaining. - */ + * Adds fields to the GROUP BY clause, + * either as a single field or an array of fields. + * @param fields The field(s) to add to the GROUP BY clause. + * @returns The current SelectQuery instance for chaining. + */ public groupBy(fields: string | string[]): this { if (Array.isArray(fields)) { - this.groupBys.push(...SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); + this.groupBys.push( + ...SqlEscaper.escapeSelectIdentifiers(fields, this.flavor), + ); } else { - this.groupBys.push(...SqlEscaper.escapeSelectIdentifiers([fields], this.flavor)); + this.groupBys.push( + ...SqlEscaper.escapeSelectIdentifiers([fields], this.flavor), + ); } return this; } /** - * Enable grouping by all selected fields. - * This automatically adds all selected fields to the GROUP BY clause. - * @returns The current SelectQuery instance for chaining. - */ + * Enable grouping by all selected fields. + * This automatically adds all selected fields to the GROUP BY clause. + * @returns The current SelectQuery instance for chaining. + */ public enableGroupBySelectFields(): this { this.groupBySelectFields = true; return this; } /** - * This is a SELECT query. - * @returns 'SELECT' The kind of query. - */ - public get kind() { + * This is a SELECT query. + * @returns 'SELECT' The kind of query. + */ + public get kind(): QueryKind { return QueryKind.SELECT; } /** - * Get params for the built query. - * If the query is not built yet, it will build it first. - * @returns any[] The parameters for the built query. - */ + * Get params for the built query. + * If the query is not built yet, it will build it first. + * @returns any[] The parameters for the built query. + */ public getParams(): any[] { if (!this.builtParams) this.build(); if (!this.builtParams) throw new Error("Failed to build query."); - return this.builtParams; + return this.builtParams; } /** - * Creates a deep clone of the current SelectQuery instance. - * This is useful for creating variations of a query without modifying the original. - * @returns A new SelectQuery instance that is a clone of the current instance. - */ + * Creates a deep clone of the current SelectQuery instance. + * This is useful for creating variations of a query without modifying the original. + * @returns A new SelectQuery instance that is a clone of the current instance. + */ public clone(): SelectQuery { const cloned = new SelectQuery(); cloned.table = this.table; @@ -477,58 +487,59 @@ export default class SelectQuery extends DmlQueryDefinition { cloned.schemas = [...this.schemas]; cloned.distinctSelect = this.distinctSelect; cloned.selectFields = [...this.selectFields]; - cloned.whereStatement = this.whereStatement ? this.whereStatement.clone() : null; - cloned.joins = this.joins.map(j => ({ - type: j.type, - table: j.table, - alias: j.alias, - on: typeof j.on === "string" ? `${j.on}` : j.on.clone() - }) as Join); + cloned.whereStatement = this.whereStatement + ? this.whereStatement.clone() + : null; + cloned.joins = this.joins.map( + (j) => + ({ + type: j.type, + table: j.table, + alias: j.alias, + on: typeof j.on === "string" ? `${j.on}` : j.on.clone(), + }) as Join, + ); cloned.orderBys = [...this.orderBys]; cloned.limitCount = this.limitCount; cloned.offsetCount = this.offsetCount; cloned.groupBys = [...this.groupBys]; - cloned.ctes = this.ctes ? new CteMaker(...this.ctes['ctes']) : null; + cloned.ctes = this.ctes ? new CteMaker(...this.ctes["ctes"]) : null; return cloned; } /** - * Combines the current SELECT query with another SELECT query using UNION ALL. - * @param query The SELECT query to combine with. - * @returns A new Union instance representing the combined queries. - */ + * Combines the current SELECT query with another SELECT query using UNION ALL. + * @param query The SELECT query to combine with. + * @returns A new Union instance representing the combined queries. + */ public unionAll(query: SelectQuery): Union { - return new Union() - .add(this) - .add(query, 'UNION ALL'); + return new Union().add(this).add(query, "UNION ALL"); } /** - * Combines the current SELECT query with another SELECT query using UNION. - * @param query The SELECT query to combine with. - * @returns A new Union instance representing the combined queries. - */ + * Combines the current SELECT query with another SELECT query using UNION. + * @param query The SELECT query to combine with. + * @returns A new Union instance representing the combined queries. + */ public union(query: SelectQuery): Union { - return new Union() - .add(this) - .add(query, 'UNION'); + return new Union().add(this).add(query, "UNION"); } /** - * Builds the final SQL SELECT query string and returns it along with the associated parameter values. - * If deepAnalysis is true, it will perform a deep analysis to identify and consolidate duplicate parameters. - * Throws an error if the table name is not set. - * @param deepAnalysis Whether to perform deep analysis for duplicate parameters. Default is false. - * @returns An object containing the SQL query string and an array of parameter values. - */ - public build(deepAnalysis: boolean = false): { text: string; values: any[]; } { + * Builds the final SQL SELECT query string and returns it along with the associated parameter values. + * If deepAnalysis is true, it will perform a deep analysis to identify and consolidate duplicate parameters. + * Throws an error if the table name is not set. + * @param deepAnalysis Whether to perform deep analysis for duplicate parameters. Default is false. + * @returns An object containing the SQL query string and an array of parameter values. + */ + public build(deepAnalysis: boolean = false): { text: string; values: any[] } { if (!this.table) { throw new Error("Table name is required for SELECT query."); } this.whereStatement = this.whereStatement || new Statement(); - let ctesClause = ''; + let ctesClause = ""; let cteValues: any[] = []; if (this.ctes) { const ctesBuilt = this.ctes.build(); @@ -537,35 +548,37 @@ export default class SelectQuery extends DmlQueryDefinition { this.whereStatement.addOffset(cteValues.length); } - let selectClause = this.selectFields.length > 0 ? this.selectFields.join(',\n ') : '*'; - if (this.groupBySelectFields && this.selectFields[0] !== '*') { - this.groupBys = Array.from(new Set([...this.groupBys, ...this.selectFields])); + let selectClause = + this.selectFields.length > 0 ? this.selectFields.join(",\n ") : "*"; + if (this.groupBySelectFields && this.selectFields[0] !== "*") { + this.groupBys = Array.from( + new Set([...this.groupBys, ...this.selectFields]), + ); } - - if(this.distinctSelect) - selectClause = ` DISTINCT ${selectClause}` + + if (this.distinctSelect) selectClause = ` DISTINCT ${selectClause}`; else selectClause = ` ${selectClause}`; let fromClause = `FROM ${this.table}`; if (this.tableAlias) fromClause += ` AS ${this.tableAlias}`; - let joinClauses = ''; + let joinClauses = ""; let currentOffset = cteValues.length; - let parametersToAdd: any = []; + const parametersToAdd: any = []; for (const join of this.joins) { - if(isJoinTable(join)) { - const onClause = - typeof join.on === 'string' ? join.on - : (() => { - join.on.disableWhere(); - join.on.addOffset(currentOffset); - const stmt = join.on.build(false); - currentOffset += stmt.values.length; - parametersToAdd.push(...stmt.values); - return stmt.statement; - })(); - joinClauses += - `${joinClauses ? '\n' : ''}${join.type.toUpperCase()} JOIN ${join.table} ${join.alias}\n ON ${onClause}`; + if (isJoinTable(join)) { + const onClause = + typeof join.on === "string" + ? join.on + : (() => { + join.on.disableWhere(); + join.on.addOffset(currentOffset); + const stmt = join.on.build(false); + currentOffset += stmt.values.length; + parametersToAdd.push(...stmt.values); + return stmt.statement; + })(); + joinClauses += `${joinClauses ? "\n" : ""}${join.type.toUpperCase()} JOIN ${join.table} ${join.alias}\n ON ${onClause}`; } else { join.subQuery.resetWhereOffset(); join.subQuery.addWhereOffset(currentOffset); @@ -574,19 +587,19 @@ export default class SelectQuery extends DmlQueryDefinition { join.subQuery.disabledAnalysis = false; currentOffset += subQueryBuilt.values.length; parametersToAdd.push(...subQueryBuilt.values); - const onClause = - typeof join.on === 'string' ? join.on - : (() => { - join.on.disableWhere(); - join.on.addOffset(currentOffset); - const stmt = join.on.build(false); - currentOffset += stmt.values.length; - parametersToAdd.push(...stmt.values); - return stmt.statement; - })(); + const onClause = + typeof join.on === "string" + ? join.on + : (() => { + join.on.disableWhere(); + join.on.addOffset(currentOffset); + const stmt = join.on.build(false); + currentOffset += stmt.values.length; + parametersToAdd.push(...stmt.values); + return stmt.statement; + })(); subQueryBuilt.text = this.spaceLines(subQueryBuilt.text, 1); - joinClauses += - `${joinClauses ? '\n' : ''}${join.type.toUpperCase()} JOIN (\n${subQueryBuilt.text}\n) ${join.alias}\n ON ${onClause}`; + joinClauses += `${joinClauses ? "\n" : ""}${join.type.toUpperCase()} JOIN (\n${subQueryBuilt.text}\n) ${join.alias}\n ON ${onClause}`; } } @@ -597,17 +610,14 @@ export default class SelectQuery extends DmlQueryDefinition { const whereClause = stmt.statement; const values = [...cteValues, ...stmt.values]; - let groupByClause = ''; - if ( - this.groupBys.length > 0 && - !this.groupBySelectFields - ) { - groupByClause = `GROUP BY ${this.groupBys.join(', ')}`; + let groupByClause = ""; + if (this.groupBys.length > 0 && !this.groupBySelectFields) { + groupByClause = `GROUP BY ${this.groupBys.join(", ")}`; } else if (this.groupBySelectFields) { - groupByClause = `GROUP BY ${this.selectFields.join(', ')}`; + groupByClause = `GROUP BY ${this.selectFields.join(", ")}`; } - let havingClause = ''; + let havingClause = ""; if (this.havingStatement) { this.havingStatement.disableWhere(); this.havingStatement.addOffset(values.length); @@ -616,27 +626,27 @@ export default class SelectQuery extends DmlQueryDefinition { values.push(...havingStmt.values); } - let orderByClause = ''; + let orderByClause = ""; if (this.orderBys.length > 0) { - const orders = this.orderBys.map(ob => { - let field = ''; - if(isOrderByField(ob)) field = ob.field + const orders = this.orderBys.map((ob) => { + let field = ""; + if (isOrderByField(ob)) field = ob.field; else field = ob.column; return `${field} ${ob.direction}`; }); - orderByClause = `ORDER BY ${orders.join(', ')}`; + orderByClause = `ORDER BY ${orders.join(", ")}`; } - let limitClause = ''; + let limitClause = ""; if (this.limitCount !== null) limitClause = `LIMIT ${this.limitCount}`; - let offsetClause = ''; + let offsetClause = ""; if (this.offsetCount !== null) offsetClause = `OFFSET ${this.offsetCount}`; const query = [ ctesClause, - 'SELECT', + "SELECT", selectClause, fromClause, joinClauses, @@ -644,16 +654,15 @@ export default class SelectQuery extends DmlQueryDefinition { groupByClause, havingClause, orderByClause, - `${limitClause} ${offsetClause}` - ].map(q => q.trim() ? q : null) + `${limitClause} ${offsetClause}`, + ] + .map((q) => (q.trim() ? q : null)) .filter(Boolean) - .join('\n'); + .join("\n"); this.builtQuery = query.trim(); - this.builtQuery = SqlEscaper.appendSchemas( - this.builtQuery, this.schemas - ); + this.builtQuery = SqlEscaper.appendSchemas(this.builtQuery, this.schemas); if (this.disabledAnalysis) { return { text: this.builtQuery, values }; @@ -661,7 +670,7 @@ export default class SelectQuery extends DmlQueryDefinition { const analyzed = this.reAnalyzeParsedQueryForDuplicateParams( this.builtQuery, values, - deepAnalysis + deepAnalysis, ); this.builtQuery = analyzed.text; this.builtParams = analyzed.values; @@ -670,10 +679,10 @@ export default class SelectQuery extends DmlQueryDefinition { } /** - * Returns the built SQL query string. - * If the query is not built yet, it will build it first. - * @returns string The SQL query string. - */ + * Returns the built SQL query string. + * If the query is not built yet, it will build it first. + * @returns string The SQL query string. + */ public toSQL(): string { if (!this.builtQuery) this.build(); if (!this.builtQuery) throw new Error("Failed to build query."); diff --git a/src/queryKinds/dml/union.ts b/src/queryKinds/dml/union.ts index 8c224c4..5593fba 100644 --- a/src/queryKinds/dml/union.ts +++ b/src/queryKinds/dml/union.ts @@ -1,9 +1,9 @@ +import SqlEscaper from "../../sqlEscaper.js"; import Statement from "../../statementMaker.js"; +import type OrderBy from "../../types/OrderBy.js"; import QueryKind from "../../types/QueryKind.js"; -import OrderBy from "../../types/OrderBy.js"; import DmlQueryDefinition from "./dmlQueryDefinition.js"; -import SelectQuery from "./select.js"; -import SqlEscaper from "../../sqlEscaper.js"; +import type SelectQuery from "./select.js"; /** Allowed types for UnionType */ export const UnionTypes = { @@ -19,29 +19,29 @@ export const UnionTypes = { const unionTypesArray = Object.values(UnionTypes); /** Base types for UnionType */ -type UnionTypeBase = typeof UnionTypes[keyof typeof UnionTypes]; +type UnionTypeBase = (typeof UnionTypes)[keyof typeof UnionTypes]; /** - * UnionType represents the type of SQL UNION operation. - */ + * UnionType represents the type of SQL UNION operation. + */ export type UnionType = Lowercase | UnionTypeBase; /** - * Type representing a SELECT query along with its associated union type. - */ + * Type representing a SELECT query along with its associated union type. + */ export type SelectQueryWithUnionType = { query: SelectQuery; type: UnionType; }; /** - * Union class represents a SQL UNION operation. - * It allows combining multiple SELECT queries into a single result set. - * It is basically a wrapper around multiple SelectQuery instances that - * creates a SELECT query that is the union of all the provided queries. - * It supports adding queries with different union types (UNION or UNION ALL) - * and can optionally assign an alias to the resulting union query. - */ + * Union class represents a SQL UNION operation. + * It allows combining multiple SELECT queries into a single result set. + * It is basically a wrapper around multiple SelectQuery instances that + * creates a SELECT query that is the union of all the provided queries. + * It supports adding queries with different union types (UNION or UNION ALL) + * and can optionally assign an alias to the resulting union query. + */ export default class Union extends DmlQueryDefinition { /** The selected fields for the union query */ private selectFields: string[] = []; @@ -68,15 +68,15 @@ export default class Union extends DmlQueryDefinition { private havingStatement: Statement | null = null; /** - * Checks if all added SELECT queries have the same number of fields. - * This is important for ensuring that the UNION operation is valid. - * @returns True if all SELECT queries have the same number of fields, - * or the index of the first query that differs in field count. - */ + * Checks if all added SELECT queries have the same number of fields. + * This is important for ensuring that the UNION operation is valid. + * @returns True if all SELECT queries have the same number of fields, + * or the index of the first query that differs in field count. + */ private allSelectsHaveSameNumberOfFields(): number | null { if (this.selectQueries.length === 0) return null; - const selects = this.selectQueries.map(sq => sq.query); + const selects = this.selectQueries.map((sq) => sq.query); const firstSelectFieldCount = selects[0]?.columns.length || 0; @@ -92,35 +92,35 @@ export default class Union extends DmlQueryDefinition { } /** - * Make the union without selecting from it - * Useful when the raw union is needed as a subquery - * @throws Error if no SELECT queries have been added to the union - * @throws Error if the SELECT queries do not have the same number of fields - * @returns An object containing the raw SQL text of the union and its parameter values. - */ + * Make the union without selecting from it + * Useful when the raw union is needed as a subquery + * @throws Error if no SELECT queries have been added to the union + * @throws Error if the SELECT queries do not have the same number of fields + * @returns An object containing the raw SQL text of the union and its parameter values. + */ public rawUnion(): { text: string; values: any[] } { if (this.selectQueries.length === 0) { - throw new Error('No SELECT queries added to the UNION.'); + throw new Error("No SELECT queries added to the UNION."); } - + const differingIndex = this.allSelectsHaveSameNumberOfFields(); if (differingIndex !== null) { - console.log('This is erroring out') + console.log("This is erroring out"); throw new Error( `All SELECT queries must have the same number of fields. Query at index ${differingIndex} differs.`, - { cause: { selectQuery: this.selectQueries[differingIndex]?.query } } + { cause: { selectQuery: this.selectQueries[differingIndex]?.query } }, ); } - let unionItself: string = ''; + let unionItself: string = ""; const values: any[] = []; let paramOffset = 1; for (const { query, type } of this.selectQueries) { (query as any).disabledAnalysis = true; query.resetWhereOffset(); - let builtQuery = query.addWhereOffset(paramOffset - 1).build(); - unionItself += (unionItself ? `\n\n${type}\n\n` : '') + `${builtQuery.text}`; + const builtQuery = query.addWhereOffset(paramOffset - 1).build(); + unionItself += `${unionItself ? `\n\n${type}\n\n` : ""}${builtQuery.text}`; paramOffset += builtQuery.values.length; values.push(...builtQuery.values); } @@ -128,21 +128,21 @@ export default class Union extends DmlQueryDefinition { const analyzed = this.reAnalyzeParsedQueryForDuplicateParams( unionItself, values, - false + false, ); return { text: analyzed.text, - values: analyzed.values + values: analyzed.values, }; } /** - * Specifies the fields to select in the union query. - * If not called, defaults to selecting all fields ('*'). - * @param fields A single field name or an array of field names to select. - * @returns The current Union instance for method chaining. - */ + * Specifies the fields to select in the union query. + * If not called, defaults to selecting all fields ('*'). + * @param fields A single field name or an array of field names to select. + * @returns The current Union instance for method chaining. + */ public select(fields: string | string[]): Union { if (Array.isArray(fields)) { this.rawSelect(SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); @@ -153,26 +153,30 @@ export default class Union extends DmlQueryDefinition { } /** - * Adds fields to the existing selection in the union query. - * If no fields have been selected yet, this behaves like the select() method. - * @param fields A single field name or an array of field names to add to the selection. - * @returns The current Union instance for method chaining. - */ + * Adds fields to the existing selection in the union query. + * If no fields have been selected yet, this behaves like the select() method. + * @param fields A single field name or an array of field names to add to the selection. + * @returns The current Union instance for method chaining. + */ public addSelect(fields: string | string[]): Union { if (Array.isArray(fields)) { - this.addRawSelect(SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); + this.addRawSelect( + SqlEscaper.escapeSelectIdentifiers(fields, this.flavor), + ); } else { - this.addRawSelect(SqlEscaper.escapeSelectIdentifiers([fields], this.flavor)); + this.addRawSelect( + SqlEscaper.escapeSelectIdentifiers([fields], this.flavor), + ); } return this; } /** - * Specifies raw fields to select in the union query without any escaping. - * Use this method with caution, as it does not perform any SQL injection protection. - * @param fields A single raw field string or an array of raw field strings to select. - * @returns The current Union instance for method chaining. - */ + * Specifies raw fields to select in the union query without any escaping. + * Use this method with caution, as it does not perform any SQL injection protection. + * @param fields A single raw field string or an array of raw field strings to select. + * @returns The current Union instance for method chaining. + */ public rawSelect(fields: string | string[]): Union { if (Array.isArray(fields)) { this.selectFields = fields; @@ -183,12 +187,12 @@ export default class Union extends DmlQueryDefinition { } /** - * Adds raw fields to the existing selection in the union query without any escaping. - * Use this method with caution, as it does not perform any SQL injection protection. - * If no fields have been selected yet, this behaves like the rawSelect() method. - * @param fields A single raw field string or an array of raw field strings to add to the selection. - * @returns The current Union instance for method chaining. - */ + * Adds raw fields to the existing selection in the union query without any escaping. + * Use this method with caution, as it does not perform any SQL injection protection. + * If no fields have been selected yet, this behaves like the rawSelect() method. + * @param fields A single raw field string or an array of raw field strings to add to the selection. + * @returns The current Union instance for method chaining. + */ public addRawSelect(fields: string | string[]): Union { if (Array.isArray(fields)) { this.selectFields.push(...fields); @@ -199,36 +203,38 @@ export default class Union extends DmlQueryDefinition { } /** - * Assigns an alias to the resulting union query. - * @param alias The alias to assign to the union query. - * @returns The current Union instance for method chaining. - */ + * Assigns an alias to the resulting union query. + * @param alias The alias to assign to the union query. + * @returns The current Union instance for method chaining. + */ public as(alias: string): Union { this.unionAlias = alias; return this; } /** - * Adds a SELECT query to the union with the specified union type. - * @param query The SelectQuery instance to add to the union. - * @param type The type of union operation ('UNION' or 'UNION ALL'). Defaults to 'UNION ALL'. - * @returns The current Union instance for method chaining. - * @throws Error if an invalid union type is provided. - */ - public add(query: SelectQuery, type: UnionType = 'UNION ALL'): Union { + * Adds a SELECT query to the union with the specified union type. + * @param query The SelectQuery instance to add to the union. + * @param type The type of union operation ('UNION' or 'UNION ALL'). Defaults to 'UNION ALL'. + * @returns The current Union instance for method chaining. + * @throws Error if an invalid union type is provided. + */ + public add(query: SelectQuery, type: UnionType = "UNION ALL"): Union { type = type.toUpperCase() as UnionTypeBase; if (!unionTypesArray.includes(type)) - throw new Error("Invalid union type. Only 'UNION' and 'UNION ALL' are allowed."); + throw new Error( + "Invalid union type. Only 'UNION' and 'UNION ALL' are allowed.", + ); this.selectQueries.push({ query, type }); return this; } /** - * Adds multiple SELECT queries to the union. - * @param queries An array of objects containing SelectQuery instances and their corresponding union types. - * @returns The current Union instance for method chaining. - */ + * Adds multiple SELECT queries to the union. + * @param queries An array of objects containing SelectQuery instances and their corresponding union types. + * @returns The current Union instance for method chaining. + */ public addMany(queries: SelectQueryWithUnionType[]): Union { queries.forEach(({ query, type }) => { this.add(query, type); @@ -237,12 +243,15 @@ export default class Union extends DmlQueryDefinition { } /** - * Adds multiple SELECT queries to the union of the same union type. - * @param queries An array of SelectQuery instances to add to the union. - * @param type The type of union operation ('UNION' or 'UNION ALL'). Defaults to 'UNION ALL'. - * @returns The current Union instance for method chaining. - */ - public addManyOfType(queries: SelectQuery[], type: UnionType = 'UNION ALL'): Union { + * Adds multiple SELECT queries to the union of the same union type. + * @param queries An array of SelectQuery instances to add to the union. + * @param type The type of union operation ('UNION' or 'UNION ALL'). Defaults to 'UNION ALL'. + * @returns The current Union instance for method chaining. + */ + public addManyOfType( + queries: SelectQuery[], + type: UnionType = "UNION ALL", + ): Union { queries.forEach((query) => { this.add(query, type); }); @@ -250,26 +259,28 @@ export default class Union extends DmlQueryDefinition { } /** - * Adds a WHERE clause to the union query. - * @param statement The WHERE clause as a Statement instance or a raw SQL string. - * @param values Optional parameter values if a raw SQL string is provided. - * @returns The current Union instance for method chaining. - */ + * Adds a WHERE clause to the union query. + * @param statement The WHERE clause as a Statement instance or a raw SQL string. + * @param values Optional parameter values if a raw SQL string is provided. + * @returns The current Union instance for method chaining. + */ public where(statement: Statement | string, ...values: any[]): Union { - if (typeof statement === 'string') { - statement = new Statement().raw('', statement, ...values); + if (typeof statement === "string") { + statement = new Statement().raw("", statement, ...values); } this.whereStatement = statement; return this; } /** - * Adds a WHERE clause to the union query using a callback function. - * The callback function receives a Statement instance to build the WHERE clause. - * @param statement A callback function that takes a Statement instance and returns a Statement or void. - * @returns The current Union instance for method chaining. - */ - public useStatement(statement: (stmt: Statement) => Statement | void): Union { + * Adds a WHERE clause to the union query using a callback function. + * The callback function receives a Statement instance to build the WHERE clause. + * @param statement A callback function that takes a Statement instance and returns a Statement or void. + * @returns The current Union instance for method chaining. + */ + public useStatement( + statement: (stmt: Statement) => Statement | undefined, + ): Union { const stmt = new Statement(); const newStatement = statement(stmt) || stmt; @@ -278,52 +289,52 @@ export default class Union extends DmlQueryDefinition { } /** - * Sets a LIMIT on the number of rows returned by the union query. - * @param limit The maximum number of rows to return. Must be a non-negative integer. - * @returns The current Union instance for method chaining. - * @throws Error if the limit is negative or not an integer. - */ + * Sets a LIMIT on the number of rows returned by the union query. + * @param limit The maximum number of rows to return. Must be a non-negative integer. + * @returns The current Union instance for method chaining. + * @throws Error if the limit is negative or not an integer. + */ public limit(limit: number): Union { if (limit < 0 || !Number.isInteger(limit)) { - throw new Error('Limit must be a non-negative integer.'); + throw new Error("Limit must be a non-negative integer."); } this.limitCount = limit; return this; } /** - * Sets an OFFSET for the union query. - * @param offset The number of rows to skip before starting to return rows. Must be a non-negative integer. - * @returns The current Union instance for method chaining. - * @throws Error if the offset is negative or not an integer. - */ + * Sets an OFFSET for the union query. + * @param offset The number of rows to skip before starting to return rows. Must be a non-negative integer. + * @returns The current Union instance for method chaining. + * @throws Error if the offset is negative or not an integer. + */ public offset(offset: number): Union { if (offset < 0 || !Number.isInteger(offset)) { - throw new Error('Offset must be a non-negative integer.'); + throw new Error("Offset must be a non-negative integer."); } this.offsetCount = offset; return this; } /** - * Sets both LIMIT and OFFSET for the union query. - * @param limit The maximum number of rows to return. Must be a non-negative integer. - * @param offset The number of rows to skip before starting to return rows. Must be a non-negative integer. - * @returns The current Union instance for method chaining. - * @throws Error if the limit or offset is negative or not an integer. - */ + * Sets both LIMIT and OFFSET for the union query. + * @param limit The maximum number of rows to return. Must be a non-negative integer. + * @param offset The number of rows to skip before starting to return rows. Must be a non-negative integer. + * @returns The current Union instance for method chaining. + * @throws Error if the limit or offset is negative or not an integer. + */ public limitAndOffset(limit: number, offset: number): Union { return this.limit(limit).offset(offset); } /** - * Sets the ORDER BY clauses for the union query, replacing any existing clauses. - * @param orderBy A single OrderBy object or an array of OrderBy objects. - * @returns The current Union instance for method chaining. - */ + * Sets the ORDER BY clauses for the union query, replacing any existing clauses. + * @param orderBy A single OrderBy object or an array of OrderBy objects. + * @returns The current Union instance for method chaining. + */ public orderBy(orderBy: OrderBy | OrderBy[]): Union { if (Array.isArray(orderBy)) { - this.orderBys = orderBy + this.orderBys = orderBy; } else { this.orderBys = [orderBy]; } @@ -331,13 +342,13 @@ export default class Union extends DmlQueryDefinition { } /** - * Adds ORDER BY clauses to the union query without replacing existing clauses. - * @param orderBy A single OrderBy object or an array of OrderBy objects to add. - * @returns The current Union instance for method chaining. - */ + * Adds ORDER BY clauses to the union query without replacing existing clauses. + * @param orderBy A single OrderBy object or an array of OrderBy objects to add. + * @returns The current Union instance for method chaining. + */ public addOrderBy(orderBy: OrderBy | OrderBy[]): Union { if (Array.isArray(orderBy)) { - this.orderBys.push(...orderBy) + this.orderBys.push(...orderBy); } else { this.orderBys.push(orderBy); } @@ -345,13 +356,13 @@ export default class Union extends DmlQueryDefinition { } /** - * Sets the GROUP BY clauses for the union query, replacing any existing clauses. - * @param field A single field name or an array of field names to group by. - * @returns The current Union instance for method chaining. - */ + * Sets the GROUP BY clauses for the union query, replacing any existing clauses. + * @param field A single field name or an array of field names to group by. + * @returns The current Union instance for method chaining. + */ public groupBy(field: string | string[]): Union { if (Array.isArray(field)) { - this.groupBys = field + this.groupBys = field; } else { this.groupBys = [field]; } @@ -359,13 +370,13 @@ export default class Union extends DmlQueryDefinition { } /** - * Adds GROUP BY clauses to the union query without replacing existing clauses. - * @param field A single field name or an array of field names to add to the GROUP BY clause. - * @returns The current Union instance for method chaining. - */ + * Adds GROUP BY clauses to the union query without replacing existing clauses. + * @param field A single field name or an array of field names to add to the GROUP BY clause. + * @returns The current Union instance for method chaining. + */ public addGroupBy(field: string | string[]): Union { if (Array.isArray(field)) { - this.groupBys.push(...field) + this.groupBys.push(...field); } else { this.groupBys.push(field); } @@ -373,26 +384,28 @@ export default class Union extends DmlQueryDefinition { } /** - * Adds a HAVING clause to the union query. - * @param statement The HAVING clause as a Statement instance or a raw SQL string. - * @param values Optional parameter values if a raw SQL string is provided. - * @returns The current Union instance for method chaining. - */ + * Adds a HAVING clause to the union query. + * @param statement The HAVING clause as a Statement instance or a raw SQL string. + * @param values Optional parameter values if a raw SQL string is provided. + * @returns The current Union instance for method chaining. + */ public having(statement: Statement | string, ...values: any[]): Union { - if (typeof statement === 'string') { - statement = new Statement().raw('', statement, ...values); + if (typeof statement === "string") { + statement = new Statement().raw("", statement, ...values); } this.havingStatement = statement; return this; } /** - * Adds a HAVING clause to the union query using a callback function. - * The callback function receives a Statement instance to build the HAVING clause. - * @param statement A callback function that takes a Statement instance and returns a Statement or void. - * @returns The current Union instance for method chaining. - */ - public useHavingStatement(statement: (stmt: Statement) => Statement | void): Union { + * Adds a HAVING clause to the union query using a callback function. + * The callback function receives a Statement instance to build the HAVING clause. + * @param statement A callback function that takes a Statement instance and returns a Statement or void. + * @returns The current Union instance for method chaining. + */ + public useHavingStatement( + statement: (stmt: Statement) => Statement | undefined, + ): Union { const stmt = new Statement(); const newStatement = statement(stmt) || stmt; @@ -401,36 +414,36 @@ export default class Union extends DmlQueryDefinition { } /** - * Builds the final SQL query for the union operation. - * It combines all added SELECT queries with their respective union types, - * applies any WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, and OFFSET clauses, - * and returns the complete SQL text along with the parameter values. - * @param deepAnalysis If true, performs a deep analysis to re-index parameters. Defaults to false. - * @returns An object containing the final SQL text and an array of parameter values. - * @throws Error if no SELECT queries have been added to the union. - */ + * Builds the final SQL query for the union operation. + * It combines all added SELECT queries with their respective union types, + * applies any WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, and OFFSET clauses, + * and returns the complete SQL text along with the parameter values. + * @param deepAnalysis If true, performs a deep analysis to re-index parameters. Defaults to false. + * @returns An object containing the final SQL text and an array of parameter values. + * @throws Error if no SELECT queries have been added to the union. + */ public build(deepAnalysis: boolean = false): { text: string; values: any[] } { if (this.selectQueries.length === 0) { - throw new Error('No SELECT queries added to the UNION.'); + throw new Error("No SELECT queries added to the UNION."); } const differingIndex = this.allSelectsHaveSameNumberOfFields(); if (differingIndex !== null) { - console.log('This is erroring out') + console.log("This is erroring out"); throw new Error( `All SELECT queries must have the same number of fields. Query at index ${differingIndex} differs.`, - { cause: { selectQuery: this.selectQueries[differingIndex]?.query } } + { cause: { selectQuery: this.selectQueries[differingIndex]?.query } }, ); } - let unionItself: string = ''; + let unionItself: string = ""; const values: any[] = []; - let selectClause = ''; + let selectClause = ""; if (this.selectFields.length > 0) { - selectClause = this.selectFields.join(',\n '); + selectClause = this.selectFields.join(",\n "); } else { - selectClause = '*'; + selectClause = "*"; } // Add offset on each select query to ensure correct parameter indexing @@ -438,15 +451,15 @@ export default class Union extends DmlQueryDefinition { for (const { query, type: unionType } of this.selectQueries) { (query as any).disabledAnalysis = true; query.resetWhereOffset(); - let builtQuery = query.addWhereOffset(paramOffset - 1).build(); + const builtQuery = query.addWhereOffset(paramOffset - 1).build(); builtQuery.text = this.spaceLines(`(${builtQuery.text})`, 1); const type = this.spaceLines(unionType, 1); - unionItself += (unionItself ? `\n\n${type}\n\n` : '') + `${builtQuery.text}`; + unionItself += `${unionItself ? `\n\n${type}\n\n` : ""}${builtQuery.text}`; paramOffset += builtQuery.values.length; values.push(...builtQuery.values); } - let whereClause = ''; + let whereClause = ""; let whereValues: any[] = []; if (this.whereStatement) { const builtWhere = this.whereStatement @@ -457,60 +470,67 @@ export default class Union extends DmlQueryDefinition { whereValues = builtWhere.values; } - let groupByClause = ''; + let groupByClause = ""; if (this.groupBys.length > 0) { - groupByClause = 'GROUP BY ' + this.groupBys.map(gb => `"${gb}"`).join(', '); + groupByClause = `GROUP BY ${this.groupBys.map((gb) => `"${gb}"`).join(", ")}`; } - let havingClause = ''; + let havingClause = ""; let havingValues: any[] = []; if (this.havingStatement) { const builtHaving = this.havingStatement .disableWhere() .setOffset(paramOffset + whereValues.length) .build(); - havingClause = 'HAVING ' + builtHaving.statement; + havingClause = `HAVING ${builtHaving.statement}`; havingValues = builtHaving.values; } - let orderByClause = ''; + let orderByClause = ""; if (this.orderBys.length > 0) { - orderByClause = 'ORDER BY ' + this.orderBys.map(ob => { - const direction = ob.direction ? ` ${ob.direction.toUpperCase()}` : ''; - return `"${(ob as any).field || (ob as any).column}"${direction}`; - }).join(', '); + orderByClause = + "ORDER BY " + + this.orderBys + .map((ob) => { + const direction = ob.direction + ? ` ${ob.direction.toUpperCase()}` + : ""; + return `"${(ob as any).field || (ob as any).column}"${direction}`; + }) + .join(", "); } - let limitClause = ''; + let limitClause = ""; if (this.limitCount !== null) { limitClause = `LIMIT ${this.limitCount}`; } - let offsetClause = ''; + let offsetClause = ""; if (this.offsetCount !== null) { offsetClause = `OFFSET ${this.offsetCount}`; } - const firstLine = - `SELECT${selectClause.length > 1 ? '\n' : ''} ${selectClause}${selectClause.length > 1 ? '\n' : ''} FROM (`; + const firstLine = `SELECT${selectClause.length > 1 ? "\n" : ""} ${selectClause}${selectClause.length > 1 ? "\n" : ""} FROM (`; const union = [ firstLine, - `${unionItself}\n) AS ${this.unionAlias || 'union_subquery'}`, + `${unionItself}\n) AS ${this.unionAlias || "union_subquery"}`, whereClause, groupByClause, havingClause, orderByClause, limitClause, - offsetClause - ].filter(part => part.trim() !== '').join('\n'); + offsetClause, + ] + .filter((part) => part.trim() !== "") + .join("\n"); - const finalValues = [...values, ...whereValues, ...havingValues]; + const finalValues = [...values, ...whereValues, ...havingValues]; const analyzed = this.reAnalyzeParsedQueryForDuplicateParams( union, finalValues, - deepAnalysis + deepAnalysis, ); this.builtQuery = analyzed.text; @@ -518,41 +538,40 @@ export default class Union extends DmlQueryDefinition { return { text: this.builtQuery, - values: this.builtParams + values: this.builtParams, }; } /** - * Generates the SQL string for the union query. - * If the query has not been built yet, it will build it first. - * @returns The SQL string of the union query. - * @throws Error if the query fails to build. - */ + * Generates the SQL string for the union query. + * If the query has not been built yet, it will build it first. + * @returns The SQL string of the union query. + * @throws Error if the query fails to build. + */ public toSQL(): string { - if(!this.builtQuery) this.build(); - if(!this.builtQuery) throw new Error("Failed to build the query."); + if (!this.builtQuery) this.build(); + if (!this.builtQuery) throw new Error("Failed to build the query."); return this.builtQuery; - } /** - * Retrieves the parameter values for the union query. - * If the query has not been built yet, it will build it first. - * @returns An array of parameter values for the union query. - * @throws Error if the query fails to build. - */ + * Retrieves the parameter values for the union query. + * If the query has not been built yet, it will build it first. + * @returns An array of parameter values for the union query. + * @throws Error if the query fails to build. + */ public getParams(): any[] { - if(!this.builtParams) this.build(); - if(!this.builtParams) throw new Error("Failed to build the query."); + if (!this.builtParams) this.build(); + if (!this.builtParams) throw new Error("Failed to build the query."); return this.builtParams; } /** - * Creates a deep clone of the current Union instance. - * This includes cloning all properties and nested objects to ensure - * that modifications to the clone do not affect the original instance. - * @returns A new Union instance that is a deep clone of the current instance. - */ + * Creates a deep clone of the current Union instance. + * This includes cloning all properties and nested objects to ensure + * that modifications to the clone do not affect the original instance. + * @returns A new Union instance that is a deep clone of the current instance. + */ public clone(): Union { const newUnion = new Union(); newUnion.selectFields = [...this.selectFields]; @@ -561,24 +580,28 @@ export default class Union extends DmlQueryDefinition { newUnion.limitCount = this.limitCount; newUnion.offsetCount = this.offsetCount; newUnion.schemas = [...this.schemas]; - newUnion.selectQueries = this.selectQueries.map(sq => ({ + newUnion.selectQueries = this.selectQueries.map((sq) => ({ query: sq.query.clone() as SelectQuery, - type: sq.type + type: sq.type, })); newUnion.orderBys = [...this.orderBys]; newUnion.groupBys = [...this.groupBys]; - newUnion.havingStatement = this.havingStatement ? this.havingStatement.clone() : null; - newUnion.whereStatement = this.whereStatement ? this.whereStatement.clone() : null; + newUnion.havingStatement = this.havingStatement + ? this.havingStatement.clone() + : null; + newUnion.whereStatement = this.whereStatement + ? this.whereStatement.clone() + : null; return newUnion; } /** - * Resets the internal state of the Union instance. - * This clears all properties, including the union alias, limit, offset, - * select queries, order by clauses, group by clauses, having statement, - * where statement, built query, built parameters, and schemas. - * After calling this method, the Union instance will be in its initial state. - */ + * Resets the internal state of the Union instance. + * This clears all properties, including the union alias, limit, offset, + * select queries, order by clauses, group by clauses, having statement, + * where statement, built query, built parameters, and schemas. + * After calling this method, the Union instance will be in its initial state. + */ public reset(): void { this.selectFields = []; this.unionAlias = null; @@ -595,12 +618,10 @@ export default class Union extends DmlQueryDefinition { } /** - * This is a UNION query. - * @returns The kind of SQL operation, which is 'UNION' for this class. - */ - public get kind() { + * This is a UNION query. + * @returns The kind of SQL operation, which is 'UNION' for this class. + */ + public get kind(): QueryKind { return QueryKind.UNION; } - } - diff --git a/src/queryKinds/dml/update.ts b/src/queryKinds/dml/update.ts index b140fc3..5e598a6 100644 --- a/src/queryKinds/dml/update.ts +++ b/src/queryKinds/dml/update.ts @@ -1,17 +1,18 @@ -import CteMaker, { Cte } from "../../cteMaker.js"; +import CteMaker, { type Cte } from "../../cteMaker.js"; import SqlEscaper from "../../sqlEscaper.js"; import Statement from "../../statementMaker.js"; -import Join, { isJoinTable } from "../../types/Join.js"; +import type Join from "../../types/Join.js"; +import { isJoinTable } from "../../types/Join.js"; import QueryKind from "../../types/QueryKind.js"; -import SetValue from "../../types/SetValue.js"; +import type SetValue from "../../types/SetValue.js"; import DmlQueryDefinition from "./dmlQueryDefinition.js"; /** - * UpdateQuery class is used to build SQL UPDATE queries. - * It provides methods to specify the table to update, set values, add joins, and define conditions. - * The class supports Common Table Expressions (CTEs) and returning clauses. - * It extends the DmlQueryDefinition class to inherit common query functionalities. - */ + * UpdateQuery class is used to build SQL UPDATE queries. + * It provides methods to specify the table to update, set values, add joins, and define conditions. + * The class supports Common Table Expressions (CTEs) and returning clauses. + * It extends the DmlQueryDefinition class to inherit common query functionalities. + */ export default class UpdateQuery extends DmlQueryDefinition { /** The table to update. */ private table: string; @@ -30,22 +31,22 @@ export default class UpdateQuery extends DmlQueryDefinition { private returningFields: string[] = []; /** - * Creates an instance of UpdateQuery. - * @param table - The name of the table to update. - * @param alias - An optional alias for the table. - */ + * Creates an instance of UpdateQuery. + * @param table - The name of the table to update. + * @param alias - An optional alias for the table. + */ constructor(table?: string, alias?: string) { super(); - this.table = table ? SqlEscaper.escapeTableName(table, this.flavor) : ''; + this.table = table ? SqlEscaper.escapeTableName(table, this.flavor) : ""; this.tableAlias = alias || null; } /** - * Adds Common Table Expressions (CTEs) to the query. - * Accepts a CteMaker instance, a single Cte, or an array of Ctes. - * @param ctes - The CTEs to be added to the query. - * @returns The current UpdateQuery instance for method chaining. - */ + * Adds Common Table Expressions (CTEs) to the query. + * Accepts a CteMaker instance, a single Cte, or an array of Ctes. + * @param ctes - The CTEs to be added to the query. + * @returns The current UpdateQuery instance for method chaining. + */ public with(ctes: CteMaker | Cte | Cte[]): this { if (ctes instanceof CteMaker) { this.ctes = ctes; @@ -58,11 +59,11 @@ export default class UpdateQuery extends DmlQueryDefinition { } /** - * Specifies the table to update and an optional alias. - * @param table - The name of the table. - * @param alias - An optional alias for the table. - * @returns The current UpdateQuery instance for method chaining. - */ + * Specifies the table to update and an optional alias. + * @param table - The name of the table. + * @param alias - An optional alias for the table. + * @returns The current UpdateQuery instance for method chaining. + */ public from(table: string, alias: string | null = null): this { this.table = SqlEscaper.escapeTableName(table, this.flavor); this.tableAlias = alias; @@ -70,11 +71,11 @@ export default class UpdateQuery extends DmlQueryDefinition { } /** - * Specifies the USING clause table and an optional alias. - * @param table - The name of the table for the USING clause. - * @param alias - An optional alias for the USING table. - * @returns The current UpdateQuery instance for method chaining. - */ + * Specifies the USING clause table and an optional alias. + * @param table - The name of the table for the USING clause. + * @param alias - An optional alias for the USING table. + * @returns The current UpdateQuery instance for method chaining. + */ public using(table: string, alias: string | null = null): this { this.usingTable = SqlEscaper.escapeTableName(table, this.flavor); this.usingAlias = alias; @@ -82,16 +83,14 @@ export default class UpdateQuery extends DmlQueryDefinition { } /** - * Adds JOIN clauses to the update query. - * Accepts either a single Join object or an array of Join objects. - * @param join - The JOIN clause(s) to be added. - * @returns The current UpdateQuery instance for method chaining. - */ - public join( - join: Join | Join[] - ): this { + * Adds JOIN clauses to the update query. + * Accepts either a single Join object or an array of Join objects. + * @param join - The JOIN clause(s) to be added. + * @returns The current UpdateQuery instance for method chaining. + */ + public join(join: Join | Join[]): this { if (Array.isArray(join)) { - this.joins.push(...join.map(j => this.parseJoinObject(j))); + this.joins.push(...join.map((j) => this.parseJoinObject(j))); } else { this.joins.push(this.parseJoinObject(join)); } @@ -99,146 +98,173 @@ export default class UpdateQuery extends DmlQueryDefinition { } /** - * Specifies the SET values for the update. - * Accepts either a single SetValue object or an array of SetValue objects. - * @param values - The SET value(s) to be added. - * @returns The current UpdateQuery instance for method chaining. - */ + * Specifies the SET values for the update. + * Accepts either a single SetValue object or an array of SetValue objects. + * @param values - The SET value(s) to be added. + * @returns The current UpdateQuery instance for method chaining. + */ public set(values: SetValue | SetValue[] | { [key: string]: any }): this { if (Array.isArray(values)) { this.setValues = values - .filter(v => v.value !== undefined) - .map(v => ({ - setColumn: v.setColumn ? SqlEscaper.escapeTableName(v.setColumn, this.flavor) : '', - from: v.from ? SqlEscaper.escapeTableName(v.from, this.flavor) : undefined as any, - value: v.value ?? null + .filter((v) => v.value !== undefined) + .map((v) => ({ + setColumn: v.setColumn + ? SqlEscaper.escapeTableName(v.setColumn, this.flavor) + : "", + from: v.from + ? SqlEscaper.escapeTableName(v.from, this.flavor) + : (undefined as any), + value: v.value ?? null, + })); + } else if ( + values?.setColumn && + (values?.from || values?.value !== undefined) + ) { + this.setValues = [ + { + setColumn: values.setColumn + ? SqlEscaper.escapeTableName(values.setColumn, this.flavor) + : "", + from: values.from + ? SqlEscaper.escapeTableName(values.from, this.flavor) + : (undefined as any), + value: values.value ?? null, + }, + ]; + } else if (typeof values === "object" && !Array.isArray(values)) { + this.setValues = Object.entries(values) + .filter(([, val]) => { + return val !== undefined; + }) + .map(([key, val]) => ({ + setColumn: SqlEscaper.escapeTableName(key, this.flavor), + value: val, })); - } else if (values?.setColumn && (values?.from || values?.value !== undefined)) { - this.setValues = [{ - setColumn: values.setColumn ? SqlEscaper.escapeTableName(values.setColumn, this.flavor) : '', - from: values.from ? SqlEscaper.escapeTableName(values.from, this.flavor) : undefined as any, - value: values.value ?? null - }]; - } else if (typeof values === 'object' && !Array.isArray(values)) { - this.setValues = Object.entries(values).filter(([, val]) => { - return val !== undefined; - }).map(([key, val]) => ({ - setColumn: SqlEscaper.escapeTableName(key, this.flavor), - value: val - })); } return this; } /** - * Adds a SET clause with a value from another column or expression. - * Example: addSet('column1', 'column2 + 1') results in "SET column1 = column2 + 1". - * @param column - The column to be set. - * @param from - The column or expression to set the value from. - * @returns The current UpdateQuery instance for method chaining. - */ + * Adds a SET clause with a value from another column or expression. + * Example: addSet('column1', 'column2 + 1') results in "SET column1 = column2 + 1". + * @param column - The column to be set. + * @param from - The column or expression to set the value from. + * @returns The current UpdateQuery instance for method chaining. + */ public addSet(column: string, from: string): this { - this.setValues.push({ + this.setValues.push({ setColumn: SqlEscaper.escapeTableName(column, this.flavor), - from: SqlEscaper.escapeTableName(from, this.flavor) + from: SqlEscaper.escapeTableName(from, this.flavor), }); return this; } /** - * Adds a SET clause with a direct value. - * Example: addSetValue('column1', 42) results in "SET column1 = $1" with 42 as a parameter. - * @param column - The column to be set. - * @param value - The value to set the column to. - * @returns The current UpdateQuery instance for method chaining. - */ + * Adds a SET clause with a direct value. + * Example: addSetValue('column1', 42) results in "SET column1 = $1" with 42 as a parameter. + * @param column - The column to be set. + * @param value - The value to set the column to. + * @returns The current UpdateQuery instance for method chaining. + */ public addSetValue(column: string, value: any): this { - this.setValues.push({ + this.setValues.push({ setColumn: SqlEscaper.escapeTableName(column, this.flavor), - value + value, }); return this; } /** - * Specifies the WHERE clause for the update. - * Accepts either a Statement object or a raw SQL string with optional parameters. - * @param statement - The WHERE clause as a Statement or raw SQL string. - * @param values - Optional parameters for the raw SQL string. - * @returns The current UpdateQuery instance for method chaining. - */ + * Specifies the WHERE clause for the update. + * Accepts either a Statement object or a raw SQL string with optional parameters. + * @param statement - The WHERE clause as a Statement or raw SQL string. + * @param values - Optional parameters for the raw SQL string. + * @returns The current UpdateQuery instance for method chaining. + */ public where(statement: Statement | string, ...values: any[]): this { - if (typeof statement === 'string') { - statement = new Statement().raw('', statement, ...values); + if (typeof statement === "string") { + statement = new Statement().raw("", statement, ...values); } - + this.whereStatement = statement; return this; } /** - * Allows using a callback to build the WHERE clause with a Statement object. - * Example: useStatement(stmt => stmt.raw('id = $1', 42)) results in "WHERE id = $1" with 42 as a parameter. - * @param statement - A callback function that receives a Statement object. - * @returns The current UpdateQuery instance for method chaining. - */ - public useStatement(statement: (stmt: Statement) => Statement | void): this { + * Allows using a callback to build the WHERE clause with a Statement object. + * Example: useStatement(stmt => stmt.raw('id = $1', 42)) results in "WHERE id = $1" with 42 as a parameter. + * @param statement - A callback function that receives a Statement object. + * @returns The current UpdateQuery instance for method chaining. + */ + public useStatement( + statement: (stmt: Statement) => Statement | undefined, + ): this { const stmt = new Statement(); const newStmt = statement(stmt) || stmt; return this.where(newStmt); } /** - * Specifies the RETURNING fields for the update. - * Accepts either a single field name or an array of field names. - * @param fields - The field(s) to be returned after the update. - * @returns The current UpdateQuery instance for method chaining. - */ + * Specifies the RETURNING fields for the update. + * Accepts either a single field name or an array of field names. + * @param fields - The field(s) to be returned after the update. + * @returns The current UpdateQuery instance for method chaining. + */ public returning(fields: string | string[]): this { if (Array.isArray(fields)) { - this.returningFields = SqlEscaper.escapeSelectIdentifiers(fields, this.flavor); + this.returningFields = SqlEscaper.escapeSelectIdentifiers( + fields, + this.flavor, + ); } else { - this.returningFields = SqlEscaper.escapeSelectIdentifiers([fields], this.flavor); + this.returningFields = SqlEscaper.escapeSelectIdentifiers( + [fields], + this.flavor, + ); } return this; } /** - * Adds additional RETURNING fields to the update. - * Accepts either a single field name or an array of field names. - * @param field - The field(s) to be added to the RETURNING clause. - * @returns The current UpdateQuery instance for method chaining. - */ + * Adds additional RETURNING fields to the update. + * Accepts either a single field name or an array of field names. + * @param field - The field(s) to be added to the RETURNING clause. + * @returns The current UpdateQuery instance for method chaining. + */ public addReturning(field: string | string[]): this { if (Array.isArray(field)) { - this.returningFields.push(...SqlEscaper.escapeSelectIdentifiers(field, this.flavor)); + this.returningFields.push( + ...SqlEscaper.escapeSelectIdentifiers(field, this.flavor), + ); } else { - this.returningFields.push(...SqlEscaper.escapeSelectIdentifiers([field], this.flavor)); + this.returningFields.push( + ...SqlEscaper.escapeSelectIdentifiers([field], this.flavor), + ); } return this; } /** - * Builds the final SQL UPDATE query string and collects the parameters. - * It handles CTEs, SET clauses, JOINs, WHERE conditions, and RETURNING fields. - * The method ensures proper parameter indexing and returns the query text and values. - * @param deepAnalysis - If true, performs a deeper analysis for duplicate parameters. - * @returns An object containing the built query text and its parameters. - * @throws Error if no table or SET values are specified. - */ + * Builds the final SQL UPDATE query string and collects the parameters. + * It handles CTEs, SET clauses, JOINs, WHERE conditions, and RETURNING fields. + * The method ensures proper parameter indexing and returns the query text and values. + * @param deepAnalysis - If true, performs a deeper analysis for duplicate parameters. + * @returns An object containing the built query text and its parameters. + * @throws Error if no table or SET values are specified. + */ public build(deepAnalysis: boolean = false): { text: string; values: any[] } { - if (this.table.trim() === '') { - throw new Error('No table specified for UPDATE query.'); + if (this.table.trim() === "") { + throw new Error("No table specified for UPDATE query."); } if (this.setValues.length === 0) { - throw new Error('No SET values specified for UPDATE query.'); + throw new Error("No SET values specified for UPDATE query."); } this.whereStatement = this.whereStatement || new Statement(); this.whereStatement?.setOffset(1); - let ctesClause = ''; + let ctesClause = ""; let cteValues: any[] = []; let offset = 0; if (this.ctes) { @@ -254,7 +280,7 @@ export default class UpdateQuery extends DmlQueryDefinition { updateClause += ` ${this.tableAlias}`; } - let setClause = 'SET '; + let setClause = "SET "; const setParts: string[] = []; const setValues: any[] = []; this.setValues.forEach((sv) => { @@ -266,12 +292,14 @@ export default class UpdateQuery extends DmlQueryDefinition { } else if (sv.from !== undefined) { setParts.push(`${sv.setColumn} = ${sv.from}`); } else { - throw new Error(`SET value for column ${sv.setColumn} must have either 'value' or 'from' defined.`); + throw new Error( + `SET value for column ${sv.setColumn} must have either 'value' or 'from' defined.`, + ); } }); - setClause += setParts.join(', '); + setClause += setParts.join(", "); - let usingClause = ''; + let usingClause = ""; if (this.usingTable) { usingClause = `FROM ${this.usingTable}`; if (this.usingAlias) { @@ -279,27 +307,27 @@ export default class UpdateQuery extends DmlQueryDefinition { } } - if(usingClause === '' && this.joins.length > 0) { - throw new Error('JOINs require a USING clause in UPDATE queries.'); + if (usingClause === "" && this.joins.length > 0) { + throw new Error("JOINs require a USING clause in UPDATE queries."); } - let joinClauses = ''; + let joinClauses = ""; let currentOffset = setValues.length + cteValues.length; - let parametersToAdd: any = []; + const parametersToAdd: any = []; for (const join of this.joins) { - if(isJoinTable(join)) { - const onClause = - typeof join.on === 'string' ? join.on - : (() => { - join.on.disableWhere(); - join.on.addOffset(currentOffset); - const stmt = join.on.build(false); - currentOffset += stmt.values.length; - parametersToAdd.push(...stmt.values); - return stmt.statement; - })(); - joinClauses += - `${joinClauses ? '\n' : ''}${join.type.toUpperCase()} JOIN ${join.table} ${join.alias}\n ON ${onClause}`; + if (isJoinTable(join)) { + const onClause = + typeof join.on === "string" + ? join.on + : (() => { + join.on.disableWhere(); + join.on.addOffset(currentOffset); + const stmt = join.on.build(false); + currentOffset += stmt.values.length; + parametersToAdd.push(...stmt.values); + return stmt.statement; + })(); + joinClauses += `${joinClauses ? "\n" : ""}${join.type.toUpperCase()} JOIN ${join.table} ${join.alias}\n ON ${onClause}`; } else { join.subQuery.resetWhereOffset(); join.subQuery.addWhereOffset(currentOffset); @@ -308,19 +336,19 @@ export default class UpdateQuery extends DmlQueryDefinition { (join.subQuery as any).disabledAnalysis = false; currentOffset += subQueryBuilt.values.length; parametersToAdd.push(...subQueryBuilt.values); - const onClause = - typeof join.on === 'string' ? join.on - : (() => { - join.on.disableWhere(); - join.on.addOffset(currentOffset); - const stmt = join.on.build(false); - currentOffset += stmt.values.length; - parametersToAdd.push(...stmt.values); - return stmt.statement; - })(); + const onClause = + typeof join.on === "string" + ? join.on + : (() => { + join.on.disableWhere(); + join.on.addOffset(currentOffset); + const stmt = join.on.build(false); + currentOffset += stmt.values.length; + parametersToAdd.push(...stmt.values); + return stmt.statement; + })(); subQueryBuilt.text = this.spaceLines(subQueryBuilt.text, 1); - joinClauses += - `${joinClauses ? '\n' : ''}${join.type.toUpperCase()} JOIN (\n${subQueryBuilt.text}\n) ${join.alias}\n ON ${onClause}`; + joinClauses += `${joinClauses ? "\n" : ""}${join.type.toUpperCase()} JOIN (\n${subQueryBuilt.text}\n) ${join.alias}\n ON ${onClause}`; } } @@ -331,9 +359,9 @@ export default class UpdateQuery extends DmlQueryDefinition { const whereClause = stmt.statement; const values = stmt.values; - let returningClause = ''; + let returningClause = ""; if (this.returningFields.length > 0) { - returningClause = `RETURNING ${this.returningFields.join(', ')}`; + returningClause = `RETURNING ${this.returningFields.join(", ")}`; } this.builtQuery = [ @@ -343,48 +371,51 @@ export default class UpdateQuery extends DmlQueryDefinition { usingClause, joinClauses, whereClause, - returningClause - ].filter(part => part !== '') - .join('\n'); + returningClause, + ] + .filter((part) => part !== "") + .join("\n"); - this.builtQuery = SqlEscaper.appendSchemas( - this.builtQuery, this.schemas - ); + this.builtQuery = SqlEscaper.appendSchemas(this.builtQuery, this.schemas); const allValues = [...cteValues, ...setValues, ...values]; - const analyzed = this.reAnalyzeParsedQueryForDuplicateParams(this.builtQuery, allValues, deepAnalysis); + const analyzed = this.reAnalyzeParsedQueryForDuplicateParams( + this.builtQuery, + allValues, + deepAnalysis, + ); this.builtQuery = analyzed.text; this.builtParams = analyzed.values; return { text: this.builtQuery, values: this.builtParams }; } /** - * Returns the built SQL query string. - * If the query is not yet built, it triggers the build process. - * @returns The SQL query string. - */ + * Returns the built SQL query string. + * If the query is not yet built, it triggers the build process. + * @returns The SQL query string. + */ public toSQL(): string { if (!this.builtQuery) this.build(); - if (!this.builtQuery) throw new Error('Failed to build the SQL query.'); + if (!this.builtQuery) throw new Error("Failed to build the SQL query."); return this.builtQuery; } /** - * This an UPDATE query. - * @returns The string 'UPDATE'. - */ + * This an UPDATE query. + * @returns The string 'UPDATE'. + */ public get kind() { return QueryKind.UPDATE; } /** - * Resets the query definition to its initial state. - * Clears all properties related to the query configuration. - * @returns void - */ + * Resets the query definition to its initial state. + * Clears all properties related to the query configuration. + * @returns void + */ public reset(): void { - this.table = ''; + this.table = ""; this.tableAlias = null; this.usingTable = null; this.usingAlias = null; @@ -398,21 +429,21 @@ export default class UpdateQuery extends DmlQueryDefinition { } /** - * Retrieves the parameters associated with the query. - * If the query is not yet built, it triggers the build process. - * @returns An array of parameters for the query. - */ + * Retrieves the parameters associated with the query. + * If the query is not yet built, it triggers the build process. + * @returns An array of parameters for the query. + */ public getParams(): any[] { if (!this.builtParams) this.build(); - if (!this.builtParams) throw new Error('Failed to build the SQL query.'); + if (!this.builtParams) throw new Error("Failed to build the SQL query."); return this.builtParams; } /** - * Creates a deep copy of the current UpdateQuery instance. - * This is useful for preserving the current state of the query while making modifications to a clone. - * @returns A new UpdateQuery instance that is a clone of the current instance. - */ + * Creates a deep copy of the current UpdateQuery instance. + * This is useful for preserving the current state of the query while making modifications to a clone. + * @returns A new UpdateQuery instance that is a clone of the current instance. + */ public clone(): UpdateQuery { const cloned = new UpdateQuery(); cloned.table = this.table; @@ -423,10 +454,11 @@ export default class UpdateQuery extends DmlQueryDefinition { cloned.usingAlias = this.usingAlias; cloned.joins = JSON.parse(JSON.stringify(this.joins)); cloned.setValues = JSON.parse(JSON.stringify(this.setValues)); - cloned.whereStatement = this.whereStatement ? this.whereStatement.clone() : null; + cloned.whereStatement = this.whereStatement + ? this.whereStatement.clone() + : null; cloned.returningFields = [...this.returningFields]; - cloned.ctes = this.ctes ? new CteMaker(...this.ctes['ctes']) : null; + cloned.ctes = this.ctes ? new CteMaker(...this.ctes["ctes"]) : null; return cloned; } - } diff --git a/src/queryMaker.ts b/src/queryMaker.ts index 49f120e..8d3f6b7 100644 --- a/src/queryMaker.ts +++ b/src/queryMaker.ts @@ -9,174 +9,172 @@ import Statement from "./statementMaker.js"; import sqlFlavor from "./types/sqlFlavor.js"; /** - * QueryMaker is a factory class that provides static methods to create instances of different query types. - * It includes methods for creating SELECT, CREATE, DELETE, and UPDATE queries. - * It also provides a method to create a Statement instance for building complex SQL statements. - * Each method returns a new instance of the respective query class. - */ + * QueryMaker is a factory class that provides static methods to create instances of different query types. + * It includes methods for creating SELECT, CREATE, DELETE, and UPDATE queries. + * It also provides a method to create a Statement instance for building complex SQL statements. + * Each method returns a new instance of the respective query class. + */ class Query { - /** - * Creates an instance of QueryMaker. - * @param deepAnalysisDefault - Optional boolean to set the default deep analysis behavior for query building. - * @param flavor - Optional SQL flavor to tailor the query syntax (default is 'postgres'). - */ + * Creates an instance of QueryMaker. + * @param deepAnalysisDefault - Optional boolean to set the default deep analysis behavior for query building. + * @param flavor - Optional SQL flavor to tailor the query syntax (default is 'postgres'). + */ constructor( private readonly deepAnalysisDefault: boolean = false, - private readonly flavor = sqlFlavor.postgres + private readonly flavor = sqlFlavor.postgres, ) {} /** - * Initiates a new SELECT query. - * @returns A new SelectQuery instance with a build method that respects the deepAnalysisDefault setting. - */ + * Initiates a new SELECT query. + * @returns A new SelectQuery instance with a build method that respects the deepAnalysisDefault setting. + */ public get select(): SelectQuery { const selectQuery = new SelectQuery(); (selectQuery as any).flavor = this.flavor; selectQuery.build = (deepAnalysis: boolean = this.deepAnalysisDefault) => { return SelectQuery.prototype.build.call(selectQuery, deepAnalysis); - } + }; return selectQuery; } /** - * Initiates a new DELETE query. - * @returns A new DeleteQuery instance with a build method that respects the deepAnalysisDefault setting. - */ + * Initiates a new DELETE query. + * @returns A new DeleteQuery instance with a build method that respects the deepAnalysisDefault setting. + */ public get delete(): DeleteQuery { const deleteQuery = new DeleteQuery(); (deleteQuery as any).flavor = this.flavor; deleteQuery.build = (deepAnalysis: boolean = this.deepAnalysisDefault) => { return DeleteQuery.prototype.build.call(deleteQuery, deepAnalysis); - } + }; return deleteQuery; } /** - * Initiates a new UPDATE query. - * @returns A new UpdateQuery instance with a build method that respects the deepAnalysisDefault setting. - */ + * Initiates a new UPDATE query. + * @returns A new UpdateQuery instance with a build method that respects the deepAnalysisDefault setting. + */ public get update(): UpdateQuery { const updateQuery = new UpdateQuery(); (updateQuery as any).flavor = this.flavor; updateQuery.build = (deepAnalysis: boolean = this.deepAnalysisDefault) => { return UpdateQuery.prototype.build.call(updateQuery, deepAnalysis); - } + }; return updateQuery; } /** - * Initiates a new INSERT query. - * @returns A new InsertQuery instance with a build method that respects the deepAnalysisDefault setting. - */ + * Initiates a new INSERT query. + * @returns A new InsertQuery instance with a build method that respects the deepAnalysisDefault setting. + */ public get create(): InsertQuery { const insertQuery = new InsertQuery(); (insertQuery as any).flavor = this.flavor; insertQuery.build = (deepAnalysis: boolean = this.deepAnalysisDefault) => { return InsertQuery.prototype.build.call(insertQuery, deepAnalysis); - } + }; return insertQuery; } /** - * Initiates a new CTE (Common Table Expression) instance. - * This can be used to define CTEs for use in queries. - * @returns A new Cte instance. - */ + * Initiates a new CTE (Common Table Expression) instance. + * This can be used to define CTEs for use in queries. + * @returns A new Cte instance. + */ public get cte(): Cte { return new Cte(); } /** - * Initiates a new UNION query. - * @returns A new Union instance with a build method that respects the deepAnalysisDefault setting. - */ + * Initiates a new UNION query. + * @returns A new Union instance with a build method that respects the deepAnalysisDefault setting. + */ public get union(): Union { const unionQuery = new Union(); (unionQuery as any).flavor = this.flavor; unionQuery.build = (deepAnalysis: boolean = this.deepAnalysisDefault) => { return Union.prototype.build.call(unionQuery, deepAnalysis); - } + }; return unionQuery; } /** - * Initiates a new Statement instance for building complex SQL statements. - * This can be used to create WHERE clauses, JOIN conditions, etc. - * @returns A new Statement instance. - */ - public get table() { + * Initiates a new Statement instance for building complex SQL statements. + * This can be used to create WHERE clauses, JOIN conditions, etc. + * @returns A new Statement instance. + */ + public get table(): Table { const table = new Table(this.deepAnalysisDefault, this.flavor); return table; } /** - * Initiates a new SELECT query. - * @returns A new SelectQuery instance. - */ + * Initiates a new SELECT query. + * @returns A new SelectQuery instance. + */ public static get select(): SelectQuery { return new SelectQuery(); } /** - * Initiates a new DELETE query. - * @returns A new DeleteQuery instance. - */ + * Initiates a new DELETE query. + * @returns A new DeleteQuery instance. + */ public static get delete(): DeleteQuery { return new DeleteQuery(); } /** - * Initiates a new UPDATE query. - * @returns A new UpdateQuery instance. - */ + * Initiates a new UPDATE query. + * @returns A new UpdateQuery instance. + */ public static get update(): UpdateQuery { return new UpdateQuery(); } /** - * Initiates a new INSERT query. - * @returns A new InsertQuery instance. - */ + * Initiates a new INSERT query. + * @returns A new InsertQuery instance. + */ public static get create(): InsertQuery { return new InsertQuery(); } /** - * Initiates a new Statement instance for building complex SQL statements. - * This can be used to create WHERE clauses, JOIN conditions, etc. - * @returns A new Statement instance. - */ + * Initiates a new Statement instance for building complex SQL statements. + * This can be used to create WHERE clauses, JOIN conditions, etc. + * @returns A new Statement instance. + */ public static get statement(): Statement { return new Statement(); } /** - * Initiates a new CTE (Common Table Expression) instance. - * This can be used to define CTEs for use in queries. - * @returns A new Cte instance. - */ + * Initiates a new CTE (Common Table Expression) instance. + * This can be used to define CTEs for use in queries. + * @returns A new Cte instance. + */ public static get cte(): Cte { return new Cte(); } /** - * Initiates a new UNION query. - * @returns A new Union instance. - */ + * Initiates a new UNION query. + * @returns A new Union instance. + */ public static get union(): Union { return new Union(); } /** - * Initiates a new Table instance for DDL operations. - * This can be used to create tables and other DDL statements. - * @returns A new Table instance. - */ - public static get table() { + * Initiates a new Table instance for DDL operations. + * This can be used to create tables and other DDL statements. + * @returns A new Table instance. + */ + public static get table(): Table { return new Table(); } - } export default Query; diff --git a/src/queryUtils/Column.ts b/src/queryUtils/Column.ts index 07ff49e..a613689 100644 --- a/src/queryUtils/Column.ts +++ b/src/queryUtils/Column.ts @@ -1,10 +1,11 @@ -import { ColumnTypes } from "../types/ColumnTypes.js"; -import ForeignKey, { Actions } from "../types/ForeignKey.js"; +import type { ColumnTypes } from "../types/ColumnTypes.js"; +import type ForeignKey from "../types/ForeignKey.js"; +import type { Actions } from "../types/ForeignKey.js"; /** - * Class representing a column type. - * It allows setting the type and adding properties, and can build a string representation of the column type. - */ + * Class representing a column type. + * It allows setting the type and adding properties, and can build a string representation of the column type. + */ export class ColumnType { /** The name of the column type. */ private typeName: ColumnTypes | null; @@ -15,64 +16,63 @@ export class ColumnType { /** The name of the column type. */ typeName: ColumnTypes | null = null, /** The properties associated with the column type. */ - properties: (string | { toString(): string })[] = [] + properties: (string | { toString(): string })[] = [], ) { this.typeName = typeName; - this.properties = properties.map(prop => prop.toString()); + this.properties = properties.map((prop) => prop.toString()); } /** - * Sets the type of the column. - * @param typeName - The name of the column type. - * @returns The current instance for method chaining. - */ + * Sets the type of the column. + * @param typeName - The name of the column type. + * @returns The current instance for method chaining. + */ public setType(typeName: ColumnTypes): this { this.typeName = typeName; return this; } /** - * Adds a property to the column type. - * @param property - The property to add. - * @returns The current instance for method chaining. - */ + * Adds a property to the column type. + * @param property - The property to add. + * @returns The current instance for method chaining. + */ public addProperty(property: string): this { this.properties.push(property); return this; } /** - * Builds the string representation of the column type. - * @returns The string representation of the column type. - * @throws Error if the type name is not set. - */ + * Builds the string representation of the column type. + * @returns The string representation of the column type. + * @throws Error if the type name is not set. + */ public build(): string { if (!this.typeName) { throw new Error("Type name is not set."); } if (this.properties.length > 0) { - return `${this.typeName}(${this.properties.join(', ')})`; + return `${this.typeName}(${this.properties.join(", ")})`; } return this.typeName; } /** - * Returns the string representation of the column type. - * @returns The string representation of the column type. - * @throws Error if the type name is not set. - */ + * Returns the string representation of the column type. + * @returns The string representation of the column type. + * @throws Error if the type name is not set. + */ public toString(): string { return this.build(); } - } /** - * Class representing a database column with various attributes and constraints. - * It allows setting the column name, type, nullability, primary key status, uniqueness, - * default value, and check conditions. The class can build a string representation of the column definition. - */ + * Class representing a database column with various attributes and constraints. + * It allows setting the column name, type, nullability, primary key status, uniqueness, + * default value, and check conditions. The class can build a string representation of the column definition. + */ export class ColumnDefinition { /** The name of the column. */ private name: string | null = null; @@ -93,11 +93,11 @@ export class ColumnDefinition { constructor( name: string | null = null, - type: ColumnType | string | null = null + type: ColumnType | string | null = null, ) { this.name = name ?? null; if (type) { - if (typeof type === 'string') { + if (typeof type === "string") { this.type = new ColumnType(type as ColumnTypes); } else { this.type = type; @@ -108,22 +108,22 @@ export class ColumnDefinition { } /** - * Sets the name of the column. - * @param name - The name of the column. - * @returns The current instance for method chaining. - */ + * Sets the name of the column. + * @param name - The name of the column. + * @returns The current instance for method chaining. + */ public setName(name: string): this { this.name = name; return this; } /** - * Sets the type of the column. - * @param type - The type of the column, either as a ColumnType instance or a string. - * @returns The current instance for method chaining. - */ + * Sets the type of the column. + * @param type - The type of the column, either as a ColumnType instance or a string. + * @returns The current instance for method chaining. + */ public setType(type: ColumnType | string): this { - if (typeof type === 'string') { + if (typeof type === "string") { this.type = new ColumnType(type as ColumnTypes); } else { this.type = type; @@ -132,28 +132,28 @@ export class ColumnDefinition { } /** - * Marks the column as nullable. - * @returns The current instance for method chaining. - */ + * Marks the column as nullable. + * @returns The current instance for method chaining. + */ public null(): this { this.isNullable = true; return this; } /** - * Marks the column as not nullable. - * @returns The current instance for method chaining. - */ + * Marks the column as not nullable. + * @returns The current instance for method chaining. + */ public notNull(): this { this.isNullable = false; return this; } /** - * Marks the column as a primary key. - * This also sets the column as not nullable. - * @returns The current instance for method chaining. - */ + * Marks the column as a primary key. + * This also sets the column as not nullable. + * @returns The current instance for method chaining. + */ public primaryKey(): this { this.isPrimaryKey = true; this.isNullable = false; // Primary key columns cannot be null @@ -161,73 +161,76 @@ export class ColumnDefinition { } /** - * Marks the column as unique. - * @returns The current instance for method chaining. - */ + * Marks the column as unique. + * @returns The current instance for method chaining. + */ public unique(): this { this.isUnique = true; return this; } /** - * Sets the default value for the column. - * @param value - The default value for the column. - * @returns The current instance for method chaining. - */ + * Sets the default value for the column. + * @param value - The default value for the column. + * @returns The current instance for method chaining. + */ public default(value: string | { toString(): string }): this { this.defaultValue = value.toString(); return this; } /** - * Sets a check condition for the column. - * @param condition - The check condition for the column. - * @returns The current instance for method chaining. - */ + * Sets a check condition for the column. + * @param condition - The check condition for the column. + * @returns The current instance for method chaining. + */ public check(condition: string): this { this.checkCondition = condition; return this; } /** - * Sets a foreign key constraint for the column. - * @param foreignTable - The name of the foreign table. - * @param foreignColumn - The name of the foreign column. - * @param onDeleteAction - Optional action to take on delete (e.g., CASCADE, SET NULL). - * @param onUpdateAction - Optional action to take on update (e.g., CASCADE, SET NULL). - * @returns The current instance for method chaining. - */ + * Sets a foreign key constraint for the column. + * @param foreignTable - The name of the foreign table. + * @param foreignColumn - The name of the foreign column. + * @param onDeleteAction - Optional action to take on delete (e.g., CASCADE, SET NULL). + * @param onUpdateAction - Optional action to take on update (e.g., CASCADE, SET NULL). + * @returns The current instance for method chaining. + */ public references( foreignTable: string, foreignColumn: string, onDeleteAction?: Actions, - onUpdateAction?: Actions + onUpdateAction?: Actions, ): this { this.foreignKey = { table: foreignTable, column: foreignColumn, onDelete: onDeleteAction, - onUpdate: onUpdateAction + onUpdate: onUpdateAction, }; return this; } /** - * Builds the string representation of the column definition. - * @returns The string representation of the column definition. - * @throws Error if the column name or type is not set. - */ + * Builds the string representation of the column definition. + * @returns The string representation of the column definition. + * @throws Error if the column name or type is not set. + */ public build(forAdding: boolean = false): string { if (!this.name || !this.type) { - let errorMsg = !this.name && !this.type - ? "Column name and type" - : !this.name - ? "Column name" - : "Column type"; - throw new Error(`${errorMsg} ${!this.name && !this.type ? "are" : "is"} not set.`); + const errorMsg = + !this.name && !this.type + ? "Column name and type" + : !this.name + ? "Column name" + : "Column type"; + throw new Error( + `${errorMsg} ${!this.name && !this.type ? "are" : "is"} not set.`, + ); } - let parts: string[] = []; + const parts: string[] = []; parts.push(this.name); parts.push(this.type.toString()); @@ -261,73 +264,85 @@ export class ColumnDefinition { parts.push(fkPart); } - return parts.join(' '); + return parts.join(" "); } public buildToAdd(tableName: string): string[] { - let addColumn = `ALTER TABLE ${tableName} ADD COLUMN ${this.build(true)}`; - let addColumnNullability = this.isNullable + const addColumn = `ALTER TABLE ${tableName} ADD COLUMN ${this.build(true)}`; + const addColumnNullability = this.isNullable ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} DROP NOT NULL` : `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET NOT NULL`; - let addColumnDefault = this.defaultValue !== undefined - ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET DEFAULT ${this.defaultValue}` - : ''; - let addColumnCheck = this.checkCondition !== undefined - ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_check CHECK (${this.checkCondition})` - : ''; - let addColumnPrimaryKey = this.isPrimaryKey + const addColumnDefault = + this.defaultValue !== undefined + ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET DEFAULT ${this.defaultValue}` + : ""; + const addColumnCheck = + this.checkCondition !== undefined + ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_check CHECK (${this.checkCondition})` + : ""; + const addColumnPrimaryKey = this.isPrimaryKey ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_pkey PRIMARY KEY (${this.name})` - : ''; - let addColumnUnique = this.isUnique + : ""; + const addColumnUnique = this.isUnique ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_unique UNIQUE (${this.name})` - : ''; - let addForeignKey = this.foreignKey + : ""; + const addForeignKey = this.foreignKey ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_fkey FOREIGN KEY (${this.name}) REFERENCES ${this.foreignKey.table}(${this.foreignKey.column})` + - (this.foreignKey.onDelete ? ` ON DELETE ${this.foreignKey.onDelete}` : '') + - (this.foreignKey.onUpdate ? ` ON UPDATE ${this.foreignKey.onUpdate}` : '') - : ''; - - let additions = [ + (this.foreignKey.onDelete + ? ` ON DELETE ${this.foreignKey.onDelete}` + : "") + + (this.foreignKey.onUpdate + ? ` ON UPDATE ${this.foreignKey.onUpdate}` + : "") + : ""; + + const additions = [ addColumn, addColumnNullability, addColumnDefault, addColumnCheck, addColumnPrimaryKey, addColumnUnique, - addForeignKey - ].filter(part => part !== ''); + addForeignKey, + ].filter((part) => part !== ""); return additions; } - public buildToAlter( - tableName: string, - previousName?: string - ): string[] { - let alterColumnName = previousName !== this.name ? `ALTER TABLE ${tableName} RENAME COLUMN ${previousName} TO ${this.name}` : ''; - let alterColumnType = `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} TYPE ${this.type?.toString()}`; - let alterColumnNullability = this.isNullable + public buildToAlter(tableName: string, previousName?: string): string[] { + const alterColumnName = + previousName !== this.name + ? `ALTER TABLE ${tableName} RENAME COLUMN ${previousName} TO ${this.name}` + : ""; + const alterColumnType = `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} TYPE ${this.type?.toString()}`; + const alterColumnNullability = this.isNullable ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} DROP NOT NULL` : `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET NOT NULL`; - let alterColumnDefault = this.defaultValue !== undefined - ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET DEFAULT ${this.defaultValue}` - : `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} DROP DEFAULT`; - let alterColumnCheck = this.checkCondition !== undefined - ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_check CHECK (${this.checkCondition})` - : ''; - let alterColumnPrimaryKey = this.isPrimaryKey + const alterColumnDefault = + this.defaultValue !== undefined + ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET DEFAULT ${this.defaultValue}` + : `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} DROP DEFAULT`; + const alterColumnCheck = + this.checkCondition !== undefined + ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_check CHECK (${this.checkCondition})` + : ""; + const alterColumnPrimaryKey = this.isPrimaryKey ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_pkey PRIMARY KEY (${this.name})` - : ''; - let alterColumnUnique = this.isUnique + : ""; + const alterColumnUnique = this.isUnique ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_unique UNIQUE (${this.name})` - : ''; - let alterForeignKey = this.foreignKey + : ""; + const alterForeignKey = this.foreignKey ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_fkey FOREIGN KEY (${this.name}) REFERENCES ${this.foreignKey.table}(${this.foreignKey.column})` + - (this.foreignKey.onDelete ? ` ON DELETE ${this.foreignKey.onDelete}` : '') + - (this.foreignKey.onUpdate ? ` ON UPDATE ${this.foreignKey.onUpdate}` : '') - : ''; - - let alterations = [ + (this.foreignKey.onDelete + ? ` ON DELETE ${this.foreignKey.onDelete}` + : "") + + (this.foreignKey.onUpdate + ? ` ON UPDATE ${this.foreignKey.onUpdate}` + : "") + : ""; + + const alterations = [ alterColumnName, alterColumnType, alterColumnNullability, @@ -335,32 +350,31 @@ export class ColumnDefinition { alterColumnCheck, alterColumnPrimaryKey, alterColumnUnique, - alterForeignKey - ].filter(part => part !== ''); + alterForeignKey, + ].filter((part) => part !== ""); return alterations; } /** - * Returns the string representation of the column definition. - * @returns The string representation of the column definition. - * @throws Error if the column name or type is not set. - */ + * Returns the string representation of the column definition. + * @returns The string representation of the column definition. + * @throws Error if the column name or type is not set. + */ public toString(): string { return this.build(); } - } /** - * Factory function to create a new ColumnDefinition instance. - * @param name - The name of the column (optional). - * @param type - The type of the column, either as a ColumnType instance or a string (optional). - * @returns A new ColumnDefinition instance. - */ + * Factory function to create a new ColumnDefinition instance. + * @param name - The name of the column (optional). + * @param type - The type of the column, either as a ColumnType instance or a string (optional). + * @returns A new ColumnDefinition instance. + */ export default function Column( name: string | null = null, - type: ColumnType | string | null = null + type: ColumnType | string | null = null, ): ColumnDefinition { return new ColumnDefinition(name, type); } diff --git a/src/queryUtils/index.ts b/src/queryUtils/index.ts index 6bf5ec7..544d3c0 100644 --- a/src/queryUtils/index.ts +++ b/src/queryUtils/index.ts @@ -1,2 +1,2 @@ -export * from './Column.js'; -export { default as Column } from './Column.js'; +export * from "./Column.js"; +export { default as Column } from "./Column.js"; diff --git a/src/searchModule.ts b/src/searchModule.ts index 71083a0..7192e75 100644 --- a/src/searchModule.ts +++ b/src/searchModule.ts @@ -1,38 +1,35 @@ import Query from "./queryMaker.js"; -import Statement, { StatementKind } from "./statementMaker.js"; - +import type Statement from "./statementMaker.js"; +import type { StatementKind } from "./statementMaker.js"; /** - * SearchModule class provides methods for building search-related SQL conditions. - * It includes methods for full-text search, word-by-word search, and fuzzy search using trigram similarity. - * Each method modifies the provided Statement instance to add the appropriate search conditions. - */ + * SearchModule class provides methods for building search-related SQL conditions. + * It includes methods for full-text search, word-by-word search, and fuzzy search using trigram similarity. + * Each method modifies the provided Statement instance to add the appropriate search conditions. + */ export default class SearchModule { - /** - * Creates an instance of SearchModule. - * @param statement - The Statement instance to which search conditions will be added. - */ - constructor( - private statement: Statement, - ) {} + * Creates an instance of SearchModule. + * @param statement - The Statement instance to which search conditions will be added. + */ + constructor(private statement: Statement) {} /** - * Performs a full-text search on a specified field with the given query. - * It supports case-insensitive searches and allows combining conditions with AND/OR. - * The method uses the LIKE or ILIKE operator based on the case sensitivity requirement. - * @param field - The database field to search. - * @param query - The search query string. - * @param caseInsensitive - Whether the search should be case-insensitive (default is true). - * @param statementKind - The kind of statement to combine with (default is 'AND'). - * @returns The modified Statement instance with the added full-text search condition. - */ + * Performs a full-text search on a specified field with the given query. + * It supports case-insensitive searches and allows combining conditions with AND/OR. + * The method uses the LIKE or ILIKE operator based on the case sensitivity requirement. + * @param field - The database field to search. + * @param query - The search query string. + * @param caseInsensitive - Whether the search should be case-insensitive (default is true). + * @param statementKind - The kind of statement to combine with (default is 'AND'). + * @returns The modified Statement instance with the added full-text search condition. + */ public fulltext( field: string, query: string, caseInsensitive: boolean = true, - statementKind: StatementKind = 'AND' - ) { + statementKind: StatementKind = "AND", + ): Statement { if (caseInsensitive) { this.statement.ilike(field, `%${query}%`, statementKind); } else { @@ -43,36 +40,36 @@ export default class SearchModule { } /** - * Performs a full-text search using PostgreSQL's full-text search capabilities. - * It constructs a ts_vector and ts_query for more advanced search functionality. - * The method allows specifying a text search configuration and combining conditions with AND/OR. - * @param field - The database field to search. - * @param query - The search query string. - * @param config - The text search configuration to use (default is 'simple'). - * @param statementKind - The kind of statement to combine with (default is 'AND'). - * @returns The modified Statement instance with the added full-text search condition. - */ + * Performs a full-text search using PostgreSQL's full-text search capabilities. + * It constructs a ts_vector and ts_query for more advanced search functionality. + * The method allows specifying a text search configuration and combining conditions with AND/OR. + * @param field - The database field to search. + * @param query - The search query string. + * @param config - The text search configuration to use (default is 'simple'). + * @param statementKind - The kind of statement to combine with (default is 'AND'). + * @returns The modified Statement instance with the added full-text search condition. + */ public fulltextTsVector( field: string, query: string, - config: string = 'simple', - statementKind: StatementKind = 'AND' - ) { + config: string = "simple", + statementKind: StatementKind = "AND", + ): Statement { const tsQuery = query - .split(' ') - .filter(word => word.trim()) - .map(word => `${word}:*`) - .join(' & '); + .split(" ") + .filter((word) => word.trim()) + .map((word) => `${word}:*`) + .join(" & "); const tsVectorCondition = Query.statement.raw( - '', + "", `to_tsvector(?, ${field}) @@ to_tsquery(?, ?)`, config, config, - tsQuery + tsQuery, ); - if (statementKind === 'AND') { + if (statementKind === "AND") { this.statement.and(tsVectorCondition); } else { this.statement.or(tsVectorCondition); @@ -82,23 +79,23 @@ export default class SearchModule { } /** - * Performs a word-by-word search on a specified field with the given query. - * It splits the query into individual words and searches for each word separately. - * The method supports case-insensitive searches and allows combining conditions with AND/OR. - * @param field - The database field to search. - * @param query - The search query string. - * @param caseInsensitive - Whether the search should be case-insensitive (default is true). - * @param statementKind - The kind of statement to combine with (default is 'AND'). - * @returns The modified Statement instance with the added word-by-word search conditions. - */ + * Performs a word-by-word search on a specified field with the given query. + * It splits the query into individual words and searches for each word separately. + * The method supports case-insensitive searches and allows combining conditions with AND/OR. + * @param field - The database field to search. + * @param query - The search query string. + * @param caseInsensitive - Whether the search should be case-insensitive (default is true). + * @param statementKind - The kind of statement to combine with (default is 'AND'). + * @returns The modified Statement instance with the added word-by-word search conditions. + */ public wordByWord( field: string, query: string, caseInsensitive: boolean = true, - statementKind: StatementKind = 'AND' - ) { - const words = query.split(' ').filter(word => word.trim()); - words.forEach(word => { + statementKind: StatementKind = "AND", + ): Statement { + const words = query.split(" ").filter((word) => word.trim()); + words.forEach((word) => { if (caseInsensitive) { this.statement.ilike(field, `%${word}%`, statementKind); } else { @@ -110,33 +107,31 @@ export default class SearchModule { } /** - * Performs a fuzzy search using trigram similarity on a specified field with the given query. - * It uses PostgreSQL's pg_trgm extension to find similar strings based on a similarity threshold. - * The method allows specifying the similarity threshold and combining conditions with AND/OR. - * @param field - The database field to search. - * @param query - The search query string. - * @param similarityThreshold - The similarity threshold (default is 0.3). - * @param statementKind - The kind of statement to combine with (default is 'AND'). - * @returns The modified Statement instance with the added fuzzy search conditions. - */ + * Performs a fuzzy search using trigram similarity on a specified field with the given query. + * It uses PostgreSQL's pg_trgm extension to find similar strings based on a similarity threshold. + * The method allows specifying the similarity threshold and combining conditions with AND/OR. + * @param field - The database field to search. + * @param query - The search query string. + * @param similarityThreshold - The similarity threshold (default is 0.3). + * @param statementKind - The kind of statement to combine with (default is 'AND'). + * @returns The modified Statement instance with the added fuzzy search conditions. + */ public fuzzyTrigram( field: string, query: string, similarityThreshold: number = 0.3, - statementKind: StatementKind = 'AND' - ) { + statementKind: StatementKind = "AND", + ): Statement { const fuzzyStatement = Query.statement - .raw('', `${field} % ?`, query) - .raw('AND', `similarity(${field}, ?) >= ?`, query, similarityThreshold); + .raw("", `${field} % ?`, query) + .raw("AND", `similarity(${field}, ?) >= ?`, query, similarityThreshold); - if (statementKind === 'AND') { + if (statementKind === "AND") { this.statement.and(fuzzyStatement); } else { this.statement.or(fuzzyStatement); } - + return this.statement; } - - } diff --git a/src/signal.ts b/src/signal.ts index 8bc9415..2927b2e 100644 --- a/src/signal.ts +++ b/src/signal.ts @@ -1,70 +1,62 @@ - /** - * Callback function type for subscribers. - * It can return void or a Promise. - */ + * Callback function type for subscribers. + * It can return void or a Promise. + */ export type callbackFn = (value: T) => any | Promise; /** - * Signal class to manage a value and notify subscribers on changes. - * It supports subscribing, subscribing once, and clearing subscribers. - */ + * Signal class to manage a value and notify subscribers on changes. + * It supports subscribing, subscribing once, and clearing subscribers. + */ export default class Signal { - /** - * If true, subscribers are notified immediately upon subscription. - * Useful for late subscribers to get the current value right away. - */ + * If true, subscribers are notified immediately upon subscription. + * Useful for late subscribers to get the current value right away. + */ public immediate: boolean; /** - * List of subscriber callback functions. - */ + * List of subscriber callback functions. + */ private subscribers: callbackFn[] = []; /** - * The current value of the signal. - */ + * The current value of the signal. + */ private _value: T | null = null; /** - * Creates a new Signal instance with an initial value. - * @param initialValue - The initial value of the signal. - */ - constructor( - initialValue: T | null = null, - immediate: boolean = false - ) { + * Creates a new Signal instance with an initial value. + * @param initialValue - The initial value of the signal. + */ + constructor(initialValue: T | null = null, immediate: boolean = false) { this._value = initialValue; this.immediate = immediate; } /** - * Creates a new Signal instance with an initial value. - * @param initialValue - The initial value of the signal. - * @returns A new Signal instance. - */ + * Creates a new Signal instance with an initial value. + * @param initialValue - The initial value of the signal. + * @returns A new Signal instance. + */ public static create( initialValue: U | null = null, - immediate: boolean = false + immediate: boolean = false, ): Signal { - return new Signal( - initialValue, - immediate - ); + return new Signal(initialValue, immediate); } /** - * Destroys the signal by clearing subscribers and setting the value to null. - */ + * Destroys the signal by clearing subscribers and setting the value to null. + */ public destroy() { this.clearSubscribers(); this._value = null; } /** - * The current value of the signal. - */ + * The current value of the signal. + */ public get value(): T { return this._value as T; } @@ -75,20 +67,20 @@ export default class Signal { } /** - * Sets the value of the signal using a callback function. - * The callback receives the current value and should return the new value. - * @param callback - The callback function to compute the new value. - */ + * Sets the value of the signal using a callback function. + * The callback receives the current value and should return the new value. + * @param callback - The callback function to compute the new value. + */ public setValue(callback: (currentValue: T) => any) { callback(this._value!); this.notifySubscribers(this._value as T); } /** - * Subscribes to changes in the signal's value. - * @param callback - The callback function(s) to be called when the value changes. - * @returns A function to unsubscribe the provided callbacks. - */ + * Subscribes to changes in the signal's value. + * @param callback - The callback function(s) to be called when the value changes. + * @returns A function to unsubscribe the provided callbacks. + */ public subscribe(...callback: callbackFn[]) { this.subscribers.push(...callback); @@ -100,21 +92,23 @@ export default class Signal { // Return an unsubscribe function return () => { - this.subscribers = this.subscribers.filter(sub => !callback.includes(sub)); + this.subscribers = this.subscribers.filter( + (sub) => !callback.includes(sub), + ); }; } /** - * Subscribes to the signal's value changes only once. - * The callback will be called the next time the value changes and then unsubscribed. - * @param callback - The callback function(s) to be called when the value changes. - * @returns A function to unsubscribe the provided callbacks if they haven't been called yet. - */ + * Subscribes to the signal's value changes only once. + * The callback will be called the next time the value changes and then unsubscribed. + * @param callback - The callback function(s) to be called when the value changes. + * @returns A function to unsubscribe the provided callbacks if they haven't been called yet. + */ public subscribeOnce(...callback: callbackFn[]) { - const onceCallbacks = callback.map(cb => { + const onceCallbacks = callback.map((cb) => { const wrapper: callbackFn = (value: T) => { cb(value); - this.subscribers = this.subscribers.filter(sub => sub !== wrapper); + this.subscribers = this.subscribers.filter((sub) => sub !== wrapper); }; return wrapper; }); @@ -128,14 +122,16 @@ export default class Signal { // Return an unsubscribe function return () => { - this.subscribers = this.subscribers.filter(sub => !onceCallbacks.includes(sub)); + this.subscribers = this.subscribers.filter( + (sub) => !onceCallbacks.includes(sub), + ); }; } /** - * Notifies all subscribers with the new value. - * @param value - The new value to be passed to subscribers. - */ + * Notifies all subscribers with the new value. + * @param value - The new value to be passed to subscribers. + */ private notifySubscribers(value: T) { for (const subscriber of this.subscribers) { subscriber(value); @@ -143,8 +139,8 @@ export default class Signal { } /** - * Clears all subscribers from the signal. - */ + * Clears all subscribers from the signal. + */ public clearSubscribers() { this.subscribers = []; } diff --git a/src/sqlEscaper.ts b/src/sqlEscaper.ts index 343c99e..c74e606 100644 --- a/src/sqlEscaper.ts +++ b/src/sqlEscaper.ts @@ -1,30 +1,28 @@ -import { match, P } from "ts-pattern"; import sqlFlavor from "./types/sqlFlavor.js"; /** - * SqlEscaper provides static methods to escape SQL identifiers and values - * according to different SQL dialects (flavors). It includes methods to escape - * identifiers, table names, and to append schema names in queries. - */ + * SqlEscaper provides static methods to escape SQL identifiers and values + * according to different SQL dialects (flavors). It includes methods to escape + * identifiers, table names, and to append schema names in queries. + */ export default class SqlEscaper { - /** Regex to identify schema placeholders like $schema, $schema1, $schema2, etc. */ private static schemaRegex = /^\$schema\d*$/; /** - * Escapes a given string value by wrapping it with the specified escape character - * and replacing occurrences of the escape character within the value. - * @param value - The string value to be escaped. - * @param escapeChar - The character used to escape the value (default is double quote `"`). - * @param escapeCharReplacement - The string to replace occurrences of the escape character (default is two double quotes `""`). - * @returns The escaped string value. - */ + * Escapes a given string value by wrapping it with the specified escape character + * and replacing occurrences of the escape character within the value. + * @param value - The string value to be escaped. + * @param escapeChar - The character used to escape the value (default is double quote `"`). + * @param escapeCharReplacement - The string to replace occurrences of the escape character (default is two double quotes `""`). + * @returns The escaped string value. + */ public static escape( value: string, - escapeCharLeft: string | RegExp = "\"", + escapeCharLeft: string | RegExp = '"', escapeCharRight: string | RegExp | null = null, - escapeCharLeftReplacement: string = "\"\"", - escapeCharRightReplacement: string | null = null + escapeCharLeftReplacement: string = '""', + escapeCharRightReplacement: string | null = null, ): string { const escapeCharLeftRegex = new RegExp(escapeCharLeft, "g"); @@ -46,23 +44,26 @@ export default class SqlEscaper { value = value.replace(escapeCharLeftRegex, escapeCharLeftReplacement); } - escapeCharLeft = typeof escapeCharLeft === "string" ? escapeCharLeft.replace("\\", "") : ""; - escapeCharRight = typeof escapeCharRight === "string" ? escapeCharRight.replace("\\", "") : ""; + escapeCharLeft = + typeof escapeCharLeft === "string" + ? escapeCharLeft.replace("\\", "") + : ""; + escapeCharRight = + typeof escapeCharRight === "string" + ? escapeCharRight.replace("\\", "") + : ""; return `${escapeCharLeft}${value}${escapeCharRight}`; } /** - * Replaces schema placeholders in the query with actual schema names from the provided array. - * Placeholders are in the format $schema, $schema1, $schema2, etc. - * @param query - The SQL query string containing schema placeholders. - * @param schemas - An array of schema names to replace the placeholders. - * @returns The SQL query string with schema names appended. - * @throws Error if a placeholder index is out of bounds for the provided schemas array. - */ - public static appendSchemas( - query: string, - schemas: string[] = [] - ) { + * Replaces schema placeholders in the query with actual schema names from the provided array. + * Placeholders are in the format $schema, $schema1, $schema2, etc. + * @param query - The SQL query string containing schema placeholders. + * @param schemas - An array of schema names to replace the placeholders. + * @returns The SQL query string with schema names appended. + * @throws Error if a placeholder index is out of bounds for the provided schemas array. + */ + public static appendSchemas(query: string, schemas: string[] = []) { return query.replace(/\$schema\d*/g, (match) => { const indexMatch = match.match(/\d+/); const index = indexMatch ? parseInt(indexMatch[0], 10) : 0; @@ -72,74 +73,71 @@ export default class SqlEscaper { throw new Error( [ `Schema index ${index} out of bounds for provided schemas.`, - `Provided schemas: [${schemas.join(", ")}]` - ].join(" ") + `Provided schemas: [${schemas.join(", ")}]`, + ].join(" "), ); } }); } /** - * Will escape an identifier for use in a SQL statement. - * The identifier will be escaped according to the specified SQL flavor. - * It will return a string of the escaped identifier. - * @param identifier - The identifier to be escaped. - * @param flavor - The SQL flavor to use for escaping (e.g., postgres, mysql, mssql, sqlite, oracle). - * @returns The escaped identifier string. - * @throws Error if the SQL flavor is unsupported. - * @remarks If the identifier matches the schema regex (e.g., $schema), it will be returned as is without escaping. - */ + * Will escape an identifier for use in a SQL statement. + * The identifier will be escaped according to the specified SQL flavor. + * It will return a string of the escaped identifier. + * @param identifier - The identifier to be escaped. + * @param flavor - The SQL flavor to use for escaping (e.g., postgres, mysql, mssql, sqlite, oracle). + * @returns The escaped identifier string. + * @throws Error if the SQL flavor is unsupported. + * @remarks If the identifier matches the schema regex (e.g., $schema), it will be returned as is without escaping. + */ public static escapeIdentifier( identifier: string, - flavor: sqlFlavor + flavor: sqlFlavor, ): string { - if (identifier) { - const isSchema = this.schemaRegex.test(identifier); + const isSchema = SqlEscaper.schemaRegex.test(identifier); if (isSchema) { return identifier; } } try { - const escapedIdentifier = match(flavor) - .returnType() - .with(P.union(sqlFlavor.postgres, sqlFlavor.sqlite), () => { - return this.escape(identifier, "\"", null, "\"\"", null); - }) - .with(sqlFlavor.mysql, () => { - return this.escape(identifier, "`", null, "``", null); - }) - .with(sqlFlavor.mssql, () => { - return this.escape(identifier, "\\[", "]", "]]", "[["); - }) - .with(sqlFlavor.oracle, () => { - return this.escape(identifier, "\"", null, "\"\"", null); - }) - .exhaustive(); - - return escapedIdentifier!; + switch (flavor) { + case sqlFlavor.postgres: + case sqlFlavor.sqlite: + case sqlFlavor.oracle: + return SqlEscaper.escape(identifier, '"', null, '""', null); + case sqlFlavor.mysql: + return SqlEscaper.escape(identifier, "`", null, "``", null); + case sqlFlavor.mssql: + return SqlEscaper.escape(identifier, "\\[", "]", "]]", "[["); + default: + throw new Error(`Unsupported SQL flavor: ${flavor}`); + } } catch (error) { - if (error instanceof Error && error.message.includes("Pattern matching error")) { + if ( + error instanceof Error && + error.message.includes("Pattern matching error") + ) { throw new Error(`Unsupported SQL flavor: ${flavor}`); } else throw error; } } /** - * Will escape a list of identifiers for use in a SELECT statement. - * The identifiers will be escaped according to the specified SQL flavor. - * It will return a string array (string[]) of the escaped identifiers. - * @param identifiers - The list of identifiers to be escaped. - * @param flavor - The SQL flavor to use for escaping (e.g., postgres, mysql, mssql, sqlite, oracle). - * @returns An array of escaped identifier strings. - * @throws Error if an identifier with an AS clause is invalid. - */ + * Will escape a list of identifiers for use in a SELECT statement. + * The identifiers will be escaped according to the specified SQL flavor. + * It will return a string array (string[]) of the escaped identifiers. + * @param identifiers - The list of identifiers to be escaped. + * @param flavor - The SQL flavor to use for escaping (e.g., postgres, mysql, mssql, sqlite, oracle). + * @returns An array of escaped identifier strings. + * @throws Error if an identifier with an AS clause is invalid. + */ public static escapeSelectIdentifiers( identifiers: string[], - flavor: sqlFlavor + flavor: sqlFlavor, ): string[] { - return identifiers.map(identifier => { + return identifiers.map((identifier) => { if (identifier.trim().toUpperCase().startsWith("AS ")) { throw new Error(`Invalid identifier with AS clause: ${identifier}`); } else if (identifier.trim().toUpperCase().endsWith(" AS")) { @@ -155,34 +153,35 @@ export default class SqlEscaper { } const columnParts = column.split("."); - const escapedColumn = columnParts.map(part => this.escapeIdentifier(part.trim(), flavor)).join("."); - const escapedAlias = this.escapeIdentifier(alias.trim(), flavor); + const escapedColumn = columnParts + .map((part) => SqlEscaper.escapeIdentifier(part.trim(), flavor)) + .join("."); + const escapedAlias = SqlEscaper.escapeIdentifier(alias.trim(), flavor); return `${escapedColumn} AS ${escapedAlias}`; } else { const columnParts = identifier.split("."); if (columnParts.length > 1) { - return columnParts.map(part => this.escapeIdentifier(part.trim(), flavor)).join("."); + return columnParts + .map((part) => SqlEscaper.escapeIdentifier(part.trim(), flavor)) + .join("."); } - return this.escapeIdentifier(identifier.trim(), flavor); + return SqlEscaper.escapeIdentifier(identifier.trim(), flavor); } }); } /** - * Will escape a table name for use in a SQL statement. - * The table name will be escaped according to the specified SQL flavor. - * It will return a string of the escaped table name. - * It supports $schema.table format. - * @param tableName - The table name to be escaped. - * @param flavor - The SQL flavor to use for escaping (e.g., postgres, mysql, mssql, sqlite, oracle). - * @returns The escaped table name string. - * @throws Error if the table name is invalid. - */ - public static escapeTableName( - tableName: string, - flavor: sqlFlavor - ): string { + * Will escape a table name for use in a SQL statement. + * The table name will be escaped according to the specified SQL flavor. + * It will return a string of the escaped table name. + * It supports $schema.table format. + * @param tableName - The table name to be escaped. + * @param flavor - The SQL flavor to use for escaping (e.g., postgres, mysql, mssql, sqlite, oracle). + * @returns The escaped table name string. + * @throws Error if the table name is invalid. + */ + public static escapeTableName(tableName: string, flavor: sqlFlavor): string { const parts = tableName.split("."); if (parts.length === 2) { const [schema, table] = parts; @@ -191,14 +190,13 @@ export default class SqlEscaper { throw new Error(`Invalid table name with schema: ${tableName}`); } - const escapedSchema = this.escapeIdentifier(schema.trim(), flavor); - const escapedTable = this.escapeIdentifier(table.trim(), flavor); + const escapedSchema = SqlEscaper.escapeIdentifier(schema.trim(), flavor); + const escapedTable = SqlEscaper.escapeIdentifier(table.trim(), flavor); return `${escapedSchema}.${escapedTable}`; } else if (parts.length === 1) { - return this.escapeIdentifier(tableName.trim(), flavor); + return SqlEscaper.escapeIdentifier(tableName.trim(), flavor); } else { throw new Error(`Invalid table name: ${tableName}`); } } - } diff --git a/src/statementMaker.ts b/src/statementMaker.ts index 8aac068..fe7362a 100644 --- a/src/statementMaker.ts +++ b/src/statementMaker.ts @@ -2,66 +2,66 @@ import SearchModule from "./searchModule.js"; import Signal from "./signal.js"; /** - * Defines the statement kind for combining statements. - * 'AND' and 'OR' are the two possible kinds. - */ -export type StatementKind = 'AND' | 'OR'; + * Defines the statement kind for combining statements. + * 'AND' and 'OR' are the two possible kinds. + */ +export type StatementKind = "AND" | "OR"; /** - * A class to build SQL WHERE clauses with parameterized queries. - * It supports various SQL conditions and allows combining multiple statements. - * It ensures that the generated SQL is safe from injection attacks by using placeholders. - * It can also handle nested statements and subqueries. - */ + * A class to build SQL WHERE clauses with parameterized queries. + * It supports various SQL conditions and allows combining multiple statements. + * It ensures that the generated SQL is safe from injection attacks by using placeholders. + * It can also handle nested statements and subqueries. + */ export default class Statement { /** - * The current index for parameter placeholders. - */ + * The current index for parameter placeholders. + */ private index: Signal; /** - * Array to hold individual unparsed SQL statements. - * These statements will be combined to form the final SQL clause. - * This is a reactive signal, so changes will trigger re-parsing - * or invalidation based on the constructor option. - */ + * Array to hold individual unparsed SQL statements. + * These statements will be combined to form the final SQL clause. + * This is a reactive signal, so changes will trigger re-parsing + * or invalidation based on the constructor option. + */ private statements = Signal.create([]); /** - * The final parsed SQL statement with placeholders. - * This is generated when the build method is called. - */ + * The final parsed SQL statement with placeholders. + * This is generated when the build method is called. + */ private parsedStatement: string | null = null; /** - * Array to hold the values corresponding to the placeholders in the SQL statement. - * These values will be used in the parameterized query execution. - * This is a reactive signal, so changes will trigger re-parsing - * or invalidation based on the constructor option. - */ + * Array to hold the values corresponding to the placeholders in the SQL statement. + * These values will be used in the parameterized query execution. + * This is a reactive signal, so changes will trigger re-parsing + * or invalidation based on the constructor option. + */ private values = Signal.create([]); /** - * Flag to determine if the final statement should include the 'WHERE' keyword. - * This is useful when the statement is part of a larger SQL query. - */ + * Flag to determine if the final statement should include the 'WHERE' keyword. + * This is useful when the statement is part of a larger SQL query. + */ private addWhere = true; /** - * Flag to indicate if the statement should be re-parsed on changes. - * This is useful for performance optimization, signals will handle reactivity. - * If false, the statement will only be parsed when build() is called. - * If true, it will re-parse on every change to statements or values. - */ + * Flag to indicate if the statement should be re-parsed on changes. + * This is useful for performance optimization, signals will handle reactivity. + * If false, the statement will only be parsed when build() is called. + * If true, it will re-parse on every change to statements or values. + */ private reparseOnChange: boolean; /** - * Array of functions to unsubscribe from signals. - * This is used to remove listeners when reparseOnChange is toggled. - */ + * Array of functions to unsubscribe from signals. + * This is used to remove listeners when reparseOnChange is toggled. + */ private unsubscribeSignals: (() => void)[] = []; /** - * Creates an instance of the Statement class. - * @param initialOffset - An optional offset to start the parameter index from. - * This is useful when combining multiple statements to ensure unique placeholders. - */ + * Creates an instance of the Statement class. + * @param initialOffset - An optional offset to start the parameter index from. + * This is useful when combining multiple statements to ensure unique placeholders. + */ constructor(initialOffset = 0, reparseOnChange = false) { this.index = Signal.create(1 + initialOffset); this.reparseOnChange = reparseOnChange; @@ -69,30 +69,30 @@ export default class Statement { } /** - * Unsubscribes from all signal listeners. - * This is used when toggling the reparseOnChange flag to prevent memory leaks. - */ - private unsubscribeAllSignals() { - this.unsubscribeSignals.forEach(unsub => unsub()); + * Unsubscribes from all signal listeners. + * This is used when toggling the reparseOnChange flag to prevent memory leaks. + */ + private unsubscribeAllSignals(): void { + this.unsubscribeSignals.forEach((unsub) => unsub()); this.unsubscribeSignals = []; } /** - * Keys of the signals to subscribe to for changes. - * This is used to set up listeners for statements, values, and index changes. - */ - private subscribeKeys = ['statements', 'values', 'index']; + * Keys of the signals to subscribe to for changes. + * This is used to set up listeners for statements, values, and index changes. + */ + private subscribeKeys = ["statements", "values", "index"]; /** - * Subscribes to changes in the statements and values signals. - * Depending on the reparseOnChange flag, it either invalidates the parsed statement - * or triggers a re-parse when changes occur. - */ - private subscribeToSignals() { + * Subscribes to changes in the statements and values signals. + * Depending on the reparseOnChange flag, it either invalidates the parsed statement + * or triggers a re-parse when changes occur. + */ + private subscribeToSignals(): void { if (!this.reparseOnChange) { for (const key of this.subscribeKeys) { this.unsubscribeSignals.push( - (this as any)[key].subscribe(() => this.invalidate()) + (this as any)[key].subscribe(() => this.invalidate()), ); } } else { @@ -103,17 +103,17 @@ export default class Statement { try { this.build(); } catch {} - }) + }), ); } } } /** - * Enables re-parsing of the statement whenever there are changes to the statements or values. - * This is useful for scenarios where the statement needs to be kept up-to-date with changes. - * @returns The current Statement instance for method chaining. - */ + * Enables re-parsing of the statement whenever there are changes to the statements or values. + * This is useful for scenarios where the statement needs to be kept up-to-date with changes. + * @returns The current Statement instance for method chaining. + */ public enableReparseOnChange(): this { if (this.reparseOnChange) return this; @@ -125,11 +125,11 @@ export default class Statement { } /** - * Disables re-parsing of the statement on changes to the statements or values. - * Instead, the statement will only be parsed when the build method is called. - * This is useful for performance optimization in scenarios where changes are frequent. - * @returns The current Statement instance for method chaining. - */ + * Disables re-parsing of the statement on changes to the statements or values. + * Instead, the statement will only be parsed when the build method is called. + * This is useful for performance optimization in scenarios where changes are frequent. + * @returns The current Statement instance for method chaining. + */ public disableReparseOnChange(): this { if (!this.reparseOnChange) return this; @@ -141,85 +141,85 @@ export default class Statement { } /** - * Adds multiple parameters to the values array and updates the index accordingly. - * This is useful when you have a list of values to be used in the SQL statement. - * @param params - An array of values to be added. - * @returns The current Statement instance for method chaining. - */ - public addParams(params: any[]) { + * Adds multiple parameters to the values array and updates the index accordingly. + * This is useful when you have a list of values to be used in the SQL statement. + * @param params - An array of values to be added. + * @returns The current Statement instance for method chaining. + */ + public addParams(params: any[]): this { this.values.value = [...params, ...this.values.value]; this.index.value += params.length; return this; } /** - * Adds an offset to the current index. - * This is useful when you want to adjust the starting point for parameter placeholders. - * @param offset - The number to add to the current index. - * @returns The current Statement instance for method chaining. - */ - public addOffset(offset: number) { + * Adds an offset to the current index. + * This is useful when you want to adjust the starting point for parameter placeholders. + * @param offset - The number to add to the current index. + * @returns The current Statement instance for method chaining. + */ + public addOffset(offset: number): this { this.index.value += offset; return this; } /** - * Sets the current index to a specific value. - * This is useful when you want to reset or set the starting point for parameter placeholders. - * @param offset - The value to set the current index to. - * @returns The current Statement instance for method chaining. - */ - public setOffset(offset: number) { + * Sets the current index to a specific value. + * This is useful when you want to reset or set the starting point for parameter placeholders. + * @param offset - The value to set the current index to. + * @returns The current Statement instance for method chaining. + */ + public setOffset(offset: number): this { this.index.value = offset; return this; } /** - * Enables the addition of the 'WHERE' keyword in the final SQL statement. - * This is useful when the statement is a standalone WHERE clause. - * @returns void - */ - public enableWhere() { + * Enables the addition of the 'WHERE' keyword in the final SQL statement. + * This is useful when the statement is a standalone WHERE clause. + * @returns void + */ + public enableWhere(): this { this.addWhere = true; return this; } /** - * Disables the addition of the 'WHERE' keyword in the final SQL statement. - * This is useful when the statement is part of a larger SQL query that already includes 'WHERE'. - * @returns void - */ - public disableWhere() { + * Disables the addition of the 'WHERE' keyword in the final SQL statement. + * This is useful when the statement is part of a larger SQL query that already includes 'WHERE'. + * @returns void + */ + public disableWhere(): this { this.addWhere = false; return this; } /** - * Counts the number of placeholders ('?') in a given SQL template string. - * This is useful for validating that the number of placeholders matches the number of provided values. - * @param template - The SQL template string to be analyzed. - * @returns The count of placeholders in the template. - */ - private static countPlaceholders(template: string) { + * Counts the number of placeholders ('?') in a given SQL template string. + * This is useful for validating that the number of placeholders matches the number of provided values. + * @param template - The SQL template string to be analyzed. + * @returns The count of placeholders in the template. + */ + private static countPlaceholders(template: string): number { return (template.match(/\?/g) || []).length; } /** - * Invalidates the current parsed statement. - * This forces a re-parse the next time the build method is called. - * @returns void - */ - public invalidate() { + * Invalidates the current parsed statement. + * This forces a re-parse the next time the build method is called. + * @returns void + */ + public invalidate(): void { this.parsedStatement = null; } /** - * Resets the statement to its initial state. - * This clears all collected statements, values, and resets the index. - * It also re-enables the addition of the 'WHERE' keyword. - * @returns void - */ - public reset() { + * Resets the statement to its initial state. + * This clears all collected statements, values, and resets the index. + * It also re-enables the addition of the 'WHERE' keyword. + * @returns void + */ + public reset(): void { this.statements.value = []; this.parsedStatement = null; this.values.value = []; @@ -228,20 +228,20 @@ export default class Statement { } /** - * Adds a new SQL statement to the list of statements. - * It handles both string statements and nested Statement instances. - * It also manages the values associated with the statement and the logical kind (AND/OR). - * @param statement - The SQL statement to be added, either as a string or a Statement instance. - * @param values - The values corresponding to the placeholders in the statement. - * @param kind - The logical operator to combine this statement with previous ones ('AND' or 'OR'). - * @returns void - * @throws Error if the number of placeholders does not match the number of provided values. - */ + * Adds a new SQL statement to the list of statements. + * It handles both string statements and nested Statement instances. + * It also manages the values associated with the statement and the logical kind (AND/OR). + * @param statement - The SQL statement to be added, either as a string or a Statement instance. + * @param values - The values corresponding to the placeholders in the statement. + * @param kind - The logical operator to combine this statement with previous ones ('AND' or 'OR'). + * @returns void + * @throws Error if the number of placeholders does not match the number of provided values. + */ private addStatement( statement: string | Statement, values: any | any[] = [], - kind: StatementKind | string = 'AND' - ) { + kind: StatementKind | string = "AND", + ): void { if (statement instanceof Statement) { const raw = statement.returnRaw(); statement = raw.statement; @@ -250,8 +250,8 @@ export default class Statement { values = Array.isArray(values) ? values : [values]; - if(Statement.countPlaceholders(statement) !== values.length) { - throw new Error('Number of placeholders does not match number of values'); + if (Statement.countPlaceholders(statement) !== values.length) { + throw new Error("Number of placeholders does not match number of values"); } if (this.statements.value.length === 0) { @@ -263,255 +263,243 @@ export default class Statement { } /** - * Adds a new statement combined with 'AND'. - * @param statement - The SQL statement to be added, either as a string or a Statement instance. - * @param values - The values corresponding to the placeholders in the statement. - * @returns The current Statement instance for method chaining. - */ - public and( - statement: string | Statement, - values: any | any[] = [] - ) { - this.addStatement(statement, values, 'AND'); + * Adds a new statement combined with 'AND'. + * @param statement - The SQL statement to be added, either as a string or a Statement instance. + * @param values - The values corresponding to the placeholders in the statement. + * @returns The current Statement instance for method chaining. + */ + public and(statement: string | Statement, values: any | any[] = []): this { + this.addStatement(statement, values, "AND"); return this; } /** - * Adds a new statement combined with 'OR'. - * @param statement - The SQL statement to be added, either as a string or a Statement instance. - * @param values - The values corresponding to the placeholders in the statement. - * @returns The current Statement instance for method chaining. - */ - public or( - statement: string | Statement, - values: any | any[] = [] - ) { - this.addStatement(statement, values, 'OR'); + * Adds a new statement combined with 'OR'. + * @param statement - The SQL statement to be added, either as a string or a Statement instance. + * @param values - The values corresponding to the placeholders in the statement. + * @returns The current Statement instance for method chaining. + */ + public or(statement: string | Statement, values: any | any[] = []): this { + this.addStatement(statement, values, "OR"); return this; } /** - * Adds an IN condition to the statement. - * @param column - The database column to compare. - * @param values - The array of values for the IN condition. - * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). - * @returns The current Statement instance for method chaining. - */ - public in( - column: string, - values: any[], - kind: StatementKind = 'AND' - ) { - const placeholders = values.map(() => '?').join(', '); + * Adds an IN condition to the statement. + * @param column - The database column to compare. + * @param values - The array of values for the IN condition. + * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). + * @returns The current Statement instance for method chaining. + */ + public in(column: string, values: any[], kind: StatementKind = "AND"): this { + const placeholders = values.map(() => "?").join(", "); this.addStatement(`${column} IN (${placeholders})`, values, kind); return this; } /** - * Adds a NOT IN condition to the statement. - * @param column - The database column to compare. - * @param values - The array of values for the NOT IN condition. - * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). - * @returns The current Statement instance for method chaining. - */ - public notIn(column: string, values: any[], kind: StatementKind = 'AND') { - const placeholders = values.map(() => '?').join(', '); + * Adds a NOT IN condition to the statement. + * @param column - The database column to compare. + * @param values - The array of values for the NOT IN condition. + * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). + * @returns The current Statement instance for method chaining. + */ + public notIn( + column: string, + values: any[], + kind: StatementKind = "AND", + ): this { + const placeholders = values.map(() => "?").join(", "); this.addStatement(`${column} NOT IN (${placeholders})`, values, kind); return this; } /** - * Adds a BETWEEN condition to the statement. - * @param column - The database column to compare. - * @param start - The start value of the range. - * @param end - The end value of the range. - * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). - * @returns The current Statement instance for method chaining. - */ + * Adds a BETWEEN condition to the statement. + * @param column - The database column to compare. + * @param start - The start value of the range. + * @param end - The end value of the range. + * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). + * @returns The current Statement instance for method chaining. + */ public between( column: string, start: any, end: any, - kind: StatementKind = 'AND' - ) { + kind: StatementKind = "AND", + ): this { this.addStatement(`${column} BETWEEN ? AND ?`, [start, end], kind); return this; } /** - * Adds a NOT BETWEEN condition to the statement. - * @param column - The database column to compare. - * @param start - The start value of the range. - * @param end - The end value of the range. - * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). - * @returns The current Statement instance for method chaining. - */ + * Adds a NOT BETWEEN condition to the statement. + * @param column - The database column to compare. + * @param start - The start value of the range. + * @param end - The end value of the range. + * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). + * @returns The current Statement instance for method chaining. + */ public notBetween( column: string, start: any, end: any, - kind: StatementKind = 'AND' - ) { + kind: StatementKind = "AND", + ): this { this.addStatement(`${column} NOT BETWEEN ? AND ?`, [start, end], kind); return this; } /** - * Adds an IS NULL condition to the statement. - * @param column - The database column to check for NULL. - * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). - * @returns The current Statement instance for method chaining. - */ - public isNull( - column: string, - kind: StatementKind = 'AND' - ) { + * Adds an IS NULL condition to the statement. + * @param column - The database column to check for NULL. + * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). + * @returns The current Statement instance for method chaining. + */ + public isNull(column: string, kind: StatementKind = "AND"): this { this.addStatement(`${column} IS NULL`, [], kind); return this; } /** - * Adds an IS NOT NULL condition to the statement. - * @param column - The database column to check for NOT NULL. - * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). - * @returns The current Statement instance for method chaining. - */ - public isNotNull( - column: string, - kind: StatementKind = 'AND' - ) { + * Adds an IS NOT NULL condition to the statement. + * @param column - The database column to check for NOT NULL. + * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). + * @returns The current Statement instance for method chaining. + */ + public isNotNull(column: string, kind: StatementKind = "AND"): this { this.addStatement(`${column} IS NOT NULL`, [], kind); return this; } /** - * Adds a case-sensitive LIKE condition to the statement. - * @param column - The database column to compare. - * @param pattern - The pattern to match using LIKE. - * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). - * @returns The current Statement instance for method chaining. - */ + * Adds a case-sensitive LIKE condition to the statement. + * @param column - The database column to compare. + * @param pattern - The pattern to match using LIKE. + * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). + * @returns The current Statement instance for method chaining. + */ public like( column: string, pattern: string, - kind: StatementKind = 'AND' - ) { + kind: StatementKind = "AND", + ): this { this.addStatement(`${column} LIKE ?`, [pattern], kind); return this; } /** - * Adds a case-insensitive LIKE condition to the statement. - * @param column - The database column to compare. - * @param pattern - The pattern to match using ILIKE. - * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). - * @returns The current Statement instance for method chaining. - */ + * Adds a case-insensitive LIKE condition to the statement. + * @param column - The database column to compare. + * @param pattern - The pattern to match using ILIKE. + * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). + * @returns The current Statement instance for method chaining. + */ public ilike( column: string, pattern: string, - kind: StatementKind = 'AND' - ) { + kind: StatementKind = "AND", + ): this { this.addStatement(`${column} ILIKE ?`, [pattern], kind); return this; } /** - * Adds a NOT LIKE condition to the statement. - * @param column - The database column to compare. - * @param pattern - The pattern to match using NOT LIKE. - * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). - */ + * Adds a NOT LIKE condition to the statement. + * @param column - The database column to compare. + * @param pattern - The pattern to match using NOT LIKE. + * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). + */ public notLike( column: string, pattern: string, - kind: StatementKind = 'AND' - ) { + kind: StatementKind = "AND", + ): this { this.addStatement(`${column} NOT LIKE ?`, [pattern], kind); return this; } /** - * Adds a case-insensitive NOT LIKE condition to the statement. - * @param column - The database column to compare. - * @param pattern - The pattern to match using NOT ILIKE. - * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). - * @returns The current Statement instance for method chaining. - */ + * Adds a case-insensitive NOT LIKE condition to the statement. + * @param column - The database column to compare. + * @param pattern - The pattern to match using NOT ILIKE. + * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). + * @returns The current Statement instance for method chaining. + */ public notIlike( column: string, pattern: string, - kind: StatementKind = 'AND' - ) { + kind: StatementKind = "AND", + ): this { this.addStatement(`${column} NOT ILIKE ?`, [pattern], kind); return this; } /** - * Adds an EXISTS condition with a subquery to the statement. - * @param subquery - The subquery to be used in the EXISTS condition. - * @param values - The values corresponding to the placeholders in the subquery. - * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). - * @returns The current Statement instance for method chaining. - */ + * Adds an EXISTS condition with a subquery to the statement. + * @param subquery - The subquery to be used in the EXISTS condition. + * @param values - The values corresponding to the placeholders in the subquery. + * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). + * @returns The current Statement instance for method chaining. + */ public exists( subquery: string, values: any | any[] = [], - kind: StatementKind = 'AND' - ) { + kind: StatementKind = "AND", + ): this { this.addStatement(`EXISTS (${subquery})`, values, kind); return this; } /** - * Adds a NOT EXISTS condition with a subquery to the statement. - * @param subquery - The subquery to be used in the NOT EXISTS condition. - * @param values - The values corresponding to the placeholders in the subquery. - * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). - * @returns The current Statement instance for method chaining. - */ + * Adds a NOT EXISTS condition with a subquery to the statement. + * @param subquery - The subquery to be used in the NOT EXISTS condition. + * @param values - The values corresponding to the placeholders in the subquery. + * @param kind - The logical operator to combine this condition with previous ones ('AND' or 'OR'). + * @returns The current Statement instance for method chaining. + */ public notExists( subquery: string, values: any | any[] = [], - kind: StatementKind = 'AND' - ) { + kind: StatementKind = "AND", + ): this { this.addStatement(`NOT EXISTS (${subquery})`, values, kind); return this; } /** - * Provides access to the SearchModule for advanced search capabilities. - * This allows for full-text search, tsvector search, word-by-word search, etc. - * Returns an instance of SearchModule linked to this Statement. - * @returns An instance of SearchModule for building search conditions. - */ + * Provides access to the SearchModule for advanced search capabilities. + * This allows for full-text search, tsvector search, word-by-word search, etc. + * Returns an instance of SearchModule linked to this Statement. + * @returns An instance of SearchModule for building search conditions. + */ public search(): SearchModule { return new SearchModule(this); } /** - * Adds a raw SQL statement with placeholders to the statement list. - * This allows for custom SQL conditions that may not be covered by the predefined methods. - * @param kind - The logical operator to combine this statement with previous ones ('AND' or 'OR'). - * @param template - The SQL template string containing '?' placeholders. - * @param values - The values corresponding to the placeholders in the template. - * @returns The current Statement instance for method chaining. - * @throws Error if the number of placeholders does not match the number of provided values. - */ + * Adds a raw SQL statement with placeholders to the statement list. + * This allows for custom SQL conditions that may not be covered by the predefined methods. + * @param kind - The logical operator to combine this statement with previous ones ('AND' or 'OR'). + * @param template - The SQL template string containing '?' placeholders. + * @param values - The values corresponding to the placeholders in the template. + * @returns The current Statement instance for method chaining. + * @throws Error if the number of placeholders does not match the number of provided values. + */ public raw( kind: StatementKind | string, template: string, ...values: any[] - ) { - const parts = template.split('?'); - let statement = ''; + ): this { + const parts = template.split("?"); + let statement = ""; if (parts.length !== values.length + 1) { - throw new Error('Number of placeholders does not match number of values'); + throw new Error("Number of placeholders does not match number of values"); } for (let i = 0; i < values.length; i++) { - statement += parts[i] + '?'; + statement += `${parts[i]}?`; } statement += parts[parts.length - 1]; @@ -520,16 +508,16 @@ export default class Statement { } /** - * Joins multiple Statement instances into the current statement. - * This allows for complex nested conditions to be built up from smaller parts. - * @param statements - An array of Statement instances to be joined. - * @param joinWith - The logical operator to combine these statements ('AND' or 'OR'). - * @returns The current Statement instance for method chaining. - */ + * Joins multiple Statement instances into the current statement. + * This allows for complex nested conditions to be built up from smaller parts. + * @param statements - An array of Statement instances to be joined. + * @param joinWith - The logical operator to combine these statements ('AND' or 'OR'). + * @returns The current Statement instance for method chaining. + */ public joinMultipleStatements( statements: Statement[], - joinWith: StatementKind = 'AND' - ) { + joinWith: StatementKind = "AND", + ): this { statements.forEach((stmt) => { const raw = stmt.returnRaw(); this.addStatement(raw.statement, raw.values.value, joinWith); @@ -539,63 +527,65 @@ export default class Statement { } /** - * Checks if the statements have already been parsed. - * This prevents re-parsing and ensures that the final SQL is only generated once. - * @returns True if the statements have been parsed, false otherwise. - */ - public isDone() { + * Checks if the statements have already been parsed. + * This prevents re-parsing and ensures that the final SQL is only generated once. + * @returns True if the statements have been parsed, false otherwise. + */ + public isDone(): boolean { return this.parsedStatement !== null; } /** - * Parses the collected statements into a single SQL string with placeholders. - * It also adds the 'WHERE' keyword if required. - * @param newLine - Whether to separate statements with new lines for readability (default is true). - * @returns The final parsed SQL statement as a string. - */ + * Parses the collected statements into a single SQL string with placeholders. + * It also adds the 'WHERE' keyword if required. + * @param newLine - Whether to separate statements with new lines for readability (default is true). + * @returns The final parsed SQL statement as a string. + */ private parseStatements(newLine = true) { if (this.isDone()) { return this.parsedStatement as string; } if (this.statements.value.length === 0) { - this.parsedStatement = ''; + this.parsedStatement = ""; return this.parsedStatement; } - let separator = newLine ? '\n ' : ' '; + const separator = newLine ? "\n " : " "; let index = this.index.value; - let statement = this.statements.value.join(separator).replace(/\?/g, () => `$${index++}`); + const statement = this.statements.value + .join(separator) + .replace(/\?/g, () => `$${index++}`); this.parsedStatement = this.addWhere ? `WHERE ${statement}` : statement; return this.parsedStatement; } /** - * Builds the final SQL statement and the corresponding values array. - * This is the method to call when the statement is complete and ready for execution. - * @param newLine - Whether to separate statements with new lines for readability (default is true). - * @returns An object containing the final SQL statement and the array of values. - */ - public build(newLine = true) { + * Builds the final SQL statement and the corresponding values array. + * This is the method to call when the statement is complete and ready for execution. + * @param newLine - Whether to separate statements with new lines for readability (default is true). + * @returns An object containing the final SQL statement and the array of values. + */ + public build(newLine = true): { statement: string; values: any[] } { return { statement: this.parseStatements(newLine), - values: this.values.value - } + values: this.values.value, + }; } /** - * Provides access to the values array. - * This is useful for retrieving the parameters to be used in the parameterized query execution. - * @returns The array of values corresponding to the placeholders in the SQL statement. - */ - public get params() { + * Provides access to the values array. + * This is useful for retrieving the parameters to be used in the parameterized query execution. + * @returns The array of values corresponding to the placeholders in the SQL statement. + */ + public get params(): any[] { return this.values.value; } /** - * Creates a deep copy of the current Statement instance. - * This is useful when you want to duplicate the statement without affecting the original. - * @returns A new Statement instance that is a clone of the current one. - */ + * Creates a deep copy of the current Statement instance. + * This is useful when you want to duplicate the statement without affecting the original. + * @returns A new Statement instance that is a clone of the current one. + */ public clone(): Statement { const clone = new Statement(this.index.value - 1, this.reparseOnChange); clone.statements.value = [...this.statements.value]; @@ -608,15 +598,15 @@ export default class Statement { } /** - * Returns the raw SQL statement and values without parsing. - * This is useful for debugging or when the raw format is needed. - * @returns An object containing the raw SQL statement and the array of values. - */ + * Returns the raw SQL statement and values without parsing. + * This is useful for debugging or when the raw format is needed. + * @returns An object containing the raw SQL statement and the array of values. + */ private returnRaw() { - const statement = this.statements.value.join('\n '); + const statement = this.statements.value.join("\n "); return { statement: statement, - values: this.values - } + values: this.values, + }; } } diff --git a/src/types/ColumnTypes.ts b/src/types/ColumnTypes.ts index fc60620..debff87 100644 --- a/src/types/ColumnTypes.ts +++ b/src/types/ColumnTypes.ts @@ -1,9 +1,9 @@ import { ColumnType } from "../queryUtils/Column.js"; /** - * Contain all the possible column types in PostgreSQL - * Reference: https://www.postgresql.org/docs/current/datatype.html - */ + * Contain all the possible column types in PostgreSQL + * Reference: https://www.postgresql.org/docs/current/datatype.html + */ export enum ColumnTypesEnum { // Numeric Types SMALLINT = "smallint", @@ -96,86 +96,79 @@ export enum ColumnTypesEnum { REGOPER = "regoper", REGOPERATOR = "regoperator", REGCLASS = "regclass", - REGTYPE = "regtype" + REGTYPE = "regtype", } /** - * Union type for ColumnTypesEnum keys - */ + * Union type for ColumnTypesEnum keys + */ export type ColumnTypesUnion = keyof typeof ColumnTypesEnum; /** - * Union type for ColumnTypesEnum values and keys - */ + * Union type for ColumnTypesEnum values and keys + */ export type ColumnTypes = ColumnTypesEnum | ColumnTypesUnion; /** - * Create a ColumnType of type VARCHAR with specified length - * @param length - The maximum length of the VARCHAR column - * @returns A ColumnType instance representing a VARCHAR column with the specified length - */ -function Varchar(length: number) { - return new ColumnType('VARCHAR', [length]); + * Create a ColumnType of type VARCHAR with specified length + * @param length - The maximum length of the VARCHAR column + * @returns A ColumnType instance representing a VARCHAR column with the specified length + */ +function Varchar(length: number): ColumnType { + return new ColumnType("VARCHAR", [length]); } /** - * Create a ColumnType of type NUMERIC with specified precision and optional scale - * @param precision - The total number of digits - * @param scale - The number of digits to the right of the decimal point (optional) - * @returns A ColumnType instance representing a NUMERIC column with the specified precision and scale - */ -function Numeric(precision: number, scale?: number) { + * Create a ColumnType of type NUMERIC with specified precision and optional scale + * @param precision - The total number of digits + * @param scale - The number of digits to the right of the decimal point (optional) + * @returns A ColumnType instance representing a NUMERIC column with the specified precision and scale + */ +function Numeric(precision: number, scale?: number): ColumnType { if (scale !== undefined) { - return new ColumnType('NUMERIC', [precision, scale]); + return new ColumnType("NUMERIC", [precision, scale]); } - return new ColumnType('NUMERIC', [precision]); + return new ColumnType("NUMERIC", [precision]); } /** - * Create a ColumnType of type DECIMAL with specified precision and optional scale - * @param precision - The total number of digits - * @param scale - The number of digits to the right of the decimal point (optional) - * @returns A ColumnType instance representing a DECIMAL column with the specified precision and scale - */ -function Decimal(precision: number, scale?: number) { + * Create a ColumnType of type DECIMAL with specified precision and optional scale + * @param precision - The total number of digits + * @param scale - The number of digits to the right of the decimal point (optional) + * @returns A ColumnType instance representing a DECIMAL column with the specified precision and scale + */ +function Decimal(precision: number, scale?: number): ColumnType { if (scale !== undefined) { - return new ColumnType('DECIMAL', [precision, scale]); + return new ColumnType("DECIMAL", [precision, scale]); } - return new ColumnType('DECIMAL', [precision]); + return new ColumnType("DECIMAL", [precision]); } /** - * Create a ColumnType of type CHAR with specified length - * @param length - The fixed length of the CHAR column - * @returns A ColumnType instance representing a CHAR column with the specified length - */ -function Char(length: number) { - return new ColumnType('CHAR', [length]); + * Create a ColumnType of type CHAR with specified length + * @param length - The fixed length of the CHAR column + * @returns A ColumnType instance representing a CHAR column with the specified length + */ +function Char(length: number): ColumnType { + return new ColumnType("CHAR", [length]); } /** - * Create a ColumnType of type VARBIT with specified length - * @param length - The maximum length of the VARBIT column - * @returns A ColumnType instance representing a VARBIT column with the specified length - */ -function VarBit(length: number) { - return new ColumnType('VARBIT', [length]); + * Create a ColumnType of type VARBIT with specified length + * @param length - The maximum length of the VARBIT column + * @returns A ColumnType instance representing a VARBIT column with the specified length + */ +function VarBit(length: number): ColumnType { + return new ColumnType("VARBIT", [length]); } /** - * Create a ColumnType of type BIT with specified length - * @param length - The fixed length of the BIT column - * @returns A ColumnType instance representing a BIT column with the specified length - */ -function Bit(length: number) { - return new ColumnType('BIT', [length]); + * Create a ColumnType of type BIT with specified length + * @param length - The fixed length of the BIT column + * @returns A ColumnType instance representing a BIT column with the specified length + */ +function Bit(length: number): ColumnType { + return new ColumnType("BIT", [length]); } -export { - Varchar, - Numeric, - Decimal, - Char, - VarBit, - Bit -} +export { Varchar, Numeric, Decimal, Char, VarBit, Bit }; diff --git a/src/types/ColumnValue.ts b/src/types/ColumnValue.ts index 5de8ce5..67f65b5 100644 --- a/src/types/ColumnValue.ts +++ b/src/types/ColumnValue.ts @@ -1,8 +1,7 @@ - /** - * ColumnValue interface represents a column and its associated value. - * It includes the column name and an optional value to be assigned to that column. - */ + * ColumnValue interface represents a column and its associated value. + * It includes the column name and an optional value to be assigned to that column. + */ export default interface ColumnValue { /** The name of the column. */ column: string; diff --git a/src/types/ForeignKey.ts b/src/types/ForeignKey.ts index 0a03da2..d51cdfe 100644 --- a/src/types/ForeignKey.ts +++ b/src/types/ForeignKey.ts @@ -1,9 +1,8 @@ - /** - * Represents a foreign key relationship in a database schema. - * It includes the referenced table and column, as well as optional actions - * to take on delete or update events. - */ + * Represents a foreign key relationship in a database schema. + * It includes the referenced table and column, as well as optional actions + * to take on delete or update events. + */ type ForeignKey = { table: string; column: string; @@ -12,6 +11,11 @@ type ForeignKey = { }; /** Possible actions for foreign key constraints. */ -export type Actions = 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | 'SET DEFAULT'; +export type Actions = + | "CASCADE" + | "SET NULL" + | "RESTRICT" + | "NO ACTION" + | "SET DEFAULT"; export default ForeignKey; diff --git a/src/types/Join.ts b/src/types/Join.ts index 4bec8a5..cbc0925 100644 --- a/src/types/Join.ts +++ b/src/types/Join.ts @@ -1,14 +1,14 @@ -import SelectQuery from "../queryKinds/dml/select.js"; -import Statement from "../statementMaker.js"; +import type SelectQuery from "../queryKinds/dml/select.js"; +import type Statement from "../statementMaker.js"; -type joinTypeBase = 'INNER' | 'LEFT' | 'RIGHT' | 'FULL'; +type joinTypeBase = "INNER" | "LEFT" | "RIGHT" | "FULL"; export type joinType = Lowercase | joinTypeBase; /** - * Join interface represents the base to a SQL JOIN clause. - * It includes the type of join, an optional alias, and the condition for the join. - */ + * Join interface represents the base to a SQL JOIN clause. + * It includes the type of join, an optional alias, and the condition for the join. + */ interface JoinBase { /** The type of join: INNER, LEFT, RIGHT, or FULL. */ type: joinType; @@ -19,9 +19,9 @@ interface JoinBase { } /** - * Join interface represents a SQL JOIN clause. - * It includes the type of join, the table to join, an optional alias, and the condition for the join. - */ + * Join interface represents a SQL JOIN clause. + * It includes the type of join, the table to join, an optional alias, and the condition for the join. + */ type JoinTable = JoinBase & { /** The name of the table to join. */ table: string; @@ -41,4 +41,3 @@ export function isJoinTable(join: Join): join is JoinTable { } export default Join; - diff --git a/src/types/OrderBy.ts b/src/types/OrderBy.ts index ab7e117..dc3df27 100644 --- a/src/types/OrderBy.ts +++ b/src/types/OrderBy.ts @@ -1,11 +1,10 @@ - -type DirectionBase = 'ASC' | 'DESC'; +type DirectionBase = "ASC" | "DESC"; export type Direction = Lowercase | DirectionBase; /** - * OrderByField interface represents sorting criteria for database queries or data collections. - * It includes the field to sort by and the direction of sorting (ascending or descending). - */ + * OrderByField interface represents sorting criteria for database queries or data collections. + * It includes the field to sort by and the direction of sorting (ascending or descending). + */ export interface OrderByField { /** The field to sort by. */ field: string; @@ -15,9 +14,9 @@ export interface OrderByField { } /** - * OrderByColumn interface represents sorting criteria for database queries or data collections. - * It includes the column to sort by and the direction of sorting (ascending or descending). - */ + * OrderByColumn interface represents sorting criteria for database queries or data collections. + * It includes the column to sort by and the direction of sorting (ascending or descending). + */ export interface OrderByColumn { /** The column to sort by. */ column: string; @@ -26,10 +25,10 @@ export interface OrderByColumn { field?: never; } -type OrderBy = OrderByField | OrderByColumn +type OrderBy = OrderByField | OrderByColumn; export function isOrderByField(obj: any): obj is OrderByField { - return obj && typeof obj === 'object' && 'field' in obj && 'direction' in obj; + return obj && typeof obj === "object" && "field" in obj && "direction" in obj; } export default OrderBy; diff --git a/src/types/QueryKind.ts b/src/types/QueryKind.ts index d890bee..cdbd52e 100644 --- a/src/types/QueryKind.ts +++ b/src/types/QueryKind.ts @@ -1,5 +1,3 @@ - - enum QueryKind { SELECT = "SELECT", INSERT = "INSERT", diff --git a/src/types/SetValue.ts b/src/types/SetValue.ts index 0f830bd..41f89a9 100644 --- a/src/types/SetValue.ts +++ b/src/types/SetValue.ts @@ -1,9 +1,8 @@ - /** - * Interface representing a value to be set in a database operation. - * It includes the column to be set, and either a direct value or a reference to another column. - * Only one of 'from' or 'value' should be provided. - */ + * Interface representing a value to be set in a database operation. + * It includes the column to be set, and either a direct value or a reference to another column. + * Only one of 'from' or 'value' should be provided. + */ export default interface SetValue { /** The column to be set. */ setColumn: string; diff --git a/src/types/UsingTable.ts b/src/types/UsingTable.ts index 90e458b..4b0947c 100644 --- a/src/types/UsingTable.ts +++ b/src/types/UsingTable.ts @@ -1,7 +1,6 @@ - /** - * Interface representing a table used in a SQL query with an optional alias. - */ + * Interface representing a table used in a SQL query with an optional alias. + */ export default interface UsingTable { /** The name of the table. */ table: string; diff --git a/src/types/index.ts b/src/types/index.ts index ecaad9e..83266a3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,16 +1,16 @@ -export type { default as ColumnValue } from './ColumnValue.js'; -export * from './Join.js'; -export type { default as Join } from './Join.js'; -export * from './OrderBy.js'; -export type { default as OrderBy } from './OrderBy.js'; -export * from './QueryKind.js'; -export type { default as QueryKind } from './QueryKind.js'; -export * from './SetValue.js'; -export type { default as SetValue } from './SetValue.js'; -export * from './sqlFlavor.js'; -export { default as sqlFlavor } from './sqlFlavor.js'; -export * from './UsingTable.js'; -export type { default as UsingTable } from './UsingTable.js'; -export * from './ColumnTypes.js'; -export type * from './ForeignKey.js'; -export type { default as ForeignKey } from './ForeignKey.js'; +export * from "./ColumnTypes.js"; +export type { default as ColumnValue } from "./ColumnValue.js"; +export type * from "./ForeignKey.js"; +export type { default as ForeignKey } from "./ForeignKey.js"; +export type { default as Join } from "./Join.js"; +export * from "./Join.js"; +export type { default as OrderBy } from "./OrderBy.js"; +export * from "./OrderBy.js"; +export type { default as QueryKind } from "./QueryKind.js"; +export * from "./QueryKind.js"; +export type { default as SetValue } from "./SetValue.js"; +export * from "./SetValue.js"; +export * from "./sqlFlavor.js"; +export { default as sqlFlavor } from "./sqlFlavor.js"; +export type { default as UsingTable } from "./UsingTable.js"; +export * from "./UsingTable.js"; diff --git a/src/types/sqlFlavor.ts b/src/types/sqlFlavor.ts index 38640fc..382f4bf 100644 --- a/src/types/sqlFlavor.ts +++ b/src/types/sqlFlavor.ts @@ -1,15 +1,14 @@ - /** - * Enumeration of supported SQL flavors. - * This enum helps in identifying the SQL dialect being used, - * which can affect query syntax and features. - */ + * Enumeration of supported SQL flavors. + * This enum helps in identifying the SQL dialect being used, + * which can affect query syntax and features. + */ enum sqlFlavor { postgres = "postgres", mysql = "mysql", sqlite = "sqlite", mssql = "mssql", - oracle = "oracle" + oracle = "oracle", } export default sqlFlavor; diff --git a/vitest.config.ts b/vitest.config.ts index 3dd1f83..e5ed32d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,21 +1,21 @@ -import { defineConfig } from 'vitest/config'; +import { defineConfig } from "vitest/config"; export default defineConfig({ test: { - environment: 'node', + environment: "node", coverage: { - reporter: ['json', 'html', 'text'], + reporter: ["json", "html", "text"], exclude: [ - 'node_modules', - 'dist', - '**/*.test.ts', - '**/*.spec.ts', - 'vitest.config.ts', - '**/getOptionalPackages.ts', - '**/index.ts', + "node_modules", + "dist", + "**/*.test.ts", + "**/*.spec.ts", + "vitest.config.ts", + "**/getOptionalPackages.ts", + "**/index.ts", ], - include: ['src/**/*.ts'] + include: ["src/**/*.ts"], }, - retry: 3 - } -}) + retry: 3, + }, +}); From 8566fd75b42981e86d3ee7649400e499bb2cbed9 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 6 Oct 2025 00:20:15 -0300 Subject: [PATCH 50/74] feat/need-testing/added jsr support --- .github/workflows/publish-npm.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 89a4211..5360208 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -96,6 +96,7 @@ jobs: - name: Update package version run: | pnpm version ${{ steps.version.outputs.version }} --no-git-tag-version --allow-same-version + ./scripts/update-jsr-version.sh ${{ steps.version.outputs.version }} - name: Create distribution archive run: | @@ -105,12 +106,21 @@ jobs: # Create a zip archive as well for Windows users zip -r dist-${{ steps.version.outputs.version }}.zip dist/ + # Create a compressed archive of the dist-qjs directory + tar -czf dist-qjs-${{ steps.version.outputs.version }}.tar.gz dist-qjs/ + + # Create a zip archive of the dist-qjs directory for Windows users + zip -r dist-qjs-${{ steps.version.outputs.version }}.zip dist-qjs + - name: Publish to NPM run: | pnpm publish --tag ${{ steps.version.outputs.npm_tag }} --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Publish to JSR + run: pnpx jsr publish --allow-dirty + - name: Create GitHub Release (main branch only) if: github.ref == 'refs/heads/main' uses: ncipollo/release-action@v1 From 82c86d8a9cda21fc1dc5735c999c9638b8661d35 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 6 Oct 2025 00:20:53 -0300 Subject: [PATCH 51/74] Removed ts-pattern as dependency From d4d9bca9403e500450d71d07f150249e0e51d310 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 6 Oct 2025 00:24:12 -0300 Subject: [PATCH 52/74] fix/added forgotten archives into releases --- .github/workflows/publish-npm.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 5360208..8ffda35 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -131,6 +131,8 @@ jobs: artifacts: | dist-${{ steps.version.outputs.version }}.tar.gz dist-${{ steps.version.outputs.version }}.zip + dist-qjs-${{ steps.version.outputs.version }}.tar.gz + dist-qjs-${{ steps.version.outputs.version }}.zip body: | ## Changes @@ -160,6 +162,8 @@ jobs: artifacts: | dist-${{ steps.version.outputs.version }}.tar.gz dist-${{ steps.version.outputs.version }}.zip + dist-qjs-${{ steps.version.outputs.version }}.tar.gz + dist-qjs-${{ steps.version.outputs.version }}.zip body: | ## ${{ steps.version.outputs.branch }} Release From c31dc06617e051b0690e59a8c5a7f6270d0b1b85 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 6 Oct 2025 00:48:31 -0300 Subject: [PATCH 53/74] feat/added safety guard so publishing the same version number doesn't happen --- .github/workflows/publish-npm.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 8ffda35..cededc9 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -63,6 +63,13 @@ jobs: "main") NPM_TAG="latest" VERSION=$PACKAGE_VERSION + + # Find the latest tag matching this package and error if it already exists + LAST_TAG=$(git tag --list "v${PACKAGE_VERSION}" | sort -V | tail -n1) + if [ "$LAST_TAG" == "v${PACKAGE_VERSION}" ]; then + echo "Version $PACKAGE_VERSION already exists. Please update the version in package.json and jsr.json before publishing." + exit 1 + fi ;; "rc"|"beta"|"next") IDENTIFIER=$BRANCH_NAME From e8e47398d4fec9f4d8a135799726ad6c53040c39 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 7 Oct 2025 14:45:50 -0300 Subject: [PATCH 54/74] feat/added the rest of the tests needed for CreateTableQuery --- src/queryKinds/ddl/table/Create.test.ts | 65 +++++++++++++++++++++++++ src/queryKinds/ddl/table/Create.ts | 7 +-- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/queryKinds/ddl/table/Create.test.ts b/src/queryKinds/ddl/table/Create.test.ts index a385c69..743db36 100644 --- a/src/queryKinds/ddl/table/Create.test.ts +++ b/src/queryKinds/ddl/table/Create.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import CreateTableQuery from "./Create.js"; import Column from "../../../queryUtils/Column.js"; import { Decimal, Varchar } from "../../../types/ColumnTypes.js"; +import QueryKind from "../../../types/QueryKind.js"; describe('Create Table Query', () => { @@ -101,4 +102,68 @@ describe('Create Table Query', () => { const query = new CreateTableQuery('empty_table'); expect(() => query.build()).toThrow('No columns defined for the table.'); }); + + it('should reset the query state', () => { + const query = new CreateTableQuery('temp_table') + .ifNotExists() + .addColumns([ + Column('id', 'INT').primaryKey().notNull() + ]); + + query.reset(); + + expect(() => query.build()).toThrow('Table name is not set.'); + }); + + it('should return toSQL correctly', () => { + const query = new CreateTableQuery('logs') + .addColumns(Column('id', 'INT').primaryKey().notNull()) + .addColumns(Column('message', Varchar(255)).notNull()); + + expect(query.toSQL()).toBe( + 'CREATE TABLE "logs" (\n id INT PRIMARY KEY,\n message VARCHAR(255) NOT NULL\n);' + ); + }); + + it('should be able to handle setting columns', () => { + const query = new CreateTableQuery('employees') + .setColumns([ + Column('id', 'INT').primaryKey().notNull(), + Column('first_name', Varchar(50)).notNull(), + Column('last_name', Varchar(50)).notNull() + ]); + + expect(query.build()).toBe( + 'CREATE TABLE "employees" (\n id INT PRIMARY KEY,\n first_name VARCHAR(50) NOT NULL,\n last_name VARCHAR(50) NOT NULL\n);' + ); + }); + + it('should be able to return its columns array', () => { + const columns = [ + Column('id', 'INT').primaryKey().notNull(), + Column('username', Varchar(50)).notNull() + ]; + const query = new CreateTableQuery('accounts') + .setColumns(columns); + + expect(query.columns).toEqual(columns); + }); + + it('should be able to set the table name later', () => { + const query = new CreateTableQuery() + .table('departments') + .addColumns([ + Column('id', 'INT').primaryKey().notNull(), + Column('dept_name', Varchar(100)).notNull() + ]); + + expect(query.build()).toBe( + 'CREATE TABLE "departments" (\n id INT PRIMARY KEY,\n dept_name VARCHAR(100) NOT NULL\n);' + ); + }); + + it('should return its kind', () => { + const query = new CreateTableQuery('audit'); + expect(query.kind).toBe(QueryKind.CREATE_TABLE); + }); }); diff --git a/src/queryKinds/ddl/table/Create.ts b/src/queryKinds/ddl/table/Create.ts index dea910e..9f712f2 100644 --- a/src/queryKinds/ddl/table/Create.ts +++ b/src/queryKinds/ddl/table/Create.ts @@ -17,7 +17,7 @@ export default class CreateTableQuery extends TableQueryDefinition { this.tableName = tableName ? SqlEscaper.escapeTableName(tableName, this.flavor) : ""; - this.tableColumns = columns ?? []; + this.tableColumns = columns; } /** @@ -98,7 +98,7 @@ export default class CreateTableQuery extends TableQueryDefinition { this.tableName = ""; this.tableColumns = []; this.builtQuery = null; - this.ifNotExistsFlag = false; + this.resetIfNotExists(); return this; } @@ -107,7 +107,8 @@ export default class CreateTableQuery extends TableQueryDefinition { * @returns The SQL string representation of the CREATE TABLE query. */ public toSQL(): string { - return this.build(); + if(!this.builtQuery) this.build(); + return this.builtQuery as string; } /** From af8caacb54a40accb8e3cc1863771a924d362c32 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 7 Oct 2025 14:46:22 -0300 Subject: [PATCH 55/74] feat/added tests for DropTableQuery --- src/queryKinds/ddl/table/Drop.test.ts | 48 +++++++++++++++++++++++++++ src/queryKinds/ddl/table/Drop.ts | 13 ++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/queryKinds/ddl/table/Drop.test.ts diff --git a/src/queryKinds/ddl/table/Drop.test.ts b/src/queryKinds/ddl/table/Drop.test.ts new file mode 100644 index 0000000..79875cb --- /dev/null +++ b/src/queryKinds/ddl/table/Drop.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from "vitest"; +import DropTableQuery from "./Drop.js"; +import QueryKind from "../../../types/QueryKind.js"; + +describe('Drop Table Query', () => { + it('should create a basic DROP TABLE query', () => { + const query = new DropTableQuery('users').build(); + expect(query).toBe('DROP TABLE "users";'); + }); + + it('should be able to set table name later', () => { + const query = new DropTableQuery().table('products').build(); + expect(query).toBe('DROP TABLE "products";'); + }); + + it('should throw if table name is not provided', () => { + const query = new DropTableQuery(); + expect(() => query.build()).toThrow('Table name is required to build DROP TABLE query.'); + }); + + it('should create a DROP TABLE query with IF EXISTS', () => { + const query = new DropTableQuery('users').ifExists().build(); + expect(query).toBe('DROP TABLE IF EXISTS "users";'); + }); + + it('should clone the DropTableQuery instance', () => { + const original = new DropTableQuery('orders').ifExists(); + const cloned = original.clone(); + expect(cloned).not.toBe(original); + expect(cloned.build()).toBe(original.build()); + }); + + it('should reset the DropTableQuery instance', () => { + const query = new DropTableQuery('customers').ifExists(); + query.reset(); + expect(() => query.build()).toThrow('Table name is required to build DROP TABLE query.'); + }); + + it('should return the correct SQL string representation', () => { + const query = new DropTableQuery('employees').ifExists(); + expect(query.toSQL()).toBe('DROP TABLE IF EXISTS "employees";'); + }); + + it('should return kind as DROP_TABLE', () => { + const query = new DropTableQuery('departments'); + expect(query.kind).toBe(QueryKind.DROP_TABLE); + }); +}); diff --git a/src/queryKinds/ddl/table/Drop.ts b/src/queryKinds/ddl/table/Drop.ts index fe29cbd..f1a13ff 100644 --- a/src/queryKinds/ddl/table/Drop.ts +++ b/src/queryKinds/ddl/table/Drop.ts @@ -15,6 +15,11 @@ export default class DropTableQuery extends TableQueryDefinition { : ""; } + public ifExists(): this { + this.ifNotExistsFlag = true; + return this; + } + /** * Builds the DROP TABLE SQL query string. * @param _deepAnalysis - Optional boolean to indicate if deep analysis is required (default is false). @@ -39,7 +44,8 @@ export default class DropTableQuery extends TableQueryDefinition { * @returns A new DropTableQuery instance with the same properties as the current instance. */ public clone(): DropTableQuery { - const cloned = new DropTableQuery(this.tableName); + const cloned = new DropTableQuery(); + cloned.tableName = this.tableName; cloned.flavor = this.flavor; cloned.ifNotExistsFlag = this.ifNotExistsFlag; return cloned; @@ -53,7 +59,7 @@ export default class DropTableQuery extends TableQueryDefinition { public reset(): this { this.tableName = ""; this.builtQuery = null; - this.ifNotExistsFlag = false; + this.resetIfNotExists(); return this; } @@ -62,7 +68,8 @@ export default class DropTableQuery extends TableQueryDefinition { * @returns The SQL string representation of the DROP TABLE query. */ public toSQL(): string { - return this.build(); + if(!this.builtQuery) this.build(); + return this.builtQuery as string; } /** Getter for the kind of query. */ From 44634ed56ab694b9b34fbd6e6bd47c57d0d52325 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 7 Oct 2025 14:46:42 -0300 Subject: [PATCH 56/74] ongoing/feat/started adding tests for AlterTableQuery --- src/queryKinds/ddl/table/Alter.test.ts | 50 ++++++++++++++++++++++++++ src/queryKinds/ddl/table/Alter.ts | 8 ++--- 2 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 src/queryKinds/ddl/table/Alter.test.ts diff --git a/src/queryKinds/ddl/table/Alter.test.ts b/src/queryKinds/ddl/table/Alter.test.ts new file mode 100644 index 0000000..07e5284 --- /dev/null +++ b/src/queryKinds/ddl/table/Alter.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from "vitest"; +import AlterTableQuery from "./Alter.js"; +import Column from "../../../queryUtils/Column.js"; + + +describe('Alter Table Query', () => { + it('should create a basic ALTER TABLE query to add a column', () => { + const query = new AlterTableQuery('users') + .addColumnsToAdd(Column('id', 'INT').primaryKey().notNull()) + .build(); + + expect(query).toEqual([ + 'ALTER TABLE "users" ADD COLUMN id INT;', + 'ALTER TABLE "users" ALTER COLUMN id SET NOT NULL;', + 'ALTER TABLE "users" ADD CONSTRAINT id_pkey PRIMARY KEY (id);', + ]); + }); + + it('should allow to set table name later', () => { + const query = new AlterTableQuery() + .table('products') + .addColumnsToAdd([ + Column('product_id', 'INT').primaryKey().notNull(), + ]) + .build(); + + expect(query).toEqual([ + 'ALTER TABLE "products" ADD COLUMN product_id INT;', + 'ALTER TABLE "products" ALTER COLUMN product_id SET NOT NULL;', + 'ALTER TABLE "products" ADD CONSTRAINT product_id_pkey PRIMARY KEY (product_id);', + ]); + }); + + it('should allow to set columns to add', () => { + const query = new AlterTableQuery('orders') + .setColumnsToAdd([ + Column('order_id', 'INT').primaryKey().notNull(), + Column('order_date', 'DATE').notNull(), + ]) + .build(); + + expect(query).toEqual([ + 'ALTER TABLE "orders" ADD COLUMN order_id INT;', + 'ALTER TABLE "orders" ALTER COLUMN order_id SET NOT NULL;', + 'ALTER TABLE "orders" ADD CONSTRAINT order_id_pkey PRIMARY KEY (order_id);', + 'ALTER TABLE "orders" ADD COLUMN order_date DATE;', + 'ALTER TABLE "orders" ALTER COLUMN order_date SET NOT NULL;', + ]); + }); +}); diff --git a/src/queryKinds/ddl/table/Alter.ts b/src/queryKinds/ddl/table/Alter.ts index 0574657..442cc3f 100644 --- a/src/queryKinds/ddl/table/Alter.ts +++ b/src/queryKinds/ddl/table/Alter.ts @@ -199,13 +199,9 @@ export default class AlterTableQuery extends TableQueryDefinition { * @returns The SQL string representation of the ALTER TABLE query. * @throws Error if the query has not been built yet. */ - public toSQL(): string | string[] { + public toSQL(): string[] { if (this.builtQuery) this.build(); - if (!this.builtQuery) - throw new Error( - "No built query available. Please build the query first.", - ); - return this.builtQuery; + return this.builtQuery as string[]; } /** From 31f83289fbb49112f9509be8f8e5b7f4f76c5a1a Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 7 Oct 2025 15:11:35 -0300 Subject: [PATCH 57/74] feat/added return all and returningRaw to all queries that support the RETURNING keyword --- src/queryKinds/dml/delete.test.ts | 10 +++++ src/queryKinds/dml/delete.ts | 57 ++++++++++++++++++++++++---- src/queryKinds/dml/insert.test.ts | 10 +++++ src/queryKinds/dml/insert.ts | 56 +++++++++++++++++++++++---- src/queryKinds/dml/update.test.ts | 11 ++++++ src/queryKinds/dml/update.ts | 63 ++++++++++++++++++++++++++----- 6 files changed, 181 insertions(+), 26 deletions(-) diff --git a/src/queryKinds/dml/delete.test.ts b/src/queryKinds/dml/delete.test.ts index a7e7dd0..2f2246e 100644 --- a/src/queryKinds/dml/delete.test.ts +++ b/src/queryKinds/dml/delete.test.ts @@ -291,4 +291,14 @@ describe('Delete Query', () => { expect(query.getParams()).toEqual([1]); expect(query.toSQL()).toBe('DELETE FROM "users" AS u\n WHERE (u.id = $1)\n RETURNING "id", "email", "biscuit"'); }); + + it('should support returning all with returnAllFields', () => { + const query = new DeleteQuery('users', 'u') + .where('u.id = ?', 1) + .returnAllFields() + .build(); + + expect(query.text).toBe('DELETE FROM "users" AS u\n WHERE (u.id = $1)\n RETURNING *'); + expect(query.values).toEqual([1]); + }); }); diff --git a/src/queryKinds/dml/delete.ts b/src/queryKinds/dml/delete.ts index a36d60c..e9c921a 100644 --- a/src/queryKinds/dml/delete.ts +++ b/src/queryKinds/dml/delete.ts @@ -20,6 +20,8 @@ export default class DeleteQuery extends DmlQueryDefinition { private usingTables: UsingTable[] = []; /** The fields to be returned after the delete operation. */ private returningFields: string[] = []; + /** Flag indicating whether to return all fields. */ + private returnAll: boolean = false; /** * Creates an instance of DeleteQuery. @@ -120,6 +122,15 @@ export default class DeleteQuery extends DmlQueryDefinition { return this.where(newStmt); } + /** + * Specifies that all fields should be returned after the delete operation. + * @returns The current DeleteQuery instance for method chaining. + */ + public returnAllFields(): this { + this.returnAll = true; + return this; + } + /** * Specifies the fields to be returned after the delete operation. * Accepts a string or an array of strings. @@ -127,11 +138,9 @@ export default class DeleteQuery extends DmlQueryDefinition { * @returns The current DeleteQuery instance for method chaining. */ public returning(fields: string | string[]): this { - if (Array.isArray(fields)) { - this.returningFields = SqlEscaper.escapeSelectIdentifiers(fields, this.flavor); - } else { - this.returningFields = SqlEscaper.escapeSelectIdentifiers([fields], this.flavor); - } + this.returningRaw(SqlEscaper.escapeSelectIdentifiers( + Array.isArray(fields) ? fields : [fields], this.flavor + )); return this; } @@ -142,10 +151,40 @@ export default class DeleteQuery extends DmlQueryDefinition { * @returns The current DeleteQuery instance for method chaining. */ public addReturning(fields: string | string[]): this { + this.addReturningRaw(SqlEscaper.escapeSelectIdentifiers( + Array.isArray(fields) ? fields : [fields], this.flavor + )); + return this; + } + + /** + * Specifies raw fields to be returned after the delete operation without any escaping. + * Accepts a string or an array of strings. + * @param fields - The raw field(s) to be returned. + * @returns The current DeleteQuery instance for method chaining. + */ + public returningRaw(fields: string | string[]): this { + this.returnAll = false; if (Array.isArray(fields)) { - this.returningFields.push(...SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); + this.returningFields = fields; + } else { + this.returningFields = [fields]; + } + return this; + } + + /** + * Adds raw fields to the existing RETURNING clause without any escaping. + * Accepts a string or an array of strings. + * @param field - The raw field(s) to be added to the RETURNING clause. + * @returns The current DeleteQuery instance for method chaining. + */ + public addReturningRaw(field: string | string[]): this { + this.returnAll = false; + if (Array.isArray(field)) { + this.returningFields.push(...field); } else { - this.returningFields.push(...SqlEscaper.escapeSelectIdentifiers([fields], this.flavor)); + this.returningFields.push(field); } return this; } @@ -164,6 +203,7 @@ export default class DeleteQuery extends DmlQueryDefinition { cloned.whereStatement = this.whereStatement ? this.whereStatement.clone() : null; cloned.returningFields = [...this.returningFields]; cloned.ctes = this.ctes ? new CteMaker(...this.ctes['ctes']) : null; + cloned.returnAll = this.returnAll; return cloned; } @@ -181,6 +221,7 @@ export default class DeleteQuery extends DmlQueryDefinition { this.returningFields = []; this.ctes = null; this.builtQuery = null; + this.returnAll = false; } /** @@ -256,7 +297,7 @@ export default class DeleteQuery extends DmlQueryDefinition { deleteClause, usingClause, whereClause, - returningClause + returningClause || (this.returnAll ? 'RETURNING *' : '') ].filter(part => part !== '') .join('\n '); diff --git a/src/queryKinds/dml/insert.test.ts b/src/queryKinds/dml/insert.test.ts index bf20c24..78e6c17 100644 --- a/src/queryKinds/dml/insert.test.ts +++ b/src/queryKinds/dml/insert.test.ts @@ -197,4 +197,14 @@ describe('Insert Query', () => { expect(secondBuild.values).toEqual(['2024-01-01']); }); + it('should support returning all with returnAllFields', () => { + const query = new InsertQuery('users') + .values({ name: 'Alice', age: 28 }) + .returnAllFields() + .build(); + + expect(query.text).toBe('INSERT INTO "users" ("name", "age") VALUES ($1, $2)\nRETURNING *'); + expect(query.values).toEqual(['Alice', 28]); + }); + }); diff --git a/src/queryKinds/dml/insert.ts b/src/queryKinds/dml/insert.ts index fdcab75..5cde882 100644 --- a/src/queryKinds/dml/insert.ts +++ b/src/queryKinds/dml/insert.ts @@ -19,6 +19,8 @@ export default class InsertQuery extends DmlQueryDefinition { private selectQuery: SelectQuery | null = null; /** The fields to be returned after the insert operation. */ private returningFields: string[] = []; + /** Flag indicating whether the query returns all fields. */ + private returnAll: boolean = false; /** * Creates an instance of InsertQuery. @@ -104,17 +106,25 @@ export default class InsertQuery extends DmlQueryDefinition { return this; } + /** + * Indicates that all fields should be returned after the insert operation. + * This is equivalent to using RETURNING * in SQL. + * @returns The current InsertQuery instance for method chaining. + */ + public returnAllFields(): this { + this.returnAll = true; + return this; + } + /** * Specifies the fields to be returned after the insert operation. * @param fields - A single field or an array of fields to be returned. * @returns The current InsertQuery instance for method chaining. */ public returning(fields: string | string[]): this { - if (Array.isArray(fields)) { - this.returningFields = SqlEscaper.escapeSelectIdentifiers(fields, this.flavor); - } else { - this.returningFields = SqlEscaper.escapeSelectIdentifiers([fields], this.flavor); - } + this.returningRaw(SqlEscaper.escapeSelectIdentifiers( + Array.isArray(fields) ? fields : [fields], this.flavor + )); return this; } @@ -124,10 +134,38 @@ export default class InsertQuery extends DmlQueryDefinition { * @returns The current InsertQuery instance for method chaining. */ public addReturning(fields: string | string[]): this { + this.addReturningRaw(SqlEscaper.escapeSelectIdentifiers( + Array.isArray(fields) ? fields : [fields], this.flavor + )); + return this; + } + + /** + * Specifies raw fields to be returned after the insert operation without escaping. + * @param fields - A single field or an array of fields to be returned. + * @returns The current InsertQuery instance for method chaining. + */ + public returningRaw(fields: string | string[]): this { + this.returnAll = false; if (Array.isArray(fields)) { - this.returningFields.push(...SqlEscaper.escapeSelectIdentifiers(fields, this.flavor)); + this.returningFields = fields; + } else { + this.returningFields = [fields]; + } + return this; + } + + /** + * Adds raw fields to the existing RETURNING clause without escaping. + * @param field - A single field or an array of fields to be added to the RETURNING clause. + * @returns The current InsertQuery instance for method chaining. + */ + public addReturningRaw(field: string | string[]): this { + this.returnAll = false; + if (Array.isArray(field)) { + this.returningFields.push(...field); } else { - this.returningFields.push(...SqlEscaper.escapeSelectIdentifiers([fields], this.flavor)); + this.returningFields.push(field); } return this; } @@ -144,6 +182,7 @@ export default class InsertQuery extends DmlQueryDefinition { cloned.selectQuery = this.selectQuery ? this.selectQuery.clone() : null; cloned.returningFields = [...this.returningFields]; cloned.ctes = this.ctes ? new CteMaker(...this.ctes['ctes']) : null; + cloned.returnAll = this.returnAll; return cloned; } @@ -181,6 +220,7 @@ export default class InsertQuery extends DmlQueryDefinition { this.returningFields = []; this.builtQuery = null; this.ctes = null; + this.returnAll = false; } /** @@ -272,7 +312,7 @@ export default class InsertQuery extends DmlQueryDefinition { const text = [ ctesClause ? `${ctesClause} ` : '', insertClause, - returningClause + returningClause || (this.returnAll ? 'RETURNING *' : '') ].join('\n').trim(); this.builtQuery = text; diff --git a/src/queryKinds/dml/update.test.ts b/src/queryKinds/dml/update.test.ts index acd3c2b..7d9153a 100644 --- a/src/queryKinds/dml/update.test.ts +++ b/src/queryKinds/dml/update.test.ts @@ -491,4 +491,15 @@ describe('Update Query', () => { expect(query.values).toEqual([true, 'active', 'Engineering', 'TechCorp']); }); + it('should support returning all columns with returnAllFields', () => { + const query = new UpdateQuery('users') + .set({ name: 'Alice', age: 28 }) + .returnAllFields() + .where('id = ?', 3) + .build(); + + expect(query.text).toBe('UPDATE "users"\nSET "name" = $1, "age" = $2\nWHERE (id = $3)\nRETURNING *'); + expect(query.values).toEqual(['Alice', 28, 3]); + }); + }); diff --git a/src/queryKinds/dml/update.ts b/src/queryKinds/dml/update.ts index b140fc3..1480b02 100644 --- a/src/queryKinds/dml/update.ts +++ b/src/queryKinds/dml/update.ts @@ -28,7 +28,8 @@ export default class UpdateQuery extends DmlQueryDefinition { private setValues: SetValue[] = []; /** RETURNING fields. */ private returningFields: string[] = []; - + /** Flag to indicate that all fields should be returned. */ + private returnAll: boolean = false; /** * Creates an instance of UpdateQuery. * @param table - The name of the table to update. @@ -188,6 +189,16 @@ export default class UpdateQuery extends DmlQueryDefinition { return this.where(newStmt); } + /** + * Specifies that all fields should be returned after the update. + * This sets the RETURNING clause to '*'. + * @returns The current UpdateQuery instance for method chaining. + */ + public returnAllFields(): this { + this.returnAll = true; + return this; + } + /** * Specifies the RETURNING fields for the update. * Accepts either a single field name or an array of field names. @@ -195,11 +206,9 @@ export default class UpdateQuery extends DmlQueryDefinition { * @returns The current UpdateQuery instance for method chaining. */ public returning(fields: string | string[]): this { - if (Array.isArray(fields)) { - this.returningFields = SqlEscaper.escapeSelectIdentifiers(fields, this.flavor); - } else { - this.returningFields = SqlEscaper.escapeSelectIdentifiers([fields], this.flavor); - } + this.returningRaw(SqlEscaper.escapeSelectIdentifiers( + Array.isArray(fields) ? fields : [fields], this.flavor + )); return this; } @@ -209,11 +218,43 @@ export default class UpdateQuery extends DmlQueryDefinition { * @param field - The field(s) to be added to the RETURNING clause. * @returns The current UpdateQuery instance for method chaining. */ - public addReturning(field: string | string[]): this { + public addReturning(fields: string | string[]): this { + this.addReturningRaw(SqlEscaper.escapeSelectIdentifiers( + Array.isArray(fields) ? fields : [fields], this.flavor + )); + return this; + } + + /** + * Specifies raw RETURNING fields for the update without escaping. + * Accepts either a single raw field string or an array of raw field strings. + * Use with caution to avoid SQL injection. + * @param fields - The raw field(s) to be returned after the update. + * @returns The current UpdateQuery instance for method chaining. + */ + public returningRaw(fields: string | string[]): this { + this.returnAll = false; + if (Array.isArray(fields)) { + this.returningFields = fields; + } else { + this.returningFields = [fields]; + } + return this; + } + + /** + * Adds additional raw RETURNING fields to the update without escaping. + * Accepts either a single raw field string or an array of raw field strings. + * Use with caution to avoid SQL injection. + * @param field - The raw field(s) to be added to the RETURNING clause. + * @returns The current UpdateQuery instance for method chaining. + */ + public addReturningRaw(field: string | string[]): this { + this.returnAll = false; if (Array.isArray(field)) { - this.returningFields.push(...SqlEscaper.escapeSelectIdentifiers(field, this.flavor)); + this.returningFields.push(...field); } else { - this.returningFields.push(...SqlEscaper.escapeSelectIdentifiers([field], this.flavor)); + this.returningFields.push(field); } return this; } @@ -343,7 +384,7 @@ export default class UpdateQuery extends DmlQueryDefinition { usingClause, joinClauses, whereClause, - returningClause + returningClause || (this.returnAll ? 'RETURNING *' : '') ].filter(part => part !== '') .join('\n'); @@ -394,6 +435,7 @@ export default class UpdateQuery extends DmlQueryDefinition { this.returningFields = []; this.builtQuery = null; this.ctes = null; + this.returnAll = false; this.schemas = []; } @@ -426,6 +468,7 @@ export default class UpdateQuery extends DmlQueryDefinition { cloned.whereStatement = this.whereStatement ? this.whereStatement.clone() : null; cloned.returningFields = [...this.returningFields]; cloned.ctes = this.ctes ? new CteMaker(...this.ctes['ctes']) : null; + cloned.returnAll = this.returnAll; return cloned; } From de259c14b0c60b695f2e10c44f000a411796d119 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 7 Oct 2025 15:13:14 -0300 Subject: [PATCH 58/74] fix/remove all returning fields when returnAllFields is called --- src/queryKinds/dml/delete.ts | 1 + src/queryKinds/dml/insert.ts | 1 + src/queryKinds/dml/update.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/src/queryKinds/dml/delete.ts b/src/queryKinds/dml/delete.ts index e9c921a..6db6c5f 100644 --- a/src/queryKinds/dml/delete.ts +++ b/src/queryKinds/dml/delete.ts @@ -128,6 +128,7 @@ export default class DeleteQuery extends DmlQueryDefinition { */ public returnAllFields(): this { this.returnAll = true; + this.returningFields = []; return this; } diff --git a/src/queryKinds/dml/insert.ts b/src/queryKinds/dml/insert.ts index 5cde882..f1f48ee 100644 --- a/src/queryKinds/dml/insert.ts +++ b/src/queryKinds/dml/insert.ts @@ -113,6 +113,7 @@ export default class InsertQuery extends DmlQueryDefinition { */ public returnAllFields(): this { this.returnAll = true; + this.returningFields = []; return this; } diff --git a/src/queryKinds/dml/update.ts b/src/queryKinds/dml/update.ts index 1480b02..af1cb15 100644 --- a/src/queryKinds/dml/update.ts +++ b/src/queryKinds/dml/update.ts @@ -196,6 +196,7 @@ export default class UpdateQuery extends DmlQueryDefinition { */ public returnAllFields(): this { this.returnAll = true; + this.returningFields = []; return this; } From e2067095a34b59ef94b999cd4069bb1975ff0039 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 7 Oct 2025 15:26:18 -0300 Subject: [PATCH 59/74] refact/biome refact --- src/queryKinds/dml/delete.ts | 65 ++++++++++++++++------------- src/queryKinds/dml/insert.ts | 60 +++++++++++++++------------ src/queryKinds/dml/update.ts | 79 ++++++++++++++++++++---------------- 3 files changed, 113 insertions(+), 91 deletions(-) diff --git a/src/queryKinds/dml/delete.ts b/src/queryKinds/dml/delete.ts index 1cae369..c626c91 100644 --- a/src/queryKinds/dml/delete.ts +++ b/src/queryKinds/dml/delete.ts @@ -129,25 +129,28 @@ export default class DeleteQuery extends DmlQueryDefinition { } /** - * Specifies that all fields should be returned after the delete operation. - * @returns The current DeleteQuery instance for method chaining. - */ + * Specifies that all fields should be returned after the delete operation. + * @returns The current DeleteQuery instance for method chaining. + */ public returnAllFields(): this { this.returnAll = true; this.returningFields = []; return this; } - /** - * Specifies the fields to be returned after the delete operation. - * Accepts a string or an array of strings. - * @param fields - The field(s) to be returned. - * @returns The current DeleteQuery instance for method chaining. - */ + /** + * Specifies the fields to be returned after the delete operation. + * Accepts a string or an array of strings. + * @param fields - The field(s) to be returned. + * @returns The current DeleteQuery instance for method chaining. + */ public returning(fields: string | string[]): this { - this.returningRaw(SqlEscaper.escapeSelectIdentifiers( - Array.isArray(fields) ? fields : [fields], this.flavor - )); + this.returningRaw( + SqlEscaper.escapeSelectIdentifiers( + Array.isArray(fields) ? fields : [fields], + this.flavor, + ), + ); return this; } @@ -158,18 +161,21 @@ export default class DeleteQuery extends DmlQueryDefinition { * @returns The current DeleteQuery instance for method chaining. */ public addReturning(fields: string | string[]): this { - this.addReturningRaw(SqlEscaper.escapeSelectIdentifiers( - Array.isArray(fields) ? fields : [fields], this.flavor - )); + this.addReturningRaw( + SqlEscaper.escapeSelectIdentifiers( + Array.isArray(fields) ? fields : [fields], + this.flavor, + ), + ); return this; } /** - * Specifies raw fields to be returned after the delete operation without any escaping. - * Accepts a string or an array of strings. - * @param fields - The raw field(s) to be returned. - * @returns The current DeleteQuery instance for method chaining. - */ + * Specifies raw fields to be returned after the delete operation without any escaping. + * Accepts a string or an array of strings. + * @param fields - The raw field(s) to be returned. + * @returns The current DeleteQuery instance for method chaining. + */ public returningRaw(fields: string | string[]): this { this.returnAll = false; if (Array.isArray(fields)) { @@ -181,11 +187,11 @@ export default class DeleteQuery extends DmlQueryDefinition { } /** - * Adds raw fields to the existing RETURNING clause without any escaping. - * Accepts a string or an array of strings. - * @param field - The raw field(s) to be added to the RETURNING clause. - * @returns The current DeleteQuery instance for method chaining. - */ + * Adds raw fields to the existing RETURNING clause without any escaping. + * Accepts a string or an array of strings. + * @param field - The raw field(s) to be added to the RETURNING clause. + * @returns The current DeleteQuery instance for method chaining. + */ public addReturningRaw(field: string | string[]): this { this.returnAll = false; if (Array.isArray(field)) { @@ -211,7 +217,7 @@ export default class DeleteQuery extends DmlQueryDefinition { ? this.whereStatement.clone() : null; cloned.returningFields = [...this.returningFields]; - cloned.ctes = this.ctes ? new CteMaker(...this.ctes['ctes']) : null; + cloned.ctes = this.ctes ? new CteMaker(...this.ctes["ctes"]) : null; cloned.returnAll = this.returnAll; return cloned; } @@ -308,9 +314,10 @@ export default class DeleteQuery extends DmlQueryDefinition { deleteClause, usingClause, whereClause, - returningClause || (this.returnAll ? 'RETURNING *' : '') - ].filter(part => part !== '') - .join('\n '); + returningClause || (this.returnAll ? "RETURNING *" : ""), + ] + .filter((part) => part !== "") + .join("\n "); this.builtQuery = SqlEscaper.appendSchemas(this.builtQuery, this.schemas); diff --git a/src/queryKinds/dml/insert.ts b/src/queryKinds/dml/insert.ts index f87c3cb..3beb932 100644 --- a/src/queryKinds/dml/insert.ts +++ b/src/queryKinds/dml/insert.ts @@ -109,25 +109,28 @@ export default class InsertQuery extends DmlQueryDefinition { } /** - * Indicates that all fields should be returned after the insert operation. - * This is equivalent to using RETURNING * in SQL. - * @returns The current InsertQuery instance for method chaining. - */ + * Indicates that all fields should be returned after the insert operation. + * This is equivalent to using RETURNING * in SQL. + * @returns The current InsertQuery instance for method chaining. + */ public returnAllFields(): this { this.returnAll = true; this.returningFields = []; return this; } - /** - * Specifies the fields to be returned after the insert operation. - * @param fields - A single field or an array of fields to be returned. - * @returns The current InsertQuery instance for method chaining. - */ + /** + * Specifies the fields to be returned after the insert operation. + * @param fields - A single field or an array of fields to be returned. + * @returns The current InsertQuery instance for method chaining. + */ public returning(fields: string | string[]): this { - this.returningRaw(SqlEscaper.escapeSelectIdentifiers( - Array.isArray(fields) ? fields : [fields], this.flavor - )); + this.returningRaw( + SqlEscaper.escapeSelectIdentifiers( + Array.isArray(fields) ? fields : [fields], + this.flavor, + ), + ); return this; } @@ -137,17 +140,20 @@ export default class InsertQuery extends DmlQueryDefinition { * @returns The current InsertQuery instance for method chaining. */ public addReturning(fields: string | string[]): this { - this.addReturningRaw(SqlEscaper.escapeSelectIdentifiers( - Array.isArray(fields) ? fields : [fields], this.flavor - )); + this.addReturningRaw( + SqlEscaper.escapeSelectIdentifiers( + Array.isArray(fields) ? fields : [fields], + this.flavor, + ), + ); return this; } /** - * Specifies raw fields to be returned after the insert operation without escaping. - * @param fields - A single field or an array of fields to be returned. - * @returns The current InsertQuery instance for method chaining. - */ + * Specifies raw fields to be returned after the insert operation without escaping. + * @param fields - A single field or an array of fields to be returned. + * @returns The current InsertQuery instance for method chaining. + */ public returningRaw(fields: string | string[]): this { this.returnAll = false; if (Array.isArray(fields)) { @@ -159,10 +165,10 @@ export default class InsertQuery extends DmlQueryDefinition { } /** - * Adds raw fields to the existing RETURNING clause without escaping. - * @param field - A single field or an array of fields to be added to the RETURNING clause. - * @returns The current InsertQuery instance for method chaining. - */ + * Adds raw fields to the existing RETURNING clause without escaping. + * @param field - A single field or an array of fields to be added to the RETURNING clause. + * @returns The current InsertQuery instance for method chaining. + */ public addReturningRaw(field: string | string[]): this { this.returnAll = false; if (Array.isArray(field)) { @@ -184,7 +190,7 @@ export default class InsertQuery extends DmlQueryDefinition { cloned.columnValues = JSON.parse(JSON.stringify(this.columnValues)); cloned.selectQuery = this.selectQuery ? this.selectQuery.clone() : null; cloned.returningFields = [...this.returningFields]; - cloned.ctes = this.ctes ? new CteMaker(...this.ctes['ctes']) : null; + cloned.ctes = this.ctes ? new CteMaker(...this.ctes["ctes"]) : null; cloned.returnAll = this.returnAll; return cloned; } @@ -321,8 +327,10 @@ export default class InsertQuery extends DmlQueryDefinition { const text = [ ctesClause ? `${ctesClause} ` : "", insertClause, - returningClause || (this.returnAll ? 'RETURNING *' : '') - ].join('\n').trim(); + returningClause || (this.returnAll ? "RETURNING *" : ""), + ] + .join("\n") + .trim(); this.builtQuery = text; this.builtQuery = SqlEscaper.appendSchemas(this.builtQuery, this.schemas); diff --git a/src/queryKinds/dml/update.ts b/src/queryKinds/dml/update.ts index 7d70c8e..1281efe 100644 --- a/src/queryKinds/dml/update.ts +++ b/src/queryKinds/dml/update.ts @@ -206,10 +206,10 @@ export default class UpdateQuery extends DmlQueryDefinition { } /** - * Specifies that all fields should be returned after the update. - * This sets the RETURNING clause to '*'. - * @returns The current UpdateQuery instance for method chaining. - */ + * Specifies that all fields should be returned after the update. + * This sets the RETURNING clause to '*'. + * @returns The current UpdateQuery instance for method chaining. + */ public returnAllFields(): this { this.returnAll = true; this.returningFields = []; @@ -217,38 +217,44 @@ export default class UpdateQuery extends DmlQueryDefinition { } /** - * Specifies the RETURNING fields for the update. - * Accepts either a single field name or an array of field names. - * @param fields - The field(s) to be returned after the update. - * @returns The current UpdateQuery instance for method chaining. - */ + * Specifies the RETURNING fields for the update. + * Accepts either a single field name or an array of field names. + * @param fields - The field(s) to be returned after the update. + * @returns The current UpdateQuery instance for method chaining. + */ public returning(fields: string | string[]): this { - this.returningRaw(SqlEscaper.escapeSelectIdentifiers( - Array.isArray(fields) ? fields : [fields], this.flavor - )); + this.returningRaw( + SqlEscaper.escapeSelectIdentifiers( + Array.isArray(fields) ? fields : [fields], + this.flavor, + ), + ); return this; } /** - * Adds additional RETURNING fields to the update. - * Accepts either a single field name or an array of field names. - * @param field - The field(s) to be added to the RETURNING clause. - * @returns The current UpdateQuery instance for method chaining. - */ + * Adds additional RETURNING fields to the update. + * Accepts either a single field name or an array of field names. + * @param field - The field(s) to be added to the RETURNING clause. + * @returns The current UpdateQuery instance for method chaining. + */ public addReturning(fields: string | string[]): this { - this.addReturningRaw(SqlEscaper.escapeSelectIdentifiers( - Array.isArray(fields) ? fields : [fields], this.flavor - )); + this.addReturningRaw( + SqlEscaper.escapeSelectIdentifiers( + Array.isArray(fields) ? fields : [fields], + this.flavor, + ), + ); return this; } /** - * Specifies raw RETURNING fields for the update without escaping. - * Accepts either a single raw field string or an array of raw field strings. - * Use with caution to avoid SQL injection. - * @param fields - The raw field(s) to be returned after the update. - * @returns The current UpdateQuery instance for method chaining. - */ + * Specifies raw RETURNING fields for the update without escaping. + * Accepts either a single raw field string or an array of raw field strings. + * Use with caution to avoid SQL injection. + * @param fields - The raw field(s) to be returned after the update. + * @returns The current UpdateQuery instance for method chaining. + */ public returningRaw(fields: string | string[]): this { this.returnAll = false; if (Array.isArray(fields)) { @@ -260,12 +266,12 @@ export default class UpdateQuery extends DmlQueryDefinition { } /** - * Adds additional raw RETURNING fields to the update without escaping. - * Accepts either a single raw field string or an array of raw field strings. - * Use with caution to avoid SQL injection. - * @param field - The raw field(s) to be added to the RETURNING clause. - * @returns The current UpdateQuery instance for method chaining. - */ + * Adds additional raw RETURNING fields to the update without escaping. + * Accepts either a single raw field string or an array of raw field strings. + * Use with caution to avoid SQL injection. + * @param field - The raw field(s) to be added to the RETURNING clause. + * @returns The current UpdateQuery instance for method chaining. + */ public addReturningRaw(field: string | string[]): this { this.returnAll = false; if (Array.isArray(field)) { @@ -403,9 +409,10 @@ export default class UpdateQuery extends DmlQueryDefinition { usingClause, joinClauses, whereClause, - returningClause || (this.returnAll ? 'RETURNING *' : '') - ].filter(part => part !== '') - .join('\n'); + returningClause || (this.returnAll ? "RETURNING *" : ""), + ] + .filter((part) => part !== "") + .join("\n"); this.builtQuery = SqlEscaper.appendSchemas(this.builtQuery, this.schemas); @@ -490,7 +497,7 @@ export default class UpdateQuery extends DmlQueryDefinition { ? this.whereStatement.clone() : null; cloned.returningFields = [...this.returningFields]; - cloned.ctes = this.ctes ? new CteMaker(...this.ctes['ctes']) : null; + cloned.ctes = this.ctes ? new CteMaker(...this.ctes["ctes"]) : null; cloned.returnAll = this.returnAll; return cloned; } From b03ce79d97fee25e8535e594cd6551fbc9df2ac2 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 7 Oct 2025 15:33:19 -0300 Subject: [PATCH 60/74] feat/added void back to unions --- biome.json | 3 ++- src/queryKinds/dml/delete.ts | 2 +- src/queryKinds/dml/select.ts | 4 ++-- src/queryKinds/dml/union.ts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/biome.json b/biome.json index 5bd63e8..695de85 100644 --- a/biome.json +++ b/biome.json @@ -20,7 +20,8 @@ "recommended": true, "suspicious": { "noExplicitAny": "off", - "useIterableCallbackReturn": "off" + "useIterableCallbackReturn": "off", + "noConfusingVoidType": "off" }, "style": { "noNonNullAssertion": "off" diff --git a/src/queryKinds/dml/delete.ts b/src/queryKinds/dml/delete.ts index c626c91..518b896 100644 --- a/src/queryKinds/dml/delete.ts +++ b/src/queryKinds/dml/delete.ts @@ -121,7 +121,7 @@ export default class DeleteQuery extends DmlQueryDefinition { * @returns The current DeleteQuery instance for method chaining. */ public useStatement( - statement: (stmt: Statement) => Statement | undefined, + statement: (stmt: Statement) => Statement | undefined | void, ): this { const stmt = new Statement(); const newStmt = statement(stmt) || stmt; diff --git a/src/queryKinds/dml/select.ts b/src/queryKinds/dml/select.ts index 26d07d6..7ae9433 100644 --- a/src/queryKinds/dml/select.ts +++ b/src/queryKinds/dml/select.ts @@ -266,7 +266,7 @@ export default class SelectQuery extends DmlQueryDefinition { * @returns The current SelectQuery instance for chaining. */ public useStatement( - statement: (stmt: Statement) => Statement | undefined, + statement: (stmt: Statement) => Statement | undefined | void, ): this { const stmt = new Statement(); const newStmt = statement(stmt) || stmt; @@ -296,7 +296,7 @@ export default class SelectQuery extends DmlQueryDefinition { * @returns The current SelectQuery instance for chaining. */ public useHavingStatement( - statement: (stmt: Statement) => Statement | undefined, + statement: (stmt: Statement) => Statement | undefined | void, ): this { const stmt = new Statement(); const newStmt = statement(stmt) || stmt; diff --git a/src/queryKinds/dml/union.ts b/src/queryKinds/dml/union.ts index 5593fba..ff5fba8 100644 --- a/src/queryKinds/dml/union.ts +++ b/src/queryKinds/dml/union.ts @@ -279,7 +279,7 @@ export default class Union extends DmlQueryDefinition { * @returns The current Union instance for method chaining. */ public useStatement( - statement: (stmt: Statement) => Statement | undefined, + statement: (stmt: Statement) => Statement | undefined | void, ): Union { const stmt = new Statement(); const newStatement = statement(stmt) || stmt; From 410d98360d023cb283d4bad7da411b157b1cbc48 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 7 Oct 2025 15:34:26 -0300 Subject: [PATCH 61/74] fix/added void back to unions on update --- src/queryKinds/dml/update.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/queryKinds/dml/update.ts b/src/queryKinds/dml/update.ts index 1281efe..399f49e 100644 --- a/src/queryKinds/dml/update.ts +++ b/src/queryKinds/dml/update.ts @@ -198,7 +198,7 @@ export default class UpdateQuery extends DmlQueryDefinition { * @returns The current UpdateQuery instance for method chaining. */ public useStatement( - statement: (stmt: Statement) => Statement | undefined, + statement: (stmt: Statement) => Statement | undefined | void, ): this { const stmt = new Statement(); const newStmt = statement(stmt) || stmt; From b478436200e5c4cd6c37c55e62541df26471ae3e Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 7 Oct 2025 15:35:09 -0300 Subject: [PATCH 62/74] bumped up version because of tag error --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c653e25..de352e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sqm", - "version": "0.7.0", + "version": "0.7.1", "description": "A lightweight and flexible query maker library for building raw SQL queries in JavaScript.", "main": "dist/index.js", "types": "dist/index.d.ts", From ac145632f8103f297e1eeeb5b30dbbbf9267e37e Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 13 Oct 2025 15:12:58 -0300 Subject: [PATCH 63/74] fix/fixed validation with wrong type in environments without zod --- src/queryKinds/dml/dmlQueryDefinition.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/queryKinds/dml/dmlQueryDefinition.ts b/src/queryKinds/dml/dmlQueryDefinition.ts index 1e8fc90..758e51c 100644 --- a/src/queryKinds/dml/dmlQueryDefinition.ts +++ b/src/queryKinds/dml/dmlQueryDefinition.ts @@ -2,7 +2,6 @@ import type { ValidatorOptions } from "class-validator"; // Import types only since they are used for type checking only // and zod is optional peer dependency import type z from "zod"; -import type { ZodObject } from "zod"; import type CteMaker from "../../cteMaker.js"; import deepEqual from "../../deepEqual.js"; import { getClassValidator, getZod } from "../../getOptionalPackages.js"; @@ -60,7 +59,7 @@ export type QueryExecutor = QueryExecutorObject | FunctionDeclaration; * SchemaType is a conditional type that infers the type of data based on the provided schema. * It supports both Zod schemas and class-validator classes. */ -type SchemaType = S extends ZodObject +type SchemaType = S extends { safeParse: Function } ? z.infer : S extends { new (): infer U } ? U From 8b49dc533e7b4fd0a5f83277ff7634af4d7e1a95 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 27 Oct 2025 03:48:29 -0300 Subject: [PATCH 64/74] feat/changed input for withQuery --- src/cteMaker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cteMaker.ts b/src/cteMaker.ts index f7c73c6..c1fc40c 100644 --- a/src/cteMaker.ts +++ b/src/cteMaker.ts @@ -63,7 +63,7 @@ export class Cte { * @param query - The query defining the CTE. * @returns The current Cte instance for method chaining. */ - public withQuery(query: SelectQuery): this { + public withQuery(query: QueryDefinition): this { this.query = query; return this; } From 47c3c4f68511f3b7d28237fbb5a4fc0d428cf7e1 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Mon, 27 Oct 2025 03:57:54 -0300 Subject: [PATCH 65/74] bookkeeping/updated dependencies --- package-lock.json | 549 +++++++++++++++++++++++++--------------------- package.json | 8 +- pnpm-lock.yaml | 354 +++++++++++------------------- 3 files changed, 436 insertions(+), 475 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7283588..9c08e18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,30 +1,29 @@ { "name": "sqm", - "version": "0.7.0", + "version": "0.7.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sqm", - "version": "0.7.0", + "version": "0.7.1", "license": "Apache-2.0", - "dependencies": { - "ts-pattern": "^5.8.0" - }, "devDependencies": { + "@biomejs/biome": "^2.3.1", "@swc/core": "^1.13.5", - "@vitest/coverage-v8": "^3.2.4", + "@vitest/coverage-v8": "^4.0.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "concurrently": "^9.2.1", + "jsr": "^0.13.5", "nodemon": "^3.1.10", "terser": "^5.44.0", "tslib": "^2.8.1", "tsup": "^8.5.0", "typescript": "^5.9.3", - "vitest": "^3.2.4", + "vitest": "^4.0.3", "wait-on": "^9.0.1", - "zod": "^4.1.11" + "zod": "^4.1.12" }, "engines": { "node": ">=21" @@ -46,20 +45,6 @@ } } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -120,6 +105,169 @@ "node": ">=18" } }, + "node_modules/@biomejs/biome": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.1.tgz", + "integrity": "sha512-A29evf1R72V5bo4o2EPxYMm5mtyGvzp2g+biZvRFx29nWebGyyeOSsDWGx3tuNNMFRepGwxmA9ZQ15mzfabK2w==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.3.1", + "@biomejs/cli-darwin-x64": "2.3.1", + "@biomejs/cli-linux-arm64": "2.3.1", + "@biomejs/cli-linux-arm64-musl": "2.3.1", + "@biomejs/cli-linux-x64": "2.3.1", + "@biomejs/cli-linux-x64-musl": "2.3.1", + "@biomejs/cli-win32-arm64": "2.3.1", + "@biomejs/cli-win32-x64": "2.3.1" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.1.tgz", + "integrity": "sha512-ombSf3MnTUueiYGN1SeI9tBCsDUhpWzOwS63Dove42osNh0PfE1cUtHFx6eZ1+MYCCLwXzlFlYFdrJ+U7h6LcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.1.tgz", + "integrity": "sha512-pcOfwyoQkrkbGvXxRvZNe5qgD797IowpJPovPX5biPk2FwMEV+INZqfCaz4G5bVq9hYnjwhRMamg11U4QsRXrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.1.tgz", + "integrity": "sha512-td5O8pFIgLs8H1sAZsD6v+5quODihyEw4nv2R8z7swUfIK1FKk+15e4eiYVLcAE4jUqngvh4j3JCNgg0Y4o4IQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.1.tgz", + "integrity": "sha512-+DZYv8l7FlUtTrWs1Tdt1KcNCAmRO87PyOnxKGunbWm5HKg1oZBSbIIPkjrCtDZaeqSG1DiGx7qF+CPsquQRcg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.1.tgz", + "integrity": "sha512-PYWgEO7up7XYwSAArOpzsVCiqxBCXy53gsReAb1kKYIyXaoAlhBaBMvxR/k2Rm9aTuZ662locXUmPk/Aj+Xu+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.1.tgz", + "integrity": "sha512-Y3Ob4nqgv38Mh+6EGHltuN+Cq8aj/gyMTJYzkFZV2AEj+9XzoXB9VNljz9pjfFNHUxvLEV4b55VWyxozQTBaUQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.1.tgz", + "integrity": "sha512-RHIG/zgo+69idUqVvV3n8+j58dKYABRpMyDmfWu2TITC+jwGPiEaT0Q3RKD+kQHiS80mpBrST0iUGeEXT0bU9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.1.tgz", + "integrity": "sha512-izl30JJ5Dp10mi90Eko47zhxE6pYyWPcnX1NQxKpL/yMhXxf95oLTzfpu4q+MDBh/gemNqyJEwjBpe0MT5iWPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.10", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", @@ -634,16 +782,6 @@ "node": ">=12" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1248,13 +1386,14 @@ } }, "node_modules/@types/chai": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { - "@types/deep-eql": "*" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, "node_modules/@types/deep-eql": { @@ -1279,32 +1418,30 @@ "license": "MIT" }, "node_modules/@vitest/coverage-v8": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", - "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.3.tgz", + "integrity": "sha512-I+MlLwyJRBjmJr1kFYSxoseINbIdpxIAeK10jmXgB0FUtIfdYsvM3lGAvBu5yk8WPyhefzdmbCHCc1idFbNRcg==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", - "ast-v8-to-istanbul": "^0.3.3", - "debug": "^4.4.1", + "@vitest/utils": "4.0.3", + "ast-v8-to-istanbul": "^0.3.5", + "debug": "^4.4.3", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", - "istanbul-reports": "^3.1.7", - "magic-string": "^0.30.17", + "istanbul-reports": "^3.2.0", "magicast": "^0.3.5", "std-env": "^3.9.0", - "test-exclude": "^7.0.1", - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "3.2.4", - "vitest": "3.2.4" + "@vitest/browser": "4.0.3", + "vitest": "4.0.3" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1313,39 +1450,40 @@ } }, "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.3.tgz", + "integrity": "sha512-v3eSDx/bF25pzar6aEJrrdTXJduEBU3uSGXHslIdGIpJVP8tQQHV6x1ZfzbFQ/bLIomLSbR/2ZCfnaEGkWkiVQ==", "dev": true, "license": "MIT", "dependencies": { + "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" + "@vitest/spy": "4.0.3", + "@vitest/utils": "4.0.3", + "chai": "^6.0.1", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.3.tgz", + "integrity": "sha512-evZcRspIPbbiJEe748zI2BRu94ThCBE+RkjCpVF8yoVYuTV7hMe+4wLF/7K86r8GwJHSmAPnPbZhpXWWrg1qbA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.2.4", + "@vitest/spy": "4.0.3", "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" + "magic-string": "^0.30.19" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0-0" }, "peerDependenciesMeta": { "msw": { @@ -1357,42 +1495,41 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.3.tgz", + "integrity": "sha512-N7gly/DRXzxa9w9sbDXwD9QNFYP2hw90LLLGDobPNwiWgyW95GMxsCt29/COIKKh3P7XJICR38PSDePenMBtsw==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^2.0.0" + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", - "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.3.tgz", + "integrity": "sha512-1/aK6fPM0lYXWyGKwop2Gbvz1plyTps/HDbIIJXYtJtspHjpXIeB3If07eWpVH4HW7Rmd3Rl+IS/+zEAXrRtXA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.2.4", - "pathe": "^2.0.3", - "strip-literal": "^3.0.0" + "@vitest/utils": "4.0.3", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", - "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.3.tgz", + "integrity": "sha512-amnYmvZ5MTjNCP1HZmdeczAPLRD6iOm9+2nMRUGxbe/6sQ0Ymur0NnR9LIrWS8JA3wKE71X25D6ya/3LN9YytA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "magic-string": "^0.30.17", + "@vitest/pretty-format": "4.0.3", + "magic-string": "^0.30.19", "pathe": "^2.0.3" }, "funding": { @@ -1400,28 +1537,24 @@ } }, "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.3.tgz", + "integrity": "sha512-82vVL8Cqz7rbXaNUl35V2G7xeNMAjBdNOVaHbrzznT9BmiCiPOzhf0FhU3eP41nP1bLDm/5wWKZqkG4nyU95DQ==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.3.tgz", + "integrity": "sha512-qV6KJkq8W3piW6MDIbGOmn1xhvcW4DuA07alqaQ+vdx7YA49J85pnwnxigZVQFQw3tWnQNRKWwhz5wbP6iv/GQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" + "@vitest/pretty-format": "4.0.3", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -1632,18 +1765,11 @@ } }, "node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.0.tgz", + "integrity": "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, "engines": { "node": ">=18" } @@ -1694,16 +1820,6 @@ "node": ">=8" } }, - "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 16" - } - }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -1955,16 +2071,6 @@ } } }, - "node_modules/deep-eql": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", - "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2600,6 +2706,20 @@ "dev": true, "license": "MIT" }, + "node_modules/jsr": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/jsr/-/jsr-0.13.5.tgz", + "integrity": "sha512-qQP20ZcG28pYes7bCq3uuvixl1TL1EpJzwLPfoQadSyWk9j2AID66qhW8+aXpRDRFDvDkXFnONsSRhpnnQAupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-stream-zip": "^1.15.0", + "semiver": "^1.1.0" + }, + "bin": { + "jsr": "dist/bin.js" + } + }, "node_modules/libphonenumber-js": { "version": "1.12.17", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.17.tgz", @@ -2651,13 +2771,6 @@ "dev": true, "license": "MIT" }, - "node_modules/loupe": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", - "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -2823,6 +2936,20 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" + } + }, "node_modules/nodemon": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", @@ -3011,16 +3138,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", - "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.16" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3247,6 +3364,16 @@ "tslib": "^2.1.0" } }, + "node_modules/semiver": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semiver/-/semiver-1.1.0.tgz", + "integrity": "sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -3478,19 +3605,6 @@ "node": ">=8" } }, - "node_modules/strip-literal": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", - "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -3560,21 +3674,6 @@ "node": ">=10" } }, - "node_modules/test-exclude": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", - "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^10.4.1", - "minimatch": "^9.0.4" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -3629,30 +3728,10 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinypool": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", - "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.0.0 || >=20.0.0" - } - }, "node_modules/tinyrainbow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", - "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tinyspy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", - "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", "dev": true, "license": "MIT", "engines": { @@ -3709,12 +3788,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/ts-pattern": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-5.8.0.tgz", - "integrity": "sha512-kIjN2qmWiHnhgr5DAkAafF9fwb0T5OhMVSWrm8XEdTFnX6+wfXwYOFjeF86UZ54vduqiR7BfqScFmXSzSaH8oA==", - "license": "MIT" - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -3829,9 +3902,9 @@ } }, "node_modules/vite": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.6.tgz", - "integrity": "sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==", + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", "peer": true, @@ -3904,66 +3977,40 @@ } } }, - "node_modules/vite-node": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", - "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.4.1", - "es-module-lexer": "^1.7.0", - "pathe": "^2.0.3", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "bin": { - "vite-node": "vite-node.mjs" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/vitest": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", - "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.3.tgz", + "integrity": "sha512-IUSop8jgaT7w0g1yOM/35qVtKjr/8Va4PrjzH1OUb0YH4c3OXB2lCZDkMAB6glA8T5w8S164oJGsbcmAecr4sA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/expect": "3.2.4", - "@vitest/mocker": "3.2.4", - "@vitest/pretty-format": "^3.2.4", - "@vitest/runner": "3.2.4", - "@vitest/snapshot": "3.2.4", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "debug": "^4.4.1", - "expect-type": "^1.2.1", - "magic-string": "^0.30.17", + "@vitest/expect": "4.0.3", + "@vitest/mocker": "4.0.3", + "@vitest/pretty-format": "4.0.3", + "@vitest/runner": "4.0.3", + "@vitest/snapshot": "4.0.3", + "@vitest/spy": "4.0.3", + "@vitest/utils": "4.0.3", + "debug": "^4.4.3", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.19", "pathe": "^2.0.3", - "picomatch": "^4.0.2", + "picomatch": "^4.0.3", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", - "tinyglobby": "^0.2.14", - "tinypool": "^1.1.1", - "tinyrainbow": "^2.0.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", - "vite-node": "3.2.4", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -3971,9 +4018,11 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.2.4", - "@vitest/ui": "3.2.4", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.3", + "@vitest/browser-preview": "4.0.3", + "@vitest/browser-webdriverio": "4.0.3", + "@vitest/ui": "4.0.3", "happy-dom": "*", "jsdom": "*" }, @@ -3987,7 +4036,13 @@ "@types/node": { "optional": true }, - "@vitest/browser": { + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { "optional": true }, "@vitest/ui": { @@ -4256,9 +4311,9 @@ } }, "node_modules/zod": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", - "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "dev": true, "license": "MIT", "funding": { diff --git a/package.json b/package.json index de352e0..493b8b9 100644 --- a/package.json +++ b/package.json @@ -76,9 +76,9 @@ "author": "Nicolas R. M. Dias ", "license": "Apache-2.0", "devDependencies": { - "@biomejs/biome": "^2.2.5", + "@biomejs/biome": "^2.3.1", "@swc/core": "^1.13.5", - "@vitest/coverage-v8": "^3.2.4", + "@vitest/coverage-v8": "^4.0.3", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "concurrently": "^9.2.1", @@ -88,9 +88,9 @@ "tslib": "^2.8.1", "tsup": "^8.5.0", "typescript": "^5.9.3", - "vitest": "^3.2.4", + "vitest": "^4.0.3", "wait-on": "^9.0.1", - "zod": "^4.1.11" + "zod": "^4.1.12" }, "peerDependencies": { "class-transformer": ">=0.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 83503b9..a58a337 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,14 +9,14 @@ importers: .: devDependencies: '@biomejs/biome': - specifier: ^2.2.5 - version: 2.2.5 + specifier: ^2.3.1 + version: 2.3.1 '@swc/core': specifier: ^1.13.5 version: 1.13.5 '@vitest/coverage-v8': - specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4(terser@5.44.0)) + specifier: ^4.0.3 + version: 4.0.3(vitest@4.0.3(terser@5.44.0)) class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -45,21 +45,17 @@ importers: specifier: ^5.9.3 version: 5.9.3 vitest: - specifier: ^3.2.4 - version: 3.2.4(terser@5.44.0) + specifier: ^4.0.3 + version: 4.0.3(terser@5.44.0) wait-on: specifier: ^9.0.1 version: 9.0.1 zod: - specifier: ^4.1.11 - version: 4.1.11 + specifier: ^4.1.12 + version: 4.1.12 packages: - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -81,55 +77,55 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@biomejs/biome@2.2.5': - resolution: {integrity: sha512-zcIi+163Rc3HtyHbEO7CjeHq8DjQRs40HsGbW6vx2WI0tg8mYQOPouhvHSyEnCBAorfYNnKdR64/IxO7xQ5faw==} + '@biomejs/biome@2.3.1': + resolution: {integrity: sha512-A29evf1R72V5bo4o2EPxYMm5mtyGvzp2g+biZvRFx29nWebGyyeOSsDWGx3tuNNMFRepGwxmA9ZQ15mzfabK2w==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.2.5': - resolution: {integrity: sha512-MYT+nZ38wEIWVcL5xLyOhYQQ7nlWD0b/4mgATW2c8dvq7R4OQjt/XGXFkXrmtWmQofaIM14L7V8qIz/M+bx5QQ==} + '@biomejs/cli-darwin-arm64@2.3.1': + resolution: {integrity: sha512-ombSf3MnTUueiYGN1SeI9tBCsDUhpWzOwS63Dove42osNh0PfE1cUtHFx6eZ1+MYCCLwXzlFlYFdrJ+U7h6LcA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.2.5': - resolution: {integrity: sha512-FLIEl73fv0R7dI10EnEiZLw+IMz3mWLnF95ASDI0kbx6DDLJjWxE5JxxBfmG+udz1hIDd3fr5wsuP7nwuTRdAg==} + '@biomejs/cli-darwin-x64@2.3.1': + resolution: {integrity: sha512-pcOfwyoQkrkbGvXxRvZNe5qgD797IowpJPovPX5biPk2FwMEV+INZqfCaz4G5bVq9hYnjwhRMamg11U4QsRXrQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.2.5': - resolution: {integrity: sha512-5Ov2wgAFwqDvQiESnu7b9ufD1faRa+40uwrohgBopeY84El2TnBDoMNXx6iuQdreoFGjwW8vH6k68G21EpNERw==} + '@biomejs/cli-linux-arm64-musl@2.3.1': + resolution: {integrity: sha512-+DZYv8l7FlUtTrWs1Tdt1KcNCAmRO87PyOnxKGunbWm5HKg1oZBSbIIPkjrCtDZaeqSG1DiGx7qF+CPsquQRcg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.2.5': - resolution: {integrity: sha512-5DjiiDfHqGgR2MS9D+AZ8kOfrzTGqLKywn8hoXpXXlJXIECGQ32t+gt/uiS2XyGBM2XQhR6ztUvbjZWeccFMoQ==} + '@biomejs/cli-linux-arm64@2.3.1': + resolution: {integrity: sha512-td5O8pFIgLs8H1sAZsD6v+5quODihyEw4nv2R8z7swUfIK1FKk+15e4eiYVLcAE4jUqngvh4j3JCNgg0Y4o4IQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.2.5': - resolution: {integrity: sha512-AVqLCDb/6K7aPNIcxHaTQj01sl1m989CJIQFQEaiQkGr2EQwyOpaATJ473h+nXDUuAcREhccfRpe/tu+0wu0eQ==} + '@biomejs/cli-linux-x64-musl@2.3.1': + resolution: {integrity: sha512-Y3Ob4nqgv38Mh+6EGHltuN+Cq8aj/gyMTJYzkFZV2AEj+9XzoXB9VNljz9pjfFNHUxvLEV4b55VWyxozQTBaUQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.2.5': - resolution: {integrity: sha512-fq9meKm1AEXeAWan3uCg6XSP5ObA6F/Ovm89TwaMiy1DNIwdgxPkNwxlXJX8iM6oRbFysYeGnT0OG8diCWb9ew==} + '@biomejs/cli-linux-x64@2.3.1': + resolution: {integrity: sha512-PYWgEO7up7XYwSAArOpzsVCiqxBCXy53gsReAb1kKYIyXaoAlhBaBMvxR/k2Rm9aTuZ662locXUmPk/Aj+Xu+Q==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.2.5': - resolution: {integrity: sha512-xaOIad4wBambwJa6mdp1FigYSIF9i7PCqRbvBqtIi9y29QtPVQ13sDGtUnsRoe6SjL10auMzQ6YAe+B3RpZXVg==} + '@biomejs/cli-win32-arm64@2.3.1': + resolution: {integrity: sha512-RHIG/zgo+69idUqVvV3n8+j58dKYABRpMyDmfWu2TITC+jwGPiEaT0Q3RKD+kQHiS80mpBrST0iUGeEXT0bU9A==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.2.5': - resolution: {integrity: sha512-F/jhuXCssPFAuciMhHKk00xnCAxJRS/pUzVfXYmOMUp//XW7mO6QeCjsjvnm8L4AO/dG2VOB0O+fJPiJ2uXtIw==} + '@biomejs/cli-win32-x64@2.3.1': + resolution: {integrity: sha512-izl30JJ5Dp10mi90Eko47zhxE6pYyWPcnX1NQxKpL/yMhXxf95oLTzfpu4q+MDBh/gemNqyJEwjBpe0MT5iWPA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -314,10 +310,6 @@ packages: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -533,43 +525,43 @@ packages: '@types/validator@13.15.3': resolution: {integrity: sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==} - '@vitest/coverage-v8@3.2.4': - resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} + '@vitest/coverage-v8@4.0.3': + resolution: {integrity: sha512-I+MlLwyJRBjmJr1kFYSxoseINbIdpxIAeK10jmXgB0FUtIfdYsvM3lGAvBu5yk8WPyhefzdmbCHCc1idFbNRcg==} peerDependencies: - '@vitest/browser': 3.2.4 - vitest: 3.2.4 + '@vitest/browser': 4.0.3 + vitest: 4.0.3 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@3.2.4': - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.0.3': + resolution: {integrity: sha512-v3eSDx/bF25pzar6aEJrrdTXJduEBU3uSGXHslIdGIpJVP8tQQHV6x1ZfzbFQ/bLIomLSbR/2ZCfnaEGkWkiVQ==} - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + '@vitest/mocker@4.0.3': + resolution: {integrity: sha512-evZcRspIPbbiJEe748zI2BRu94ThCBE+RkjCpVF8yoVYuTV7hMe+4wLF/7K86r8GwJHSmAPnPbZhpXWWrg1qbA==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0-0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.0.3': + resolution: {integrity: sha512-N7gly/DRXzxa9w9sbDXwD9QNFYP2hw90LLLGDobPNwiWgyW95GMxsCt29/COIKKh3P7XJICR38PSDePenMBtsw==} - '@vitest/runner@3.2.4': - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/runner@4.0.3': + resolution: {integrity: sha512-1/aK6fPM0lYXWyGKwop2Gbvz1plyTps/HDbIIJXYtJtspHjpXIeB3If07eWpVH4HW7Rmd3Rl+IS/+zEAXrRtXA==} - '@vitest/snapshot@3.2.4': - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/snapshot@4.0.3': + resolution: {integrity: sha512-amnYmvZ5MTjNCP1HZmdeczAPLRD6iOm9+2nMRUGxbe/6sQ0Ymur0NnR9LIrWS8JA3wKE71X25D6ya/3LN9YytA==} - '@vitest/spy@3.2.4': - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.0.3': + resolution: {integrity: sha512-82vVL8Cqz7rbXaNUl35V2G7xeNMAjBdNOVaHbrzznT9BmiCiPOzhf0FhU3eP41nP1bLDm/5wWKZqkG4nyU95DQ==} - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.0.3': + resolution: {integrity: sha512-qV6KJkq8W3piW6MDIbGOmn1xhvcW4DuA07alqaQ+vdx7YA49J85pnwnxigZVQFQw3tWnQNRKWwhz5wbP6iv/GQ==} acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} @@ -599,10 +591,6 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - ast-v8-to-istanbul@0.3.5: resolution: {integrity: sha512-9SdXjNheSiE8bALAQCQQuT6fgQaoxJh7IRYrRGZ8/9nv8WhJeC1aXAwN8TbaOssGOukUvyvnkgD9+Yuykvl1aA==} @@ -646,18 +634,14 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + chai@6.2.0: + resolution: {integrity: sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==} engines: {node: '>=18'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} - chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -722,9 +706,14 @@ packages: supports-color: optional: true - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -946,9 +935,6 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -1033,10 +1019,6 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1181,9 +1163,6 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} - strip-literal@3.0.0: - resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} - sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -1206,10 +1185,6 @@ packages: engines: {node: '>=10'} hasBin: true - test-exclude@7.0.1: - resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} - engines: {node: '>=18'} - thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -1227,16 +1202,8 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@4.0.4: - resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} to-regex-range@5.0.1: @@ -1294,11 +1261,6 @@ packages: resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==} engines: {node: '>= 0.10'} - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - vite@7.1.6: resolution: {integrity: sha512-SRYIB8t/isTwNn8vMB3MR6E+EQZM/WG1aKmmIUCfDXfVvKfc20ZpamngWHKzAmmu9ppsgxsg4b2I7c90JZudIQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1339,16 +1301,18 @@ packages: yaml: optional: true - vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vitest@4.0.3: + resolution: {integrity: sha512-IUSop8jgaT7w0g1yOM/35qVtKjr/8Va4PrjzH1OUb0YH4c3OXB2lCZDkMAB6glA8T5w8S164oJGsbcmAecr4sA==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.4 - '@vitest/ui': 3.2.4 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.3 + '@vitest/browser-preview': 4.0.3 + '@vitest/browser-webdriverio': 4.0.3 + '@vitest/ui': 4.0.3 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -1358,7 +1322,11 @@ packages: optional: true '@types/node': optional: true - '@vitest/browser': + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': optional: true '@vitest/ui': optional: true @@ -1408,16 +1376,11 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} - zod@4.1.11: - resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==} + zod@4.1.12: + resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} snapshots: - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {} @@ -1433,39 +1396,39 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@biomejs/biome@2.2.5': + '@biomejs/biome@2.3.1': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.2.5 - '@biomejs/cli-darwin-x64': 2.2.5 - '@biomejs/cli-linux-arm64': 2.2.5 - '@biomejs/cli-linux-arm64-musl': 2.2.5 - '@biomejs/cli-linux-x64': 2.2.5 - '@biomejs/cli-linux-x64-musl': 2.2.5 - '@biomejs/cli-win32-arm64': 2.2.5 - '@biomejs/cli-win32-x64': 2.2.5 + '@biomejs/cli-darwin-arm64': 2.3.1 + '@biomejs/cli-darwin-x64': 2.3.1 + '@biomejs/cli-linux-arm64': 2.3.1 + '@biomejs/cli-linux-arm64-musl': 2.3.1 + '@biomejs/cli-linux-x64': 2.3.1 + '@biomejs/cli-linux-x64-musl': 2.3.1 + '@biomejs/cli-win32-arm64': 2.3.1 + '@biomejs/cli-win32-x64': 2.3.1 - '@biomejs/cli-darwin-arm64@2.2.5': + '@biomejs/cli-darwin-arm64@2.3.1': optional: true - '@biomejs/cli-darwin-x64@2.2.5': + '@biomejs/cli-darwin-x64@2.3.1': optional: true - '@biomejs/cli-linux-arm64-musl@2.2.5': + '@biomejs/cli-linux-arm64-musl@2.3.1': optional: true - '@biomejs/cli-linux-arm64@2.2.5': + '@biomejs/cli-linux-arm64@2.3.1': optional: true - '@biomejs/cli-linux-x64-musl@2.2.5': + '@biomejs/cli-linux-x64-musl@2.3.1': optional: true - '@biomejs/cli-linux-x64@2.2.5': + '@biomejs/cli-linux-x64@2.3.1': optional: true - '@biomejs/cli-win32-arm64@2.2.5': + '@biomejs/cli-win32-arm64@2.3.1': optional: true - '@biomejs/cli-win32-x64@2.2.5': + '@biomejs/cli-win32-x64@2.3.1': optional: true '@esbuild/aix-ppc64@0.25.9': @@ -1571,8 +1534,6 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@istanbuljs/schema@0.1.3': {} - '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1722,66 +1683,61 @@ snapshots: '@types/validator@13.15.3': {} - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(terser@5.44.0))': + '@vitest/coverage-v8@4.0.3(vitest@4.0.3(terser@5.44.0))': dependencies: - '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.0.3 ast-v8-to-istanbul: 0.3.5 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.3 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.2.0 - magic-string: 0.30.19 magicast: 0.3.5 std-env: 3.9.0 - test-exclude: 7.0.1 - tinyrainbow: 2.0.0 - vitest: 3.2.4(terser@5.44.0) + tinyrainbow: 3.0.3 + vitest: 4.0.3(terser@5.44.0) transitivePeerDependencies: - supports-color - '@vitest/expect@3.2.4': + '@vitest/expect@4.0.3': dependencies: + '@standard-schema/spec': 1.0.0 '@types/chai': 5.2.2 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - tinyrainbow: 2.0.0 + '@vitest/spy': 4.0.3 + '@vitest/utils': 4.0.3 + chai: 6.2.0 + tinyrainbow: 3.0.3 - '@vitest/mocker@3.2.4(vite@7.1.6(terser@5.44.0))': + '@vitest/mocker@4.0.3(vite@7.1.6(terser@5.44.0))': dependencies: - '@vitest/spy': 3.2.4 + '@vitest/spy': 4.0.3 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: vite: 7.1.6(terser@5.44.0) - '@vitest/pretty-format@3.2.4': + '@vitest/pretty-format@4.0.3': dependencies: - tinyrainbow: 2.0.0 + tinyrainbow: 3.0.3 - '@vitest/runner@3.2.4': + '@vitest/runner@4.0.3': dependencies: - '@vitest/utils': 3.2.4 + '@vitest/utils': 4.0.3 pathe: 2.0.3 - strip-literal: 3.0.0 - '@vitest/snapshot@3.2.4': + '@vitest/snapshot@4.0.3': dependencies: - '@vitest/pretty-format': 3.2.4 + '@vitest/pretty-format': 4.0.3 magic-string: 0.30.19 pathe: 2.0.3 - '@vitest/spy@3.2.4': - dependencies: - tinyspy: 4.0.4 + '@vitest/spy@4.0.3': {} - '@vitest/utils@3.2.4': + '@vitest/utils@4.0.3': dependencies: - '@vitest/pretty-format': 3.2.4 - loupe: 3.2.1 - tinyrainbow: 2.0.0 + '@vitest/pretty-format': 4.0.3 + tinyrainbow: 3.0.3 acorn@8.15.0: {} @@ -1802,8 +1758,6 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - assertion-error@2.0.1: {} - ast-v8-to-istanbul@0.3.5: dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -1851,21 +1805,13 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - chai@5.3.3: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.2.1 - pathval: 2.0.1 + chai@6.2.0: {} chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - check-error@2.1.1: {} - chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -1937,7 +1883,9 @@ snapshots: optionalDependencies: supports-color: 5.5.0 - deep-eql@5.0.2: {} + debug@4.4.3: + dependencies: + ms: 2.1.3 delayed-stream@1.0.0: {} @@ -2121,7 +2069,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.31 - debug: 4.4.1(supports-color@5.5.0) + debug: 4.4.3 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -2168,8 +2116,6 @@ snapshots: lodash@4.17.21: {} - loupe@3.2.1: {} - lru-cache@10.4.3: {} magic-string@0.30.19: @@ -2253,8 +2199,6 @@ snapshots: pathe@2.0.3: {} - pathval@2.0.1: {} - picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -2385,10 +2329,6 @@ snapshots: dependencies: ansi-regex: 6.2.2 - strip-literal@3.0.0: - dependencies: - js-tokens: 9.0.1 - sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -2418,12 +2358,6 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - test-exclude@7.0.1: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 10.4.5 - minimatch: 9.0.5 - thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -2441,11 +2375,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinypool@1.1.1: {} - - tinyrainbow@2.0.0: {} - - tinyspy@4.0.4: {} + tinyrainbow@3.0.3: {} to-regex-range@5.0.1: dependencies: @@ -2500,27 +2430,6 @@ snapshots: validator@13.15.15: {} - vite-node@3.2.4(terser@5.44.0): - dependencies: - cac: 6.7.14 - debug: 4.4.1(supports-color@5.5.0) - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.1.6(terser@5.44.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - vite@7.1.6(terser@5.44.0): dependencies: esbuild: 0.25.9 @@ -2533,18 +2442,17 @@ snapshots: fsevents: 2.3.3 terser: 5.44.0 - vitest@3.2.4(terser@5.44.0): + vitest@4.0.3(terser@5.44.0): dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.6(terser@5.44.0)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - debug: 4.4.1(supports-color@5.5.0) + '@vitest/expect': 4.0.3 + '@vitest/mocker': 4.0.3(vite@7.1.6(terser@5.44.0)) + '@vitest/pretty-format': 4.0.3 + '@vitest/runner': 4.0.3 + '@vitest/snapshot': 4.0.3 + '@vitest/spy': 4.0.3 + '@vitest/utils': 4.0.3 + debug: 4.4.3 + es-module-lexer: 1.7.0 expect-type: 1.2.2 magic-string: 0.30.19 pathe: 2.0.3 @@ -2553,10 +2461,8 @@ snapshots: tinybench: 2.9.0 tinyexec: 0.3.2 tinyglobby: 0.2.15 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 + tinyrainbow: 3.0.3 vite: 7.1.6(terser@5.44.0) - vite-node: 3.2.4(terser@5.44.0) why-is-node-running: 2.3.0 transitivePeerDependencies: - jiti @@ -2625,4 +2531,4 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - zod@4.1.11: {} + zod@4.1.12: {} From 1468f9923db4b7580ee6ac4365a9564e6c02033d Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 28 Oct 2025 10:34:15 -0300 Subject: [PATCH 66/74] feat/added better support for unions in CTEs --- src/cteMaker.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/cteMaker.ts b/src/cteMaker.ts index c1fc40c..b054993 100644 --- a/src/cteMaker.ts +++ b/src/cteMaker.ts @@ -1,5 +1,6 @@ import type QueryDefinition from "./queryKinds/dml/dmlQueryDefinition.js"; import SelectQuery from "./queryKinds/dml/select.js"; +import Union from "./queryKinds/dml/union.js"; /** * Cte represents a Common Table Expression (CTE) in SQL. @@ -75,7 +76,9 @@ export class Cte { */ public build(): { text: string; values: any[] } { const recursiveStr = this.recursiveCte ? "RECURSIVE " : ""; - const query = this.query.build(); + const query = this.query instanceof Union + ? this.query.rawUnion() + : this.query.build(); return { text: `${recursiveStr}${this.name} AS (\n${query.text}\n)`, values: query.values, From 7a9b1bfb26aba37cc2bfe8953f8fbbd6264079ee Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 28 Oct 2025 11:01:47 -0300 Subject: [PATCH 67/74] fix/removed wrong draft code --- src/cteMaker.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/cteMaker.ts b/src/cteMaker.ts index b054993..c1fc40c 100644 --- a/src/cteMaker.ts +++ b/src/cteMaker.ts @@ -1,6 +1,5 @@ import type QueryDefinition from "./queryKinds/dml/dmlQueryDefinition.js"; import SelectQuery from "./queryKinds/dml/select.js"; -import Union from "./queryKinds/dml/union.js"; /** * Cte represents a Common Table Expression (CTE) in SQL. @@ -76,9 +75,7 @@ export class Cte { */ public build(): { text: string; values: any[] } { const recursiveStr = this.recursiveCte ? "RECURSIVE " : ""; - const query = this.query instanceof Union - ? this.query.rawUnion() - : this.query.build(); + const query = this.query.build(); return { text: `${recursiveStr}${this.name} AS (\n${query.text}\n)`, values: query.values, From 21cce8cf387bdfaabea33bd660062f88fcf204f7 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 28 Oct 2025 11:32:18 -0300 Subject: [PATCH 68/74] fix/fixed how unions and selects work in CTEs, disable analysis --- src/cteMaker.ts | 13 +++++++++++++ src/queryKinds/dml/union.ts | 35 ++++++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/cteMaker.ts b/src/cteMaker.ts index c1fc40c..d7a8ad3 100644 --- a/src/cteMaker.ts +++ b/src/cteMaker.ts @@ -1,5 +1,6 @@ import type QueryDefinition from "./queryKinds/dml/dmlQueryDefinition.js"; import SelectQuery from "./queryKinds/dml/select.js"; +import Union from "./queryKinds/dml/union.js"; /** * Cte represents a Common Table Expression (CTE) in SQL. @@ -75,7 +76,19 @@ export class Cte { */ public build(): { text: string; values: any[] } { const recursiveStr = this.recursiveCte ? "RECURSIVE " : ""; + if ( + this.query instanceof SelectQuery + || this.query instanceof Union + ) { + (this.query as any).disabledAnalysis = true; + } const query = this.query.build(); + if ( + this.query instanceof SelectQuery + || this.query instanceof Union + ) { + (this.query as any).disabledAnalysis = false; + } return { text: `${recursiveStr}${this.name} AS (\n${query.text}\n)`, values: query.values, diff --git a/src/queryKinds/dml/union.ts b/src/queryKinds/dml/union.ts index ff5fba8..7f504c9 100644 --- a/src/queryKinds/dml/union.ts +++ b/src/queryKinds/dml/union.ts @@ -67,6 +67,9 @@ export default class Union extends DmlQueryDefinition { /** Having statement for the union query */ private havingStatement: Statement | null = null; + /** Where statement for the union query */ + private disabledAnalysis: boolean = false; + /** * Checks if all added SELECT queries have the same number of fields. * This is important for ensuring that the UNION operation is valid. @@ -527,19 +530,29 @@ export default class Union extends DmlQueryDefinition { const finalValues = [...values, ...whereValues, ...havingValues]; - const analyzed = this.reAnalyzeParsedQueryForDuplicateParams( - union, - finalValues, - deepAnalysis, - ); + if (!deepAnalysis) { + const analyzed = this.reAnalyzeParsedQueryForDuplicateParams( + union, + finalValues, + deepAnalysis, + ); - this.builtQuery = analyzed.text; - this.builtParams = analyzed.values; + this.builtQuery = analyzed.text; + this.builtParams = analyzed.values; - return { - text: this.builtQuery, - values: this.builtParams, - }; + return { + text: this.builtQuery, + values: this.builtParams, + }; + } else { + this.builtQuery = union; + this.builtParams = finalValues; + + return { + text: this.builtQuery, + values: this.builtParams, + }; + } } /** From 028caaeec06730b3db6c7aeea74ed7c4f6f721a0 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Tue, 28 Oct 2025 11:34:11 -0300 Subject: [PATCH 69/74] fix/fixed naming --- src/queryKinds/dml/union.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/queryKinds/dml/union.ts b/src/queryKinds/dml/union.ts index 7f504c9..8b196b8 100644 --- a/src/queryKinds/dml/union.ts +++ b/src/queryKinds/dml/union.ts @@ -530,7 +530,7 @@ export default class Union extends DmlQueryDefinition { const finalValues = [...values, ...whereValues, ...havingValues]; - if (!deepAnalysis) { + if (!this.disabledAnalysis) { const analyzed = this.reAnalyzeParsedQueryForDuplicateParams( union, finalValues, From 2c70c2ff03070bbc5dda0ccd9e1722b1b2430199 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Thu, 30 Oct 2025 03:03:41 -0300 Subject: [PATCH 70/74] bookkeeping/updated dependencies --- package-lock.json | 260 +++++++++++++++++++++---------------------- package.json | 8 +- pnpm-lock.yaml | 274 +++++++++++++++++++++++----------------------- 3 files changed, 271 insertions(+), 271 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9c08e18..e0fd28a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "0.7.1", "license": "Apache-2.0", "devDependencies": { - "@biomejs/biome": "^2.3.1", - "@swc/core": "^1.13.5", - "@vitest/coverage-v8": "^4.0.3", + "@biomejs/biome": "^2.3.2", + "@swc/core": "^1.14.0", + "@vitest/coverage-v8": "^4.0.5", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "concurrently": "^9.2.1", @@ -21,7 +21,7 @@ "tslib": "^2.8.1", "tsup": "^8.5.0", "typescript": "^5.9.3", - "vitest": "^4.0.3", + "vitest": "^4.0.5", "wait-on": "^9.0.1", "zod": "^4.1.12" }, @@ -106,9 +106,9 @@ } }, "node_modules/@biomejs/biome": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.1.tgz", - "integrity": "sha512-A29evf1R72V5bo4o2EPxYMm5mtyGvzp2g+biZvRFx29nWebGyyeOSsDWGx3tuNNMFRepGwxmA9ZQ15mzfabK2w==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.2.tgz", + "integrity": "sha512-8e9tzamuDycx7fdrcJ/F/GDZ8SYukc5ud6tDicjjFqURKYFSWMl0H0iXNXZEGmcmNUmABgGuHThPykcM41INgg==", "dev": true, "license": "MIT OR Apache-2.0", "bin": { @@ -122,20 +122,20 @@ "url": "https://opencollective.com/biome" }, "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.3.1", - "@biomejs/cli-darwin-x64": "2.3.1", - "@biomejs/cli-linux-arm64": "2.3.1", - "@biomejs/cli-linux-arm64-musl": "2.3.1", - "@biomejs/cli-linux-x64": "2.3.1", - "@biomejs/cli-linux-x64-musl": "2.3.1", - "@biomejs/cli-win32-arm64": "2.3.1", - "@biomejs/cli-win32-x64": "2.3.1" + "@biomejs/cli-darwin-arm64": "2.3.2", + "@biomejs/cli-darwin-x64": "2.3.2", + "@biomejs/cli-linux-arm64": "2.3.2", + "@biomejs/cli-linux-arm64-musl": "2.3.2", + "@biomejs/cli-linux-x64": "2.3.2", + "@biomejs/cli-linux-x64-musl": "2.3.2", + "@biomejs/cli-win32-arm64": "2.3.2", + "@biomejs/cli-win32-x64": "2.3.2" } }, "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.1.tgz", - "integrity": "sha512-ombSf3MnTUueiYGN1SeI9tBCsDUhpWzOwS63Dove42osNh0PfE1cUtHFx6eZ1+MYCCLwXzlFlYFdrJ+U7h6LcA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.2.tgz", + "integrity": "sha512-4LECm4kc3If0JISai4c3KWQzukoUdpxy4fRzlrPcrdMSRFksR9ZoXK7JBcPuLBmd2SoT4/d7CQS33VnZpgBjew==", "cpu": [ "arm64" ], @@ -150,9 +150,9 @@ } }, "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.1.tgz", - "integrity": "sha512-pcOfwyoQkrkbGvXxRvZNe5qgD797IowpJPovPX5biPk2FwMEV+INZqfCaz4G5bVq9hYnjwhRMamg11U4QsRXrQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.2.tgz", + "integrity": "sha512-jNMnfwHT4N3wi+ypRfMTjLGnDmKYGzxVr1EYAPBcauRcDnICFXN81wD6wxJcSUrLynoyyYCdfW6vJHS/IAoTDA==", "cpu": [ "x64" ], @@ -167,9 +167,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.1.tgz", - "integrity": "sha512-td5O8pFIgLs8H1sAZsD6v+5quODihyEw4nv2R8z7swUfIK1FKk+15e4eiYVLcAE4jUqngvh4j3JCNgg0Y4o4IQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.2.tgz", + "integrity": "sha512-amnqvk+gWybbQleRRq8TMe0rIv7GHss8mFJEaGuEZYWg1Tw14YKOkeo8h6pf1c+d3qR+JU4iT9KXnBKGON4klw==", "cpu": [ "arm64" ], @@ -184,9 +184,9 @@ } }, "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.1.tgz", - "integrity": "sha512-+DZYv8l7FlUtTrWs1Tdt1KcNCAmRO87PyOnxKGunbWm5HKg1oZBSbIIPkjrCtDZaeqSG1DiGx7qF+CPsquQRcg==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.2.tgz", + "integrity": "sha512-2Zz4usDG1GTTPQnliIeNx6eVGGP2ry5vE/v39nT73a3cKN6t5H5XxjcEoZZh62uVZvED7hXXikclvI64vZkYqw==", "cpu": [ "arm64" ], @@ -201,9 +201,9 @@ } }, "node_modules/@biomejs/cli-linux-x64": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.1.tgz", - "integrity": "sha512-PYWgEO7up7XYwSAArOpzsVCiqxBCXy53gsReAb1kKYIyXaoAlhBaBMvxR/k2Rm9aTuZ662locXUmPk/Aj+Xu+Q==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.2.tgz", + "integrity": "sha512-8BG/vRAhFz1pmuyd24FQPhNeueLqPtwvZk6yblABY2gzL2H8fLQAF/Z2OPIc+BPIVPld+8cSiKY/KFh6k81xfA==", "cpu": [ "x64" ], @@ -218,9 +218,9 @@ } }, "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.1.tgz", - "integrity": "sha512-Y3Ob4nqgv38Mh+6EGHltuN+Cq8aj/gyMTJYzkFZV2AEj+9XzoXB9VNljz9pjfFNHUxvLEV4b55VWyxozQTBaUQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.2.tgz", + "integrity": "sha512-gzB19MpRdTuOuLtPpFBGrV3Lq424gHyq2lFj8wfX9tvLMLdmA/R9C7k/mqBp/spcbWuHeIEKgEs3RviOPcWGBA==", "cpu": [ "x64" ], @@ -235,9 +235,9 @@ } }, "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.1.tgz", - "integrity": "sha512-RHIG/zgo+69idUqVvV3n8+j58dKYABRpMyDmfWu2TITC+jwGPiEaT0Q3RKD+kQHiS80mpBrST0iUGeEXT0bU9A==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.2.tgz", + "integrity": "sha512-lCruqQlfWjhMlOdyf5pDHOxoNm4WoyY2vZ4YN33/nuZBRstVDuqPPjS0yBkbUlLEte11FbpW+wWSlfnZfSIZvg==", "cpu": [ "arm64" ], @@ -252,9 +252,9 @@ } }, "node_modules/@biomejs/cli-win32-x64": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.1.tgz", - "integrity": "sha512-izl30JJ5Dp10mi90Eko47zhxE6pYyWPcnX1NQxKpL/yMhXxf95oLTzfpu4q+MDBh/gemNqyJEwjBpe0MT5iWPA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.2.tgz", + "integrity": "sha512-6Ee9P26DTb4D8sN9nXxgbi9Dw5vSOfH98M7UlmkjKB2vtUbrRqCbZiNfryGiwnPIpd6YUoTl7rLVD2/x1CyEHQ==", "cpu": [ "x64" ], @@ -1159,16 +1159,16 @@ "license": "MIT" }, "node_modules/@swc/core": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.13.5.tgz", - "integrity": "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.14.0.tgz", + "integrity": "sha512-oExhY90bes5pDTVrei0xlMVosTxwd/NMafIpqsC4dMbRYZ5KB981l/CX8tMnGsagTplj/RcG9BeRYmV6/J5m3w==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "peer": true, "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.24" + "@swc/types": "^0.1.25" }, "engines": { "node": ">=10" @@ -1178,16 +1178,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.13.5", - "@swc/core-darwin-x64": "1.13.5", - "@swc/core-linux-arm-gnueabihf": "1.13.5", - "@swc/core-linux-arm64-gnu": "1.13.5", - "@swc/core-linux-arm64-musl": "1.13.5", - "@swc/core-linux-x64-gnu": "1.13.5", - "@swc/core-linux-x64-musl": "1.13.5", - "@swc/core-win32-arm64-msvc": "1.13.5", - "@swc/core-win32-ia32-msvc": "1.13.5", - "@swc/core-win32-x64-msvc": "1.13.5" + "@swc/core-darwin-arm64": "1.14.0", + "@swc/core-darwin-x64": "1.14.0", + "@swc/core-linux-arm-gnueabihf": "1.14.0", + "@swc/core-linux-arm64-gnu": "1.14.0", + "@swc/core-linux-arm64-musl": "1.14.0", + "@swc/core-linux-x64-gnu": "1.14.0", + "@swc/core-linux-x64-musl": "1.14.0", + "@swc/core-win32-arm64-msvc": "1.14.0", + "@swc/core-win32-ia32-msvc": "1.14.0", + "@swc/core-win32-x64-msvc": "1.14.0" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -1199,9 +1199,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.13.5.tgz", - "integrity": "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.14.0.tgz", + "integrity": "sha512-uHPC8rlCt04nvYNczWzKVdgnRhxCa3ndKTBBbBpResOZsRmiwRAvByIGh599j+Oo6Z5eyTPrgY+XfJzVmXnN7Q==", "cpu": [ "arm64" ], @@ -1216,9 +1216,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.13.5.tgz", - "integrity": "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.14.0.tgz", + "integrity": "sha512-2SHrlpl68vtePRknv9shvM9YKKg7B9T13tcTg9aFCwR318QTYo+FzsKGmQSv9ox/Ua0Q2/5y2BNjieffJoo4nA==", "cpu": [ "x64" ], @@ -1233,9 +1233,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.13.5.tgz", - "integrity": "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.14.0.tgz", + "integrity": "sha512-SMH8zn01dxt809svetnxpeg/jWdpi6dqHKO3Eb11u4OzU2PK7I5uKS6gf2hx5LlTbcJMFKULZiVwjlQLe8eqtg==", "cpu": [ "arm" ], @@ -1250,9 +1250,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.13.5.tgz", - "integrity": "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.14.0.tgz", + "integrity": "sha512-q2JRu2D8LVqGeHkmpVCljVNltG0tB4o4eYg+dElFwCS8l2Mnt9qurMCxIeo9mgoqz0ax+k7jWtIRHktnVCbjvQ==", "cpu": [ "arm64" ], @@ -1267,9 +1267,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.13.5.tgz", - "integrity": "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.14.0.tgz", + "integrity": "sha512-uofpVoPCEUjYIv454ZEZ3sLgMD17nIwlz2z7bsn7rl301Kt/01umFA7MscUovFfAK2IRGck6XB+uulMu6aFhKQ==", "cpu": [ "arm64" ], @@ -1284,9 +1284,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.13.5.tgz", - "integrity": "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.14.0.tgz", + "integrity": "sha512-quTTx1Olm05fBfv66DEBuOsOgqdypnZ/1Bh3yGXWY7ANLFeeRpCDZpljD9BSjdsNdPOlwJmEUZXMHtGm3v1TZQ==", "cpu": [ "x64" ], @@ -1301,9 +1301,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.13.5.tgz", - "integrity": "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.14.0.tgz", + "integrity": "sha512-caaNAu+aIqT8seLtCf08i8C3/UC5ttQujUjejhMcuS1/LoCKtNiUs4VekJd2UGt+pyuuSrQ6dKl8CbCfWvWeXw==", "cpu": [ "x64" ], @@ -1318,9 +1318,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.13.5.tgz", - "integrity": "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.14.0.tgz", + "integrity": "sha512-EeW3jFlT3YNckJ6V/JnTfGcX7UHGyh6/AiCPopZ1HNaGiXVCKHPpVQZicmtyr/UpqxCXLrTgjHOvyMke7YN26A==", "cpu": [ "arm64" ], @@ -1335,9 +1335,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.13.5.tgz", - "integrity": "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.14.0.tgz", + "integrity": "sha512-dPai3KUIcihV5hfoO4QNQF5HAaw8+2bT7dvi8E5zLtecW2SfL3mUZipzampXq5FHll0RSCLzlrXnSx+dBRZIIQ==", "cpu": [ "ia32" ], @@ -1352,9 +1352,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.13.5.tgz", - "integrity": "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.14.0.tgz", + "integrity": "sha512-nm+JajGrTqUA6sEHdghDlHMNfH1WKSiuvljhdmBACW4ta4LC3gKurX2qZuiBARvPkephW9V/i5S8QPY1PzFEqg==", "cpu": [ "x64" ], @@ -1418,14 +1418,14 @@ "license": "MIT" }, "node_modules/@vitest/coverage-v8": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.3.tgz", - "integrity": "sha512-I+MlLwyJRBjmJr1kFYSxoseINbIdpxIAeK10jmXgB0FUtIfdYsvM3lGAvBu5yk8WPyhefzdmbCHCc1idFbNRcg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.5.tgz", + "integrity": "sha512-Yn5Dx0UVvllE3uatQw+ftObWtM/TjAOdbd8WvygaR04iyFXdNmtvZ/nJ2/JndyzfPQtbAWw0F+GJY5+lgM/7qg==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.0.3", + "@vitest/utils": "4.0.5", "ast-v8-to-istanbul": "^0.3.5", "debug": "^4.4.3", "istanbul-lib-coverage": "^3.2.2", @@ -1440,8 +1440,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.0.3", - "vitest": "4.0.3" + "@vitest/browser": "4.0.5", + "vitest": "4.0.5" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -1450,16 +1450,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.3.tgz", - "integrity": "sha512-v3eSDx/bF25pzar6aEJrrdTXJduEBU3uSGXHslIdGIpJVP8tQQHV6x1ZfzbFQ/bLIomLSbR/2ZCfnaEGkWkiVQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.5.tgz", + "integrity": "sha512-DJctLVlKoddvP/G389oGmKWNG6GD9frm2FPXARziU80Rjo7SIYxQzb2YFzmQ4fVD3Q5utUYY8nUmWrqsuIlIXQ==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.3", - "@vitest/utils": "4.0.3", + "@vitest/spy": "4.0.5", + "@vitest/utils": "4.0.5", "chai": "^6.0.1", "tinyrainbow": "^3.0.3" }, @@ -1468,13 +1468,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.3.tgz", - "integrity": "sha512-evZcRspIPbbiJEe748zI2BRu94ThCBE+RkjCpVF8yoVYuTV7hMe+4wLF/7K86r8GwJHSmAPnPbZhpXWWrg1qbA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.5.tgz", + "integrity": "sha512-iYHIy72LfbK+mL5W8zXROp6oOcJKXWeKcNjcPPsqoa18qIEDrhB6/Z08o0wRajTd6SSSDNw8NCSIHVNOMpz0mw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.0.3", + "@vitest/spy": "4.0.5", "estree-walker": "^3.0.3", "magic-string": "^0.30.19" }, @@ -1495,9 +1495,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.3.tgz", - "integrity": "sha512-N7gly/DRXzxa9w9sbDXwD9QNFYP2hw90LLLGDobPNwiWgyW95GMxsCt29/COIKKh3P7XJICR38PSDePenMBtsw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.5.tgz", + "integrity": "sha512-t1T/sSdsYyNc5AZl0EMeD0jW9cpJe2cODP0R++ZQe1kTkpgrwEfxGFR/yCG4w8ZybizbXRTHU7lE8sTDD/QsGw==", "dev": true, "license": "MIT", "dependencies": { @@ -1508,13 +1508,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.3.tgz", - "integrity": "sha512-1/aK6fPM0lYXWyGKwop2Gbvz1plyTps/HDbIIJXYtJtspHjpXIeB3If07eWpVH4HW7Rmd3Rl+IS/+zEAXrRtXA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.5.tgz", + "integrity": "sha512-CQVVe+YEeKSiFBD5gBAmRDQglm4PnMBYzeTmt06t5iWtsUN9StQeeKhYCea/oaqBYilf8sARG6fSctUcEL/UmQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.0.3", + "@vitest/utils": "4.0.5", "pathe": "^2.0.3" }, "funding": { @@ -1522,13 +1522,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.3.tgz", - "integrity": "sha512-amnYmvZ5MTjNCP1HZmdeczAPLRD6iOm9+2nMRUGxbe/6sQ0Ymur0NnR9LIrWS8JA3wKE71X25D6ya/3LN9YytA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.5.tgz", + "integrity": "sha512-jfmSAeR6xYNEvcD+/RxFGA1bzpqHtkVhgxo2cxXia+Q3xX7m6GpZij07rz+WyQcA/xEGn4eIS1OItkMyWsGBmQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.3", + "@vitest/pretty-format": "4.0.5", "magic-string": "^0.30.19", "pathe": "^2.0.3" }, @@ -1537,9 +1537,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.3.tgz", - "integrity": "sha512-82vVL8Cqz7rbXaNUl35V2G7xeNMAjBdNOVaHbrzznT9BmiCiPOzhf0FhU3eP41nP1bLDm/5wWKZqkG4nyU95DQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.5.tgz", + "integrity": "sha512-TUmVQpAQign7r8+EnZsgTF3vY9BdGofTUge1rGNbnHn2IN3FChiQoT9lrPz7A7AVUZJU2LAZXl4v66HhsNMhoA==", "dev": true, "license": "MIT", "funding": { @@ -1547,13 +1547,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.3.tgz", - "integrity": "sha512-qV6KJkq8W3piW6MDIbGOmn1xhvcW4DuA07alqaQ+vdx7YA49J85pnwnxigZVQFQw3tWnQNRKWwhz5wbP6iv/GQ==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.5.tgz", + "integrity": "sha512-V5RndUgCB5/AfNvK9zxGCrRs99IrPYtMTIdUzJMMFs9nrmE5JXExIEfjVtUteyTRiLfCm+dCRMHf/Uu7Mm8/dg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.0.3", + "@vitest/pretty-format": "4.0.5", "tinyrainbow": "^3.0.3" }, "funding": { @@ -3978,20 +3978,20 @@ } }, "node_modules/vitest": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.3.tgz", - "integrity": "sha512-IUSop8jgaT7w0g1yOM/35qVtKjr/8Va4PrjzH1OUb0YH4c3OXB2lCZDkMAB6glA8T5w8S164oJGsbcmAecr4sA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.5.tgz", + "integrity": "sha512-4H+J28MI5oeYgGg3h5BFSkQ1g/2GKK1IR8oorH3a6EQQbb7CwjbnyBjH4PGxw9/6vpwAPNzaeUMp4Js4WJmdXQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@vitest/expect": "4.0.3", - "@vitest/mocker": "4.0.3", - "@vitest/pretty-format": "4.0.3", - "@vitest/runner": "4.0.3", - "@vitest/snapshot": "4.0.3", - "@vitest/spy": "4.0.3", - "@vitest/utils": "4.0.3", + "@vitest/expect": "4.0.5", + "@vitest/mocker": "4.0.5", + "@vitest/pretty-format": "4.0.5", + "@vitest/runner": "4.0.5", + "@vitest/snapshot": "4.0.5", + "@vitest/spy": "4.0.5", + "@vitest/utils": "4.0.5", "debug": "^4.4.3", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", @@ -4019,10 +4019,10 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.3", - "@vitest/browser-preview": "4.0.3", - "@vitest/browser-webdriverio": "4.0.3", - "@vitest/ui": "4.0.3", + "@vitest/browser-playwright": "4.0.5", + "@vitest/browser-preview": "4.0.5", + "@vitest/browser-webdriverio": "4.0.5", + "@vitest/ui": "4.0.5", "happy-dom": "*", "jsdom": "*" }, diff --git a/package.json b/package.json index 493b8b9..6494679 100644 --- a/package.json +++ b/package.json @@ -76,9 +76,9 @@ "author": "Nicolas R. M. Dias ", "license": "Apache-2.0", "devDependencies": { - "@biomejs/biome": "^2.3.1", - "@swc/core": "^1.13.5", - "@vitest/coverage-v8": "^4.0.3", + "@biomejs/biome": "^2.3.2", + "@swc/core": "^1.14.0", + "@vitest/coverage-v8": "^4.0.5", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "concurrently": "^9.2.1", @@ -88,7 +88,7 @@ "tslib": "^2.8.1", "tsup": "^8.5.0", "typescript": "^5.9.3", - "vitest": "^4.0.3", + "vitest": "^4.0.5", "wait-on": "^9.0.1", "zod": "^4.1.12" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a58a337..a871a06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,14 +9,14 @@ importers: .: devDependencies: '@biomejs/biome': - specifier: ^2.3.1 - version: 2.3.1 + specifier: ^2.3.2 + version: 2.3.2 '@swc/core': - specifier: ^1.13.5 - version: 1.13.5 + specifier: ^1.14.0 + version: 1.14.0 '@vitest/coverage-v8': - specifier: ^4.0.3 - version: 4.0.3(vitest@4.0.3(terser@5.44.0)) + specifier: ^4.0.5 + version: 4.0.5(vitest@4.0.5(terser@5.44.0)) class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -40,13 +40,13 @@ importers: version: 2.8.1 tsup: specifier: ^8.5.0 - version: 8.5.0(@swc/core@1.13.5)(postcss@8.5.6)(typescript@5.9.3) + version: 8.5.0(@swc/core@1.14.0)(postcss@8.5.6)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 vitest: - specifier: ^4.0.3 - version: 4.0.3(terser@5.44.0) + specifier: ^4.0.5 + version: 4.0.5(terser@5.44.0) wait-on: specifier: ^9.0.1 version: 9.0.1 @@ -77,55 +77,55 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@biomejs/biome@2.3.1': - resolution: {integrity: sha512-A29evf1R72V5bo4o2EPxYMm5mtyGvzp2g+biZvRFx29nWebGyyeOSsDWGx3tuNNMFRepGwxmA9ZQ15mzfabK2w==} + '@biomejs/biome@2.3.2': + resolution: {integrity: sha512-8e9tzamuDycx7fdrcJ/F/GDZ8SYukc5ud6tDicjjFqURKYFSWMl0H0iXNXZEGmcmNUmABgGuHThPykcM41INgg==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.3.1': - resolution: {integrity: sha512-ombSf3MnTUueiYGN1SeI9tBCsDUhpWzOwS63Dove42osNh0PfE1cUtHFx6eZ1+MYCCLwXzlFlYFdrJ+U7h6LcA==} + '@biomejs/cli-darwin-arm64@2.3.2': + resolution: {integrity: sha512-4LECm4kc3If0JISai4c3KWQzukoUdpxy4fRzlrPcrdMSRFksR9ZoXK7JBcPuLBmd2SoT4/d7CQS33VnZpgBjew==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.3.1': - resolution: {integrity: sha512-pcOfwyoQkrkbGvXxRvZNe5qgD797IowpJPovPX5biPk2FwMEV+INZqfCaz4G5bVq9hYnjwhRMamg11U4QsRXrQ==} + '@biomejs/cli-darwin-x64@2.3.2': + resolution: {integrity: sha512-jNMnfwHT4N3wi+ypRfMTjLGnDmKYGzxVr1EYAPBcauRcDnICFXN81wD6wxJcSUrLynoyyYCdfW6vJHS/IAoTDA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.3.1': - resolution: {integrity: sha512-+DZYv8l7FlUtTrWs1Tdt1KcNCAmRO87PyOnxKGunbWm5HKg1oZBSbIIPkjrCtDZaeqSG1DiGx7qF+CPsquQRcg==} + '@biomejs/cli-linux-arm64-musl@2.3.2': + resolution: {integrity: sha512-2Zz4usDG1GTTPQnliIeNx6eVGGP2ry5vE/v39nT73a3cKN6t5H5XxjcEoZZh62uVZvED7hXXikclvI64vZkYqw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.3.1': - resolution: {integrity: sha512-td5O8pFIgLs8H1sAZsD6v+5quODihyEw4nv2R8z7swUfIK1FKk+15e4eiYVLcAE4jUqngvh4j3JCNgg0Y4o4IQ==} + '@biomejs/cli-linux-arm64@2.3.2': + resolution: {integrity: sha512-amnqvk+gWybbQleRRq8TMe0rIv7GHss8mFJEaGuEZYWg1Tw14YKOkeo8h6pf1c+d3qR+JU4iT9KXnBKGON4klw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.3.1': - resolution: {integrity: sha512-Y3Ob4nqgv38Mh+6EGHltuN+Cq8aj/gyMTJYzkFZV2AEj+9XzoXB9VNljz9pjfFNHUxvLEV4b55VWyxozQTBaUQ==} + '@biomejs/cli-linux-x64-musl@2.3.2': + resolution: {integrity: sha512-gzB19MpRdTuOuLtPpFBGrV3Lq424gHyq2lFj8wfX9tvLMLdmA/R9C7k/mqBp/spcbWuHeIEKgEs3RviOPcWGBA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.3.1': - resolution: {integrity: sha512-PYWgEO7up7XYwSAArOpzsVCiqxBCXy53gsReAb1kKYIyXaoAlhBaBMvxR/k2Rm9aTuZ662locXUmPk/Aj+Xu+Q==} + '@biomejs/cli-linux-x64@2.3.2': + resolution: {integrity: sha512-8BG/vRAhFz1pmuyd24FQPhNeueLqPtwvZk6yblABY2gzL2H8fLQAF/Z2OPIc+BPIVPld+8cSiKY/KFh6k81xfA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.3.1': - resolution: {integrity: sha512-RHIG/zgo+69idUqVvV3n8+j58dKYABRpMyDmfWu2TITC+jwGPiEaT0Q3RKD+kQHiS80mpBrST0iUGeEXT0bU9A==} + '@biomejs/cli-win32-arm64@2.3.2': + resolution: {integrity: sha512-lCruqQlfWjhMlOdyf5pDHOxoNm4WoyY2vZ4YN33/nuZBRstVDuqPPjS0yBkbUlLEte11FbpW+wWSlfnZfSIZvg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.3.1': - resolution: {integrity: sha512-izl30JJ5Dp10mi90Eko47zhxE6pYyWPcnX1NQxKpL/yMhXxf95oLTzfpu4q+MDBh/gemNqyJEwjBpe0MT5iWPA==} + '@biomejs/cli-win32-x64@2.3.2': + resolution: {integrity: sha512-6Ee9P26DTb4D8sN9nXxgbi9Dw5vSOfH98M7UlmkjKB2vtUbrRqCbZiNfryGiwnPIpd6YUoTl7rLVD2/x1CyEHQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -438,68 +438,68 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@swc/core-darwin-arm64@1.13.5': - resolution: {integrity: sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ==} + '@swc/core-darwin-arm64@1.14.0': + resolution: {integrity: sha512-uHPC8rlCt04nvYNczWzKVdgnRhxCa3ndKTBBbBpResOZsRmiwRAvByIGh599j+Oo6Z5eyTPrgY+XfJzVmXnN7Q==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.13.5': - resolution: {integrity: sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng==} + '@swc/core-darwin-x64@1.14.0': + resolution: {integrity: sha512-2SHrlpl68vtePRknv9shvM9YKKg7B9T13tcTg9aFCwR318QTYo+FzsKGmQSv9ox/Ua0Q2/5y2BNjieffJoo4nA==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.13.5': - resolution: {integrity: sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ==} + '@swc/core-linux-arm-gnueabihf@1.14.0': + resolution: {integrity: sha512-SMH8zn01dxt809svetnxpeg/jWdpi6dqHKO3Eb11u4OzU2PK7I5uKS6gf2hx5LlTbcJMFKULZiVwjlQLe8eqtg==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.13.5': - resolution: {integrity: sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw==} + '@swc/core-linux-arm64-gnu@1.14.0': + resolution: {integrity: sha512-q2JRu2D8LVqGeHkmpVCljVNltG0tB4o4eYg+dElFwCS8l2Mnt9qurMCxIeo9mgoqz0ax+k7jWtIRHktnVCbjvQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.13.5': - resolution: {integrity: sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ==} + '@swc/core-linux-arm64-musl@1.14.0': + resolution: {integrity: sha512-uofpVoPCEUjYIv454ZEZ3sLgMD17nIwlz2z7bsn7rl301Kt/01umFA7MscUovFfAK2IRGck6XB+uulMu6aFhKQ==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.13.5': - resolution: {integrity: sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA==} + '@swc/core-linux-x64-gnu@1.14.0': + resolution: {integrity: sha512-quTTx1Olm05fBfv66DEBuOsOgqdypnZ/1Bh3yGXWY7ANLFeeRpCDZpljD9BSjdsNdPOlwJmEUZXMHtGm3v1TZQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.13.5': - resolution: {integrity: sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q==} + '@swc/core-linux-x64-musl@1.14.0': + resolution: {integrity: sha512-caaNAu+aIqT8seLtCf08i8C3/UC5ttQujUjejhMcuS1/LoCKtNiUs4VekJd2UGt+pyuuSrQ6dKl8CbCfWvWeXw==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.13.5': - resolution: {integrity: sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw==} + '@swc/core-win32-arm64-msvc@1.14.0': + resolution: {integrity: sha512-EeW3jFlT3YNckJ6V/JnTfGcX7UHGyh6/AiCPopZ1HNaGiXVCKHPpVQZicmtyr/UpqxCXLrTgjHOvyMke7YN26A==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.13.5': - resolution: {integrity: sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw==} + '@swc/core-win32-ia32-msvc@1.14.0': + resolution: {integrity: sha512-dPai3KUIcihV5hfoO4QNQF5HAaw8+2bT7dvi8E5zLtecW2SfL3mUZipzampXq5FHll0RSCLzlrXnSx+dBRZIIQ==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.13.5': - resolution: {integrity: sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q==} + '@swc/core-win32-x64-msvc@1.14.0': + resolution: {integrity: sha512-nm+JajGrTqUA6sEHdghDlHMNfH1WKSiuvljhdmBACW4ta4LC3gKurX2qZuiBARvPkephW9V/i5S8QPY1PzFEqg==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.13.5': - resolution: {integrity: sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ==} + '@swc/core@1.14.0': + resolution: {integrity: sha512-oExhY90bes5pDTVrei0xlMVosTxwd/NMafIpqsC4dMbRYZ5KB981l/CX8tMnGsagTplj/RcG9BeRYmV6/J5m3w==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -525,20 +525,20 @@ packages: '@types/validator@13.15.3': resolution: {integrity: sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==} - '@vitest/coverage-v8@4.0.3': - resolution: {integrity: sha512-I+MlLwyJRBjmJr1kFYSxoseINbIdpxIAeK10jmXgB0FUtIfdYsvM3lGAvBu5yk8WPyhefzdmbCHCc1idFbNRcg==} + '@vitest/coverage-v8@4.0.5': + resolution: {integrity: sha512-Yn5Dx0UVvllE3uatQw+ftObWtM/TjAOdbd8WvygaR04iyFXdNmtvZ/nJ2/JndyzfPQtbAWw0F+GJY5+lgM/7qg==} peerDependencies: - '@vitest/browser': 4.0.3 - vitest: 4.0.3 + '@vitest/browser': 4.0.5 + vitest: 4.0.5 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.0.3': - resolution: {integrity: sha512-v3eSDx/bF25pzar6aEJrrdTXJduEBU3uSGXHslIdGIpJVP8tQQHV6x1ZfzbFQ/bLIomLSbR/2ZCfnaEGkWkiVQ==} + '@vitest/expect@4.0.5': + resolution: {integrity: sha512-DJctLVlKoddvP/G389oGmKWNG6GD9frm2FPXARziU80Rjo7SIYxQzb2YFzmQ4fVD3Q5utUYY8nUmWrqsuIlIXQ==} - '@vitest/mocker@4.0.3': - resolution: {integrity: sha512-evZcRspIPbbiJEe748zI2BRu94ThCBE+RkjCpVF8yoVYuTV7hMe+4wLF/7K86r8GwJHSmAPnPbZhpXWWrg1qbA==} + '@vitest/mocker@4.0.5': + resolution: {integrity: sha512-iYHIy72LfbK+mL5W8zXROp6oOcJKXWeKcNjcPPsqoa18qIEDrhB6/Z08o0wRajTd6SSSDNw8NCSIHVNOMpz0mw==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -548,20 +548,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.0.3': - resolution: {integrity: sha512-N7gly/DRXzxa9w9sbDXwD9QNFYP2hw90LLLGDobPNwiWgyW95GMxsCt29/COIKKh3P7XJICR38PSDePenMBtsw==} + '@vitest/pretty-format@4.0.5': + resolution: {integrity: sha512-t1T/sSdsYyNc5AZl0EMeD0jW9cpJe2cODP0R++ZQe1kTkpgrwEfxGFR/yCG4w8ZybizbXRTHU7lE8sTDD/QsGw==} - '@vitest/runner@4.0.3': - resolution: {integrity: sha512-1/aK6fPM0lYXWyGKwop2Gbvz1plyTps/HDbIIJXYtJtspHjpXIeB3If07eWpVH4HW7Rmd3Rl+IS/+zEAXrRtXA==} + '@vitest/runner@4.0.5': + resolution: {integrity: sha512-CQVVe+YEeKSiFBD5gBAmRDQglm4PnMBYzeTmt06t5iWtsUN9StQeeKhYCea/oaqBYilf8sARG6fSctUcEL/UmQ==} - '@vitest/snapshot@4.0.3': - resolution: {integrity: sha512-amnYmvZ5MTjNCP1HZmdeczAPLRD6iOm9+2nMRUGxbe/6sQ0Ymur0NnR9LIrWS8JA3wKE71X25D6ya/3LN9YytA==} + '@vitest/snapshot@4.0.5': + resolution: {integrity: sha512-jfmSAeR6xYNEvcD+/RxFGA1bzpqHtkVhgxo2cxXia+Q3xX7m6GpZij07rz+WyQcA/xEGn4eIS1OItkMyWsGBmQ==} - '@vitest/spy@4.0.3': - resolution: {integrity: sha512-82vVL8Cqz7rbXaNUl35V2G7xeNMAjBdNOVaHbrzznT9BmiCiPOzhf0FhU3eP41nP1bLDm/5wWKZqkG4nyU95DQ==} + '@vitest/spy@4.0.5': + resolution: {integrity: sha512-TUmVQpAQign7r8+EnZsgTF3vY9BdGofTUge1rGNbnHn2IN3FChiQoT9lrPz7A7AVUZJU2LAZXl4v66HhsNMhoA==} - '@vitest/utils@4.0.3': - resolution: {integrity: sha512-qV6KJkq8W3piW6MDIbGOmn1xhvcW4DuA07alqaQ+vdx7YA49J85pnwnxigZVQFQw3tWnQNRKWwhz5wbP6iv/GQ==} + '@vitest/utils@4.0.5': + resolution: {integrity: sha512-V5RndUgCB5/AfNvK9zxGCrRs99IrPYtMTIdUzJMMFs9nrmE5JXExIEfjVtUteyTRiLfCm+dCRMHf/Uu7Mm8/dg==} acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} @@ -1301,18 +1301,18 @@ packages: yaml: optional: true - vitest@4.0.3: - resolution: {integrity: sha512-IUSop8jgaT7w0g1yOM/35qVtKjr/8Va4PrjzH1OUb0YH4c3OXB2lCZDkMAB6glA8T5w8S164oJGsbcmAecr4sA==} + vitest@4.0.5: + resolution: {integrity: sha512-4H+J28MI5oeYgGg3h5BFSkQ1g/2GKK1IR8oorH3a6EQQbb7CwjbnyBjH4PGxw9/6vpwAPNzaeUMp4Js4WJmdXQ==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/debug': ^4.1.12 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.3 - '@vitest/browser-preview': 4.0.3 - '@vitest/browser-webdriverio': 4.0.3 - '@vitest/ui': 4.0.3 + '@vitest/browser-playwright': 4.0.5 + '@vitest/browser-preview': 4.0.5 + '@vitest/browser-webdriverio': 4.0.5 + '@vitest/ui': 4.0.5 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -1396,39 +1396,39 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@biomejs/biome@2.3.1': + '@biomejs/biome@2.3.2': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.3.1 - '@biomejs/cli-darwin-x64': 2.3.1 - '@biomejs/cli-linux-arm64': 2.3.1 - '@biomejs/cli-linux-arm64-musl': 2.3.1 - '@biomejs/cli-linux-x64': 2.3.1 - '@biomejs/cli-linux-x64-musl': 2.3.1 - '@biomejs/cli-win32-arm64': 2.3.1 - '@biomejs/cli-win32-x64': 2.3.1 + '@biomejs/cli-darwin-arm64': 2.3.2 + '@biomejs/cli-darwin-x64': 2.3.2 + '@biomejs/cli-linux-arm64': 2.3.2 + '@biomejs/cli-linux-arm64-musl': 2.3.2 + '@biomejs/cli-linux-x64': 2.3.2 + '@biomejs/cli-linux-x64-musl': 2.3.2 + '@biomejs/cli-win32-arm64': 2.3.2 + '@biomejs/cli-win32-x64': 2.3.2 - '@biomejs/cli-darwin-arm64@2.3.1': + '@biomejs/cli-darwin-arm64@2.3.2': optional: true - '@biomejs/cli-darwin-x64@2.3.1': + '@biomejs/cli-darwin-x64@2.3.2': optional: true - '@biomejs/cli-linux-arm64-musl@2.3.1': + '@biomejs/cli-linux-arm64-musl@2.3.2': optional: true - '@biomejs/cli-linux-arm64@2.3.1': + '@biomejs/cli-linux-arm64@2.3.2': optional: true - '@biomejs/cli-linux-x64-musl@2.3.1': + '@biomejs/cli-linux-x64-musl@2.3.2': optional: true - '@biomejs/cli-linux-x64@2.3.1': + '@biomejs/cli-linux-x64@2.3.2': optional: true - '@biomejs/cli-win32-arm64@2.3.1': + '@biomejs/cli-win32-arm64@2.3.2': optional: true - '@biomejs/cli-win32-x64@2.3.1': + '@biomejs/cli-win32-x64@2.3.2': optional: true '@esbuild/aix-ppc64@0.25.9': @@ -1621,51 +1621,51 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@swc/core-darwin-arm64@1.13.5': + '@swc/core-darwin-arm64@1.14.0': optional: true - '@swc/core-darwin-x64@1.13.5': + '@swc/core-darwin-x64@1.14.0': optional: true - '@swc/core-linux-arm-gnueabihf@1.13.5': + '@swc/core-linux-arm-gnueabihf@1.14.0': optional: true - '@swc/core-linux-arm64-gnu@1.13.5': + '@swc/core-linux-arm64-gnu@1.14.0': optional: true - '@swc/core-linux-arm64-musl@1.13.5': + '@swc/core-linux-arm64-musl@1.14.0': optional: true - '@swc/core-linux-x64-gnu@1.13.5': + '@swc/core-linux-x64-gnu@1.14.0': optional: true - '@swc/core-linux-x64-musl@1.13.5': + '@swc/core-linux-x64-musl@1.14.0': optional: true - '@swc/core-win32-arm64-msvc@1.13.5': + '@swc/core-win32-arm64-msvc@1.14.0': optional: true - '@swc/core-win32-ia32-msvc@1.13.5': + '@swc/core-win32-ia32-msvc@1.14.0': optional: true - '@swc/core-win32-x64-msvc@1.13.5': + '@swc/core-win32-x64-msvc@1.14.0': optional: true - '@swc/core@1.13.5': + '@swc/core@1.14.0': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.25 optionalDependencies: - '@swc/core-darwin-arm64': 1.13.5 - '@swc/core-darwin-x64': 1.13.5 - '@swc/core-linux-arm-gnueabihf': 1.13.5 - '@swc/core-linux-arm64-gnu': 1.13.5 - '@swc/core-linux-arm64-musl': 1.13.5 - '@swc/core-linux-x64-gnu': 1.13.5 - '@swc/core-linux-x64-musl': 1.13.5 - '@swc/core-win32-arm64-msvc': 1.13.5 - '@swc/core-win32-ia32-msvc': 1.13.5 - '@swc/core-win32-x64-msvc': 1.13.5 + '@swc/core-darwin-arm64': 1.14.0 + '@swc/core-darwin-x64': 1.14.0 + '@swc/core-linux-arm-gnueabihf': 1.14.0 + '@swc/core-linux-arm64-gnu': 1.14.0 + '@swc/core-linux-arm64-musl': 1.14.0 + '@swc/core-linux-x64-gnu': 1.14.0 + '@swc/core-linux-x64-musl': 1.14.0 + '@swc/core-win32-arm64-msvc': 1.14.0 + '@swc/core-win32-ia32-msvc': 1.14.0 + '@swc/core-win32-x64-msvc': 1.14.0 '@swc/counter@0.1.3': {} @@ -1683,10 +1683,10 @@ snapshots: '@types/validator@13.15.3': {} - '@vitest/coverage-v8@4.0.3(vitest@4.0.3(terser@5.44.0))': + '@vitest/coverage-v8@4.0.5(vitest@4.0.5(terser@5.44.0))': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.3 + '@vitest/utils': 4.0.5 ast-v8-to-istanbul: 0.3.5 debug: 4.4.3 istanbul-lib-coverage: 3.2.2 @@ -1696,47 +1696,47 @@ snapshots: magicast: 0.3.5 std-env: 3.9.0 tinyrainbow: 3.0.3 - vitest: 4.0.3(terser@5.44.0) + vitest: 4.0.5(terser@5.44.0) transitivePeerDependencies: - supports-color - '@vitest/expect@4.0.3': + '@vitest/expect@4.0.5': dependencies: '@standard-schema/spec': 1.0.0 '@types/chai': 5.2.2 - '@vitest/spy': 4.0.3 - '@vitest/utils': 4.0.3 + '@vitest/spy': 4.0.5 + '@vitest/utils': 4.0.5 chai: 6.2.0 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.3(vite@7.1.6(terser@5.44.0))': + '@vitest/mocker@4.0.5(vite@7.1.6(terser@5.44.0))': dependencies: - '@vitest/spy': 4.0.3 + '@vitest/spy': 4.0.5 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: vite: 7.1.6(terser@5.44.0) - '@vitest/pretty-format@4.0.3': + '@vitest/pretty-format@4.0.5': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.3': + '@vitest/runner@4.0.5': dependencies: - '@vitest/utils': 4.0.3 + '@vitest/utils': 4.0.5 pathe: 2.0.3 - '@vitest/snapshot@4.0.3': + '@vitest/snapshot@4.0.5': dependencies: - '@vitest/pretty-format': 4.0.3 + '@vitest/pretty-format': 4.0.5 magic-string: 0.30.19 pathe: 2.0.3 - '@vitest/spy@4.0.3': {} + '@vitest/spy@4.0.5': {} - '@vitest/utils@4.0.3': + '@vitest/utils@4.0.5': dependencies: - '@vitest/pretty-format': 4.0.3 + '@vitest/pretty-format': 4.0.5 tinyrainbow: 3.0.3 acorn@8.15.0: {} @@ -2393,7 +2393,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.0(@swc/core@1.13.5)(postcss@8.5.6)(typescript@5.9.3): + tsup@8.5.0(@swc/core@1.14.0)(postcss@8.5.6)(typescript@5.9.3): dependencies: bundle-require: 5.1.0(esbuild@0.25.9) cac: 6.7.14 @@ -2413,7 +2413,7 @@ snapshots: tinyglobby: 0.2.15 tree-kill: 1.2.2 optionalDependencies: - '@swc/core': 1.13.5 + '@swc/core': 1.14.0 postcss: 8.5.6 typescript: 5.9.3 transitivePeerDependencies: @@ -2442,15 +2442,15 @@ snapshots: fsevents: 2.3.3 terser: 5.44.0 - vitest@4.0.3(terser@5.44.0): + vitest@4.0.5(terser@5.44.0): dependencies: - '@vitest/expect': 4.0.3 - '@vitest/mocker': 4.0.3(vite@7.1.6(terser@5.44.0)) - '@vitest/pretty-format': 4.0.3 - '@vitest/runner': 4.0.3 - '@vitest/snapshot': 4.0.3 - '@vitest/spy': 4.0.3 - '@vitest/utils': 4.0.3 + '@vitest/expect': 4.0.5 + '@vitest/mocker': 4.0.5(vite@7.1.6(terser@5.44.0)) + '@vitest/pretty-format': 4.0.5 + '@vitest/runner': 4.0.5 + '@vitest/snapshot': 4.0.5 + '@vitest/spy': 4.0.5 + '@vitest/utils': 4.0.5 debug: 4.4.3 es-module-lexer: 1.7.0 expect-type: 1.2.2 From babfdb5ddade81cbcd7d0b9d8df206e519d4f9e4 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Thu, 30 Oct 2025 03:04:34 -0300 Subject: [PATCH 71/74] feat/added tests for columntypes --- src/types/ColumnTypes.test.ts | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/types/ColumnTypes.test.ts diff --git a/src/types/ColumnTypes.test.ts b/src/types/ColumnTypes.test.ts new file mode 100644 index 0000000..65c8bad --- /dev/null +++ b/src/types/ColumnTypes.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; +import { Bit, Char, Decimal, Numeric, VarBit, Varchar } from "./ColumnTypes.js"; + +describe("Column Types", () => { + it("should create Numeric column type", () => { + const numericType = Numeric(10, 2); + expect(numericType.build()).toEqual("NUMERIC(10, 2)"); + + const numericTypeNoScale = Numeric(5); + expect(numericTypeNoScale.build()).toEqual("NUMERIC(5)"); + }); + + it('should create Decimal column type', () => { + const decimalType = Decimal(8, 3); + expect(decimalType.build()).toEqual("DECIMAL(8, 3)"); + + const decimalTypeNoScale = Decimal(4); + expect(decimalTypeNoScale.build()).toEqual("DECIMAL(4)"); + }); + + it('should create Char column type', () => { + const charType = Char(10); + expect(charType.build()).toEqual("CHAR(10)"); + }); + + it('should create Varchar column type', () => { + const varcharType = Varchar(255); + expect(varcharType.build()).toEqual("VARCHAR(255)"); + }); + + it('should create Bit column type', () => { + const bitType = Bit(1); + expect(bitType.build()).toEqual("BIT(1)"); + }); + + it('should create VarBit column type', () => { + const varBitType = VarBit(16); + expect(varBitType.build()).toEqual("VARBIT(16)"); + }); +}); From 6bad81abe29c5236a3178a6a3641e4d802c6a0d4 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Thu, 30 Oct 2025 03:05:08 -0300 Subject: [PATCH 72/74] feat/added tests and fixed some issues in the ColumnDefinition and ColumnType classes --- src/queryUtils/Column.test.ts | 319 ++++++++++++++++++++++++++++++++++ src/queryUtils/Column.ts | 76 +++++--- 2 files changed, 370 insertions(+), 25 deletions(-) create mode 100644 src/queryUtils/Column.test.ts diff --git a/src/queryUtils/Column.test.ts b/src/queryUtils/Column.test.ts new file mode 100644 index 0000000..78580c9 --- /dev/null +++ b/src/queryUtils/Column.test.ts @@ -0,0 +1,319 @@ +import { describe, expect, it } from "vitest"; +import { ColumnDefinition, ColumnType } from "./Column.js"; +import { Varchar } from "../types/ColumnTypes.js"; + + +describe("Column Definition Class", () => { + + it('should create a column definition instance', () => { + const columnDef = new ColumnDefinition("id", new ColumnType("INTEGER")); + expect((columnDef as any).name).toBe("id"); + expect((columnDef as any).type).toBeInstanceOf(ColumnType); + expect(columnDef.build()).toBe('id INTEGER'); + }); + + it('type should be null if not set', () => { + const columnDef = new ColumnDefinition("name"); + expect((columnDef as any).name).toBe("name"); + expect((columnDef as any).type).toBeNull(); + expect(() => columnDef.build()).toThrow("Column type is not set."); + }); + + it('name should be null if not set', () => { + const columnDef = new ColumnDefinition(undefined, new ColumnType("TEXT")); + expect((columnDef as any).name).toBeNull(); + expect((columnDef as any).type).toBeInstanceOf(ColumnType); + expect(() => columnDef.build()).toThrow("Column name is not set."); + }); + + it('should be able to set name and type after creation', () => { + const columnDef = new ColumnDefinition(); + columnDef.setName("username"); + columnDef.setType(new ColumnType("VARCHAR", [50])); + expect((columnDef as any).name).toBe("username"); + expect((columnDef as any).type).toBeInstanceOf(ColumnType); + expect(columnDef.build()).toBe('username VARCHAR(50)'); + }); + + it('should be able to pass type as string', () => { + const columnDef = new ColumnDefinition("age", "INTEGER"); + expect((columnDef as any).name).toBe("age"); + expect((columnDef as any).type).toBeInstanceOf(ColumnType); + expect(columnDef.build()).toBe('age INTEGER'); + + const columnDef2 = new ColumnDefinition(); + columnDef2.setName("created_at"); + columnDef2.setType("TIMESTAMP"); + expect((columnDef2 as any).name).toBe("created_at"); + expect((columnDef2 as any).type).toBeInstanceOf(ColumnType); + expect(columnDef2.build()).toBe('created_at TIMESTAMP'); + }); + + it('should be able to set null or not null constraint', () => { + const columnDef = new ColumnDefinition("email", new ColumnType("VARCHAR", [100])); + columnDef.notNull(); + expect(columnDef.build()).toBe('email VARCHAR(100) NOT NULL'); + + const columnDef2 = new ColumnDefinition("bio", new ColumnType("TEXT")); + columnDef2.null(); + expect(columnDef2.build()).toBe('bio TEXT'); + }); + + it('should chain methods correctly', () => { + const columnDef = new ColumnDefinition() + .setName("status") + .setType(Varchar(20)) + .notNull(); + expect((columnDef as any).name).toBe("status"); + expect((columnDef as any).type).toBeInstanceOf(ColumnType); + expect(columnDef.build()).toBe('status VARCHAR(20) NOT NULL'); + }); + + it('should be able to set default value', () => { + const columnDef = new ColumnDefinition("is_active", new ColumnType("BOOLEAN")); + columnDef.default(true); + expect(columnDef.build()).toBe('is_active BOOLEAN DEFAULT true'); + + const columnDef2 = new ColumnDefinition("created_at", new ColumnType("TIMESTAMP")); + columnDef2.default('CURRENT_TIMESTAMP'); + expect(columnDef2.build()).toBe('created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP'); + }); + + it('should be able to add check constraint', () => { + const columnDef = new ColumnDefinition("age", new ColumnType("INTEGER")); + columnDef.check("age >= 0"); + expect(columnDef.build()).toBe('age INTEGER CHECK (age >= 0)'); + + const columnDef2 = new ColumnDefinition("score", new ColumnType("INTEGER")); + columnDef2.check("score BETWEEN 0 AND 100"); + expect(columnDef2.build()).toBe('score INTEGER CHECK (score BETWEEN 0 AND 100)'); + }); + + it('should be able to set unique constraint', () => { + const columnDef = new ColumnDefinition("email", new ColumnType("VARCHAR", [255])); + columnDef.unique(); + expect(columnDef.build()).toBe('email VARCHAR(255) UNIQUE'); + }); + + it('should be able to set primary key constraint', () => { + const columnDef = new ColumnDefinition("id", new ColumnType("SERIAL")); + columnDef.primaryKey(); + expect(columnDef.build()).toBe('id SERIAL PRIMARY KEY'); + }); + + it('should throw error if name, type or name and type are not set', () => { + const columnDef1 = new ColumnDefinition(); + expect(() => columnDef1.build()).toThrow("Column name and type are not set."); + + const columnDef2 = new ColumnDefinition("username"); + expect(() => columnDef2.build()).toThrow("Column type is not set."); + + const columnDef3 = new ColumnDefinition(undefined, new ColumnType("TEXT")); + expect(() => columnDef3.build()).toThrow("Column name is not set."); + }); + + it('should be able to set foreign key constraint', () => { + const columnDef = new ColumnDefinition("user_id", new ColumnType("INTEGER")); + columnDef.references("users", "id"); + expect(columnDef.build()).toBe('user_id INTEGER REFERENCES users(id)'); + + // With actions + const columnDef2 = new ColumnDefinition("profile_id", new ColumnType("INTEGER")); + columnDef2.references("profiles", "id", "CASCADE", "SET NULL"); + expect(columnDef2.build()).toBe('profile_id INTEGER REFERENCES profiles(id) ON DELETE CASCADE ON UPDATE SET NULL'); + }); + + it('should be able to build for adding in a table', () => { + const columnDef = new ColumnDefinition("email", new ColumnType("VARCHAR", [150])); + expect(columnDef.buildToAdd("users")).toEqual([ + 'ALTER TABLE users ADD COLUMN email VARCHAR(150)', + 'ALTER TABLE users ALTER COLUMN email DROP NOT NULL' + ]); + + // With not null constraint + const columnDef2 = new ColumnDefinition("id", new ColumnType("SERIAL")); + columnDef2.notNull(); + expect(columnDef2.buildToAdd("accounts")).toEqual([ + 'ALTER TABLE accounts ADD COLUMN id SERIAL', + 'ALTER TABLE accounts ALTER COLUMN id SET NOT NULL' + ]); + + // With default value + const columnDef3 = new ColumnDefinition("created_at", new ColumnType("TIMESTAMP")); + columnDef3.default('CURRENT_TIMESTAMP'); + expect(columnDef3.buildToAdd("logs")).toEqual([ + 'ALTER TABLE logs ADD COLUMN created_at TIMESTAMP', + 'ALTER TABLE logs ALTER COLUMN created_at DROP NOT NULL', + 'ALTER TABLE logs ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMP' + ]); + + // With check constraint + const columnDef4 = new ColumnDefinition("age", new ColumnType("INTEGER")); + columnDef4.check("age >= 0"); + expect(columnDef4.buildToAdd("persons")).toEqual([ + 'ALTER TABLE persons ADD COLUMN age INTEGER', + 'ALTER TABLE persons ALTER COLUMN age DROP NOT NULL', + 'ALTER TABLE persons ADD CONSTRAINT persons_age_check CHECK (age >= 0)' + ]); + + // With foreign key constraint + const columnDef5 = new ColumnDefinition("user_id", new ColumnType("INTEGER")); + columnDef5.references("users", "id", "CASCADE", "CASCADE"); + expect(columnDef5.buildToAdd("orders")).toEqual([ + 'ALTER TABLE orders ADD COLUMN user_id INTEGER', + 'ALTER TABLE orders ALTER COLUMN user_id DROP NOT NULL', + 'ALTER TABLE orders ADD CONSTRAINT orders_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE' + ]); + + // With unique constraint + const columnDef6 = new ColumnDefinition("email", new ColumnType("VARCHAR", [255])); + columnDef6.unique(); + expect(columnDef6.buildToAdd("subscribers")).toEqual([ + 'ALTER TABLE subscribers ADD COLUMN email VARCHAR(255)', + 'ALTER TABLE subscribers ALTER COLUMN email DROP NOT NULL', + 'ALTER TABLE subscribers ADD CONSTRAINT subscribers_email_unique UNIQUE (email)' + ]); + + // With foreign key with no actions + const columnDef7 = new ColumnDefinition("category_id", new ColumnType("INTEGER")); + columnDef7.references("categories", "id"); + expect(columnDef7.buildToAdd("products")).toEqual([ + 'ALTER TABLE products ADD COLUMN category_id INTEGER', + 'ALTER TABLE products ALTER COLUMN category_id DROP NOT NULL', + 'ALTER TABLE products ADD CONSTRAINT products_category_id_fkey FOREIGN KEY (category_id) REFERENCES categories(id)' + ]); + + // With primary key constraint + const columnDef8 = new ColumnDefinition("id", new ColumnType("INTEGER")); + columnDef8.primaryKey(); + expect(columnDef8.buildToAdd("employees")).toEqual([ + 'ALTER TABLE employees ADD COLUMN id INTEGER', + 'ALTER TABLE employees ALTER COLUMN id SET NOT NULL', + 'ALTER TABLE employees ADD CONSTRAINT employees_id_pkey PRIMARY KEY (id)' + ]); + }); + + it('should be able to build for altering in a table', () => { + + // Altering name + const columnDef = new ColumnDefinition("new_name", new ColumnType("VARCHAR", [100])); + expect(columnDef.buildToAlter("users", "old_name")).toEqual([ + 'ALTER TABLE users RENAME COLUMN old_name TO new_name', + 'ALTER TABLE users ALTER COLUMN new_name TYPE VARCHAR(100)', + 'ALTER TABLE users ALTER COLUMN new_name DROP NOT NULL', + ]); + + // Without constraints + const columnDef2 = new ColumnDefinition("email", new ColumnType("VARCHAR", [150])); + expect(columnDef2.buildToAlter("users")).toEqual([ + 'ALTER TABLE users ALTER COLUMN email TYPE VARCHAR(150)', + 'ALTER TABLE users ALTER COLUMN email DROP NOT NULL', + ]); + + // With not null constraint + const columnDef3 = new ColumnDefinition("id", new ColumnType("SERIAL")); + columnDef3.notNull(); + expect(columnDef3.buildToAlter("accounts")).toEqual([ + 'ALTER TABLE accounts ALTER COLUMN id TYPE SERIAL', + 'ALTER TABLE accounts ALTER COLUMN id SET NOT NULL', + ]); + + // With default value + const columnDef4 = new ColumnDefinition("created_at", new ColumnType("TIMESTAMP")); + columnDef4.default('CURRENT_TIMESTAMP'); + expect(columnDef4.buildToAlter("logs")).toEqual([ + 'ALTER TABLE logs ALTER COLUMN created_at TYPE TIMESTAMP', + 'ALTER TABLE logs ALTER COLUMN created_at DROP NOT NULL', + 'ALTER TABLE logs ALTER COLUMN created_at SET DEFAULT CURRENT_TIMESTAMP' + ]); + + // With check constraint + const columnDef5 = new ColumnDefinition("age", new ColumnType("INTEGER")); + columnDef5.check("age >= 0"); + expect(columnDef5.buildToAlter("persons")).toEqual([ + 'ALTER TABLE persons ALTER COLUMN age TYPE INTEGER', + 'ALTER TABLE persons ALTER COLUMN age DROP NOT NULL', + 'ALTER TABLE persons ADD CONSTRAINT persons_age_check CHECK (age >= 0)' + ]); + + // With unique constraint + const columnDef6 = new ColumnDefinition("email", new ColumnType("VARCHAR", [255])); + columnDef6.unique(); + expect(columnDef6.buildToAlter("subscribers")).toEqual([ + 'ALTER TABLE subscribers ALTER COLUMN email TYPE VARCHAR(255)', + 'ALTER TABLE subscribers ALTER COLUMN email DROP NOT NULL', + 'ALTER TABLE subscribers ADD CONSTRAINT subscribers_email_unique UNIQUE (email)' + ]); + + // With primary key constraint + const columnDef7 = new ColumnDefinition("id", new ColumnType("INTEGER")); + columnDef7.primaryKey(); + expect(columnDef7.buildToAlter("employees")).toEqual([ + 'ALTER TABLE employees ALTER COLUMN id TYPE INTEGER', + 'ALTER TABLE employees ALTER COLUMN id SET NOT NULL', + 'ALTER TABLE employees ADD CONSTRAINT employees_id_pkey PRIMARY KEY (id)' + ]); + + // With foreign key constraint + const columnDef8 = new ColumnDefinition("user_id", new ColumnType("INTEGER")); + columnDef8.references("users", "id", "CASCADE", "SET NULL"); + expect(columnDef8.buildToAlter("orders")).toEqual([ + 'ALTER TABLE orders ALTER COLUMN user_id TYPE INTEGER', + 'ALTER TABLE orders ALTER COLUMN user_id DROP NOT NULL', + 'ALTER TABLE orders ADD CONSTRAINT orders_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE SET NULL' + ]); + + // With foreign key with no actions + const columnDef9 = new ColumnDefinition("category_id", new ColumnType("INTEGER")); + columnDef9.references("categories", "id"); + expect(columnDef9.buildToAlter("products")).toEqual([ + 'ALTER TABLE products ALTER COLUMN category_id TYPE INTEGER', + 'ALTER TABLE products ALTER COLUMN category_id DROP NOT NULL', + 'ALTER TABLE products ADD CONSTRAINT products_category_id_fkey FOREIGN KEY (category_id) REFERENCES categories(id)' + ]); + + // To drop default + const columnDef10 = new ColumnDefinition("is_active", new ColumnType("BOOLEAN")); + columnDef10.dropDefaultValue(); + expect(columnDef10.buildToAlter("members")).toEqual([ + 'ALTER TABLE members ALTER COLUMN is_active TYPE BOOLEAN', + 'ALTER TABLE members ALTER COLUMN is_active DROP NOT NULL', + 'ALTER TABLE members ALTER COLUMN is_active DROP DEFAULT' + ]); + }); + + it('should use toString method to build normally', () => { + const columnDef = new ColumnDefinition("username", new ColumnType("VARCHAR", [50])); + expect(columnDef.toString()).toBe('username VARCHAR(50)'); + }); + + +}); + +describe("Column Type Class", () => { + it('should create a column type instance', () => { + const columnType = new ColumnType("VARCHAR", [255]); + expect((columnType as any).typeName).toBe("VARCHAR"); + expect((columnType as any).properties).toEqual(['255']); + expect(columnType.build()).toBe("VARCHAR(255)"); + }); + + it('should be able to set type and properties after creation', () => { + const columnType = new ColumnType("INTEGER"); + columnType.setType("CHAR"); + columnType.addProperty(10); + expect((columnType as any).typeName).toBe("CHAR"); + expect((columnType as any).properties).toEqual(['10']); + expect(columnType.build()).toBe("CHAR(10)"); + }); + + it('should handle types without properties', () => { + const columnType = new ColumnType("TEXT"); + expect(columnType.build()).toBe("TEXT"); + }); + + it('should throw error if type is not set', () => { + const columnType = new ColumnType(); + expect(() => columnType.build()).toThrow("Type name is not set."); + }); +}) diff --git a/src/queryUtils/Column.ts b/src/queryUtils/Column.ts index a613689..d7e422c 100644 --- a/src/queryUtils/Column.ts +++ b/src/queryUtils/Column.ts @@ -37,8 +37,10 @@ export class ColumnType { * @param property - The property to add. * @returns The current instance for method chaining. */ - public addProperty(property: string): this { - this.properties.push(property); + public addProperty( + property: string | { toString(): string }, + ): this { + this.properties.push(property.toString()); return this; } @@ -90,6 +92,8 @@ export class ColumnDefinition { private checkCondition?: string; /** Foreign key constraint details, if any. */ private foreignKey: ForeignKey | null = null; + /** Drop default value flag */ + private dropDefault: boolean = false; constructor( name: string | null = null, @@ -107,6 +111,15 @@ export class ColumnDefinition { } } + /** + * Marks the column to drop its default value. + * @returns The current instance for method chaining. + */ + public dropDefaultValue(): this { + this.dropDefault = true; + return this; + } + /** * Sets the name of the column. * @param name - The name of the column. @@ -245,7 +258,7 @@ export class ColumnDefinition { } } - if (this.defaultValue !== undefined) { + if (this.defaultValue !== undefined && !forAdding) { parts.push(`DEFAULT ${this.defaultValue}`); } @@ -253,7 +266,7 @@ export class ColumnDefinition { parts.push(`CHECK (${this.checkCondition})`); } - if (this.foreignKey) { + if (this.foreignKey && !forAdding) { let fkPart = `REFERENCES ${this.foreignKey.table}(${this.foreignKey.column})`; if (this.foreignKey.onDelete) { fkPart += ` ON DELETE ${this.foreignKey.onDelete}`; @@ -267,27 +280,31 @@ export class ColumnDefinition { return parts.join(" "); } - public buildToAdd(tableName: string): string[] { - const addColumn = `ALTER TABLE ${tableName} ADD COLUMN ${this.build(true)}`; + public buildToAdd(tableNameEntry: string): string[] { + let tableName = tableNameEntry.trim(); + if (tableName.startsWith('"') && tableName.endsWith('"')) { + tableName = tableName.slice(1, -1); + } + const addColumn = `ALTER TABLE ${tableNameEntry} ADD COLUMN ${this.build(true)}`; const addColumnNullability = this.isNullable - ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} DROP NOT NULL` - : `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET NOT NULL`; + ? `ALTER TABLE ${tableNameEntry} ALTER COLUMN ${this.name} DROP NOT NULL` + : `ALTER TABLE ${tableNameEntry} ALTER COLUMN ${this.name} SET NOT NULL`; const addColumnDefault = this.defaultValue !== undefined - ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET DEFAULT ${this.defaultValue}` + ? `ALTER TABLE ${tableNameEntry} ALTER COLUMN ${this.name} SET DEFAULT ${this.defaultValue}` : ""; const addColumnCheck = this.checkCondition !== undefined - ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_check CHECK (${this.checkCondition})` + ? `ALTER TABLE ${tableNameEntry} ADD CONSTRAINT ${tableName}_${this.name}_check CHECK (${this.checkCondition})` : ""; const addColumnPrimaryKey = this.isPrimaryKey - ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_pkey PRIMARY KEY (${this.name})` + ? `ALTER TABLE ${tableNameEntry} ADD CONSTRAINT ${tableName}_${this.name}_pkey PRIMARY KEY (${this.name})` : ""; const addColumnUnique = this.isUnique - ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_unique UNIQUE (${this.name})` + ? `ALTER TABLE ${tableNameEntry} ADD CONSTRAINT ${tableName}_${this.name}_unique UNIQUE (${this.name})` : ""; const addForeignKey = this.foreignKey - ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_fkey FOREIGN KEY (${this.name}) REFERENCES ${this.foreignKey.table}(${this.foreignKey.column})` + + ? `ALTER TABLE ${tableNameEntry} ADD CONSTRAINT ${tableName}_${this.name}_fkey FOREIGN KEY (${this.name}) REFERENCES ${this.foreignKey.table}(${this.foreignKey.column})` + (this.foreignKey.onDelete ? ` ON DELETE ${this.foreignKey.onDelete}` : "") + @@ -309,31 +326,40 @@ export class ColumnDefinition { return additions; } - public buildToAlter(tableName: string, previousName?: string): string[] { + public buildToAlter(tableNameEntry: string, previousName?: string): string[] { + + let tableName = tableNameEntry.trim(); + + if (tableName.startsWith('"') && tableName.endsWith('"')) { + tableName = tableName.slice(1, -1); + } + const alterColumnName = - previousName !== this.name - ? `ALTER TABLE ${tableName} RENAME COLUMN ${previousName} TO ${this.name}` + (previousName !== this.name && previousName) + ? `ALTER TABLE ${tableNameEntry} RENAME COLUMN ${previousName} TO ${this.name}` : ""; - const alterColumnType = `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} TYPE ${this.type?.toString()}`; + const alterColumnType = `ALTER TABLE ${tableNameEntry} ALTER COLUMN ${this.name} TYPE ${this.type?.toString()}`; const alterColumnNullability = this.isNullable - ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} DROP NOT NULL` - : `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET NOT NULL`; + ? `ALTER TABLE ${tableNameEntry} ALTER COLUMN ${this.name} DROP NOT NULL` + : `ALTER TABLE ${tableNameEntry} ALTER COLUMN ${this.name} SET NOT NULL`; const alterColumnDefault = this.defaultValue !== undefined - ? `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} SET DEFAULT ${this.defaultValue}` - : `ALTER TABLE ${tableName} ALTER COLUMN ${this.name} DROP DEFAULT`; + ? `ALTER TABLE ${tableNameEntry} ALTER COLUMN ${this.name} SET DEFAULT ${this.defaultValue}` + : this.dropDefault + ? `ALTER TABLE ${tableNameEntry} ALTER COLUMN ${this.name} DROP DEFAULT` + : ""; const alterColumnCheck = this.checkCondition !== undefined - ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_check CHECK (${this.checkCondition})` + ? `ALTER TABLE ${tableNameEntry} ADD CONSTRAINT ${tableName}_${this.name}_check CHECK (${this.checkCondition})` : ""; const alterColumnPrimaryKey = this.isPrimaryKey - ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_pkey PRIMARY KEY (${this.name})` + ? `ALTER TABLE ${tableNameEntry} ADD CONSTRAINT ${tableName}_${this.name}_pkey PRIMARY KEY (${this.name})` : ""; const alterColumnUnique = this.isUnique - ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_unique UNIQUE (${this.name})` + ? `ALTER TABLE ${tableNameEntry} ADD CONSTRAINT ${tableName}_${this.name}_unique UNIQUE (${this.name})` : ""; const alterForeignKey = this.foreignKey - ? `ALTER TABLE ${tableName} ADD CONSTRAINT ${this.name}_fkey FOREIGN KEY (${this.name}) REFERENCES ${this.foreignKey.table}(${this.foreignKey.column})` + + ? `ALTER TABLE ${tableNameEntry} ADD CONSTRAINT ${tableName}_${this.name}_fkey FOREIGN KEY (${this.name}) REFERENCES ${this.foreignKey.table}(${this.foreignKey.column})` + (this.foreignKey.onDelete ? ` ON DELETE ${this.foreignKey.onDelete}` : "") + From 391eaa921b96de612260c77c3746e581fab502ea Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Thu, 30 Oct 2025 03:05:52 -0300 Subject: [PATCH 73/74] feat/completed tests for AlterTableQuery class --- src/queryKinds/ddl/table/Alter.test.ts | 164 ++++++++++++++++++++++++- src/queryKinds/ddl/table/Alter.ts | 7 +- 2 files changed, 165 insertions(+), 6 deletions(-) diff --git a/src/queryKinds/ddl/table/Alter.test.ts b/src/queryKinds/ddl/table/Alter.test.ts index 07e5284..b5f9b84 100644 --- a/src/queryKinds/ddl/table/Alter.test.ts +++ b/src/queryKinds/ddl/table/Alter.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import AlterTableQuery from "./Alter.js"; import Column from "../../../queryUtils/Column.js"; +import { Varchar } from "../../../types/ColumnTypes.js"; describe('Alter Table Query', () => { @@ -12,7 +13,22 @@ describe('Alter Table Query', () => { expect(query).toEqual([ 'ALTER TABLE "users" ADD COLUMN id INT;', 'ALTER TABLE "users" ALTER COLUMN id SET NOT NULL;', - 'ALTER TABLE "users" ADD CONSTRAINT id_pkey PRIMARY KEY (id);', + 'ALTER TABLE "users" ADD CONSTRAINT users_id_pkey PRIMARY KEY (id);', + ]); + + const query2 = new AlterTableQuery('employees') + .addColumnsToAdd([ + Column('employee_id', 'INT').primaryKey().notNull(), + Column('employee_name', Varchar(100)).notNull(), + ]) + .build(); + + expect(query2).toEqual([ + 'ALTER TABLE "employees" ADD COLUMN employee_id INT;', + 'ALTER TABLE "employees" ALTER COLUMN employee_id SET NOT NULL;', + 'ALTER TABLE "employees" ADD CONSTRAINT employees_employee_id_pkey PRIMARY KEY (employee_id);', + 'ALTER TABLE "employees" ADD COLUMN employee_name VARCHAR(100);', + 'ALTER TABLE "employees" ALTER COLUMN employee_name SET NOT NULL;', ]); }); @@ -27,7 +43,7 @@ describe('Alter Table Query', () => { expect(query).toEqual([ 'ALTER TABLE "products" ADD COLUMN product_id INT;', 'ALTER TABLE "products" ALTER COLUMN product_id SET NOT NULL;', - 'ALTER TABLE "products" ADD CONSTRAINT product_id_pkey PRIMARY KEY (product_id);', + 'ALTER TABLE "products" ADD CONSTRAINT products_product_id_pkey PRIMARY KEY (product_id);', ]); }); @@ -42,9 +58,151 @@ describe('Alter Table Query', () => { expect(query).toEqual([ 'ALTER TABLE "orders" ADD COLUMN order_id INT;', 'ALTER TABLE "orders" ALTER COLUMN order_id SET NOT NULL;', - 'ALTER TABLE "orders" ADD CONSTRAINT order_id_pkey PRIMARY KEY (order_id);', + 'ALTER TABLE "orders" ADD CONSTRAINT orders_order_id_pkey PRIMARY KEY (order_id);', 'ALTER TABLE "orders" ADD COLUMN order_date DATE;', 'ALTER TABLE "orders" ALTER COLUMN order_date SET NOT NULL;', ]); + + const query2 = new AlterTableQuery('orders') + .setColumnsToAdd(Column('customer_id', 'INT').notNull()) + .build(); + + expect(query2).toEqual([ + 'ALTER TABLE "orders" ADD COLUMN customer_id INT;', + 'ALTER TABLE "orders" ALTER COLUMN customer_id SET NOT NULL;', + ]); + }); + + it('should allow to set columns to alter', () => { + const query = new AlterTableQuery('customers') + .setColumnsToAlter([ + { name: 'customer_name', columns: Column('customer_name', Varchar(100)).notNull() }, + ]) + .build(); + + expect(query).toEqual([ + 'ALTER TABLE "customers" ALTER COLUMN customer_name TYPE VARCHAR(100);', + 'ALTER TABLE "customers" ALTER COLUMN customer_name SET NOT NULL;', + ]); + + const query2 = new AlterTableQuery('customers') + .setColumnsToAlter({ name: 'customer_email', columns: Column('customer_email', Varchar(150)).unique() }) + .build(); + + expect(query2).toEqual([ + 'ALTER TABLE "customers" ALTER COLUMN customer_email TYPE VARCHAR(150);', + 'ALTER TABLE "customers" ALTER COLUMN customer_email DROP NOT NULL;', + 'ALTER TABLE "customers" ADD CONSTRAINT customers_customer_email_unique UNIQUE (customer_email);' + ]); + }); + + it('should allow to drop columns', () => { + const query = new AlterTableQuery('inventory') + .dropColumnsByName(['old_column1', 'old_column2']) + .build(); + + expect(query).toEqual([ + 'ALTER TABLE "inventory" DROP COLUMN old_column1;', + 'ALTER TABLE "inventory" DROP COLUMN old_column2;', + ]); + + const query2 = new AlterTableQuery('inventory') + .dropColumnsByName('obsolete_column') + .build(); + + expect(query2).toEqual([ + 'ALTER TABLE "inventory" DROP COLUMN obsolete_column;', + ]); + + // set + const query3 = new AlterTableQuery('inventory') + .setColumnsToDrop('discontinued_column') + .build(); + + expect(query3).toEqual([ + 'ALTER TABLE "inventory" DROP COLUMN discontinued_column;', + ]); + + const query4 = new AlterTableQuery('inventory') + .setColumnsToDrop(['temp_column1', 'temp_column2']) + .build(); + + expect(query4).toEqual([ + 'ALTER TABLE "inventory" DROP COLUMN temp_column1;', + 'ALTER TABLE "inventory" DROP COLUMN temp_column2;', + ]); + }); + + it('should give kind as ALTER_TABLE', () => { + const query = new AlterTableQuery('test_table'); + expect(query.kind).toBe('ALTER_TABLE'); + }); + + it('should throw error if table name is not provided', () => { + const query = new AlterTableQuery(); + expect(() => query.build()).toThrowError( + 'Table name is required to build ALTER TABLE query.' + ); + }); + + it('should throw error if no alterations are specified', () => { + const query = new AlterTableQuery('test_table'); + expect(() => query.build()).toThrowError( + 'No alterations specified for ALTER TABLE query.' + ); + }); + + it('should be able to convert to sql using toSQL method', () => { + const query = new AlterTableQuery('employees') + .addColumnsToAdd(Column('employee_id', 'INT').primaryKey().notNull()); + + expect(query.toSQL()).toEqual([ + 'ALTER TABLE "employees" ADD COLUMN employee_id INT;', + 'ALTER TABLE "employees" ALTER COLUMN employee_id SET NOT NULL;', + 'ALTER TABLE "employees" ADD CONSTRAINT employees_employee_id_pkey PRIMARY KEY (employee_id);', + ]); + + const query2 = new AlterTableQuery('employees') + .dropColumnsByName('old_employee_column'); + + query2.toSQL(); + + expect(query2.toSQL()).toEqual([ + 'ALTER TABLE "employees" DROP COLUMN old_employee_column;', + ]); + }); + + it('should clone the AlterTableQuery instance', () => { + const originalQuery = new AlterTableQuery('departments') + .addColumnsToAdd(Column('department_id', 'INT').primaryKey().notNull()); + + const clonedQuery = originalQuery.clone(); + + expect(clonedQuery).toEqual(originalQuery); + expect(clonedQuery).not.toBe(originalQuery); + + // Modify the clone and ensure the original is unaffected + clonedQuery.table('new_departments'); + + expect((clonedQuery as any).tableName).toBe('"new_departments"'); + expect((originalQuery as any).tableName).toBe('"departments"'); + }); + + it('should be able to reset the AlterTableQuery instance', () => { + const query = new AlterTableQuery('projects') + .addColumnsToAdd(Column('project_id', 'INT').primaryKey().notNull()) + .dropColumnsByName('old_project_column'); + + query.reset(); + + expect(() => query.build()).toThrowError( + 'Table name is required to build ALTER TABLE query.' + ); + + query.table('new_projects'); + + expect(() => query.build()).toThrowError( + 'No alterations specified for ALTER TABLE query.' + ); }); }); diff --git a/src/queryKinds/ddl/table/Alter.ts b/src/queryKinds/ddl/table/Alter.ts index 442cc3f..aa40c54 100644 --- a/src/queryKinds/ddl/table/Alter.ts +++ b/src/queryKinds/ddl/table/Alter.ts @@ -153,7 +153,7 @@ export default class AlterTableQuery extends TableQueryDefinition { * Gets the kind of query. * @returns The kind of query, which is 'ALTER_TABLE' for this class. */ - public get kind() { + public get kind(): QueryKind { return QueryKind.ALTER_TABLE; } @@ -200,7 +200,7 @@ export default class AlterTableQuery extends TableQueryDefinition { * @throws Error if the query has not been built yet. */ public toSQL(): string[] { - if (this.builtQuery) this.build(); + if (!this.builtQuery) this.build(); return this.builtQuery as string[]; } @@ -209,7 +209,8 @@ export default class AlterTableQuery extends TableQueryDefinition { * @returns A new AlterTableQuery instance with the same properties as the current instance. */ public clone(): AlterTableQuery { - const cloned = new AlterTableQuery(this.tableName); + const cloned = new AlterTableQuery(); + cloned.tableName = this.tableName; cloned.flavor = this.flavor; cloned.columnsToAdd = [...this.columnsToAdd]; cloned.columnsToAlter = new Map(this.columnsToAlter); From dea3ea74c939901bef838ff667e21a49dbecbc45 Mon Sep 17 00:00:00 2001 From: Nicolas Renan Machado Dias Date: Thu, 30 Oct 2025 03:06:18 -0300 Subject: [PATCH 74/74] feat/added tests for DdlQueryDefinition specific functions --- src/queryKinds/ddl/ddlQueryDefinition.test.ts | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 src/queryKinds/ddl/ddlQueryDefinition.test.ts diff --git a/src/queryKinds/ddl/ddlQueryDefinition.test.ts b/src/queryKinds/ddl/ddlQueryDefinition.test.ts new file mode 100644 index 0000000..e72e756 --- /dev/null +++ b/src/queryKinds/ddl/ddlQueryDefinition.test.ts @@ -0,0 +1,208 @@ +import { describe, expect, it } from "vitest"; +import { Query } from "../../index.js" +import Column from "../../queryUtils/Column.js"; + +describe('DDL Query Definition', () => { + it('should create a basic DDL query using Query interface and test if it\'s done', () => { + const query = Query + .table.create + .table('users') + .setColumns([ + Column('id', 'INT').primaryKey().notNull(), + Column('username', 'VARCHAR(50)').notNull() + ]); + + let isDone = query.isDone(); + expect(isDone).toBe(false); + + expect(query.build()) + .toEqual('CREATE TABLE "users" (\n id INT PRIMARY KEY,\n username VARCHAR(50) NOT NULL\n);'); + + isDone = query.isDone(); + expect(isDone).toBe(true); + }); + + it('should be able to build explain query', () => { + const query = Query + .table.create + .table('products') + .setColumns([ + Column('product_id', 'INT').primaryKey().notNull(), + Column('product_name', 'VARCHAR(100)').notNull() + ]); + + expect(query.buildExplain()) + .toEqual('EXPLAIN CREATE TABLE "products" (\n product_id INT PRIMARY KEY,\n product_name VARCHAR(100) NOT NULL\n);'); + + // Explain Analyze + expect(query.buildExplainAnalyze()) + .toEqual('EXPLAIN ANALYZE CREATE TABLE "products" (\n product_id INT PRIMARY KEY,\n product_name VARCHAR(100) NOT NULL\n);'); + }); + + it('should be able to handle schemas in table names', () => { + const query = Query + .table.create + .schema('client_a') + .table('$schema.products') + .setColumns([ + Column('product_id', 'INT').primaryKey().notNull(), + Column('product_name', 'VARCHAR(100)').notNull() + ]); + + expect(query.build()) + .toEqual('CREATE TABLE client_a."products" (\n product_id INT PRIMARY KEY,\n product_name VARCHAR(100) NOT NULL\n);'); + + // add schema + const query2 = Query + .table.create + .schema('client_a') + .table('$schema1.products') + .setColumns([ + Column('product_id', 'INT').primaryKey().notNull(), + Column('product_name', 'VARCHAR(100)').notNull() + ]) + .addSchema('client_b'); + + expect(query2.build()) + .toEqual('CREATE TABLE client_b."products" (\n product_id INT PRIMARY KEY,\n product_name VARCHAR(100) NOT NULL\n);'); + }); + + + it('should be able to execute the built query', async () => { + const executeFunction = async (queryString: string) => { + // Mock execution function + expect(queryString).toSatisfy((value: string | string[]) => { + if (typeof value !== 'string' && !Array.isArray(value)) { + return false; + } + return true; + }); + }; + + const query = Query + .table.create + .table('orders') + .setColumns([ + Column('order_id', 'INT').primaryKey().notNull(), + Column('order_date', 'DATE').notNull() + ]); + + const result = await query.execute(executeFunction); + expect(result).toEqual(undefined); + + const query2 = Query + .table.alter + .table('employees') + .addColumnsToAdd([ + Column('employee_id', 'INT').primaryKey().notNull(), + Column('employee_name', 'VARCHAR(100)').notNull(), + ]); + + const result2 = await query2.execute(executeFunction); + expect(result2).toEqual(undefined); + + let executeObject: { + [key: string]: any, + manager?: { + execute?: (queryString: string) => Promise; + }; + } = { + execute: async (queryString: string) => { + // Mock execution function + expect(queryString).toSatisfy((value: string | string[]) => { + if (typeof value !== 'string' && !Array.isArray(value)) { + return false; + } + return true; + }); + } + }; + + const query3 = Query + .table.create + .table('customers') + .setColumns([ + Column('customer_id', 'INT').primaryKey().notNull(), + Column('customer_name', 'VARCHAR(100)').notNull() + ]); + + const result3 = await query3.execute(executeObject); + expect(result3).toEqual(undefined); + + executeObject.manager = { + execute: async (queryString: string) => { + // Mock execution function + expect(queryString).toSatisfy((value: string | string[]) => { + if (typeof value !== 'string' && !Array.isArray(value)) { + return false; + } + return true; + }); + } + } + + const query4 = Query + .table.create + .table('suppliers') + .setColumns([ + Column('supplier_id', 'INT').primaryKey().notNull(), + Column('supplier_name', 'VARCHAR(100)').notNull() + ]); + + const result4 = await query4.execute(executeObject); + expect(result4).toEqual(undefined); + + const query5 = Query + .table.drop + .table('old_table'); + + delete executeObject.manager.execute; + + await expect(query5.execute(executeObject)).rejects.toThrowError(); + + // Ignore manager + const query6 = Query + .table.drop + .table('another_old_table'); + + const result6 = await query6.execute(executeObject, true); + expect(result6).toEqual(undefined); + + const query7 = Query + .table.alter + .table('departments') + .addColumnsToAdd([ + Column('department_id', 'INT').primaryKey().notNull(), + Column('department_name', 'VARCHAR(100)').notNull(), + ]); + + executeObject.manager = { + execute: async (queryString: string) => { + // Mock execution function + expect(queryString).toSatisfy((value: string | string[]) => { + if (typeof value !== 'string' && !Array.isArray(value)) { + return false; + } + return true; + }); + } + }; + + const result7 = await query7.execute(executeObject); + expect(result7).toEqual(undefined); + + delete executeObject.manager; + + expect(await query7.execute(executeObject)).toEqual(undefined); + }); + + it('should test utility spaceLines', () => { + const query = Query + .table.create; + + const noSpaces = `CREATE TABLE "test" (\nid INT\n);`; + expect(query['spaceLines'](noSpaces, 1)).toEqual(" CREATE TABLE \"test\" (\n id INT\n );"); + + + }) +});