uranio 1.1.7 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,6 +33,6 @@
33
33
  "mongodb": "^7.1.0",
34
34
  "mysql2": "^3.17.5",
35
35
  "pg": "^8.18.0",
36
- "uranio": "1.1.7"
36
+ "uranio": "1.1.9"
37
37
  }
38
38
  }
@@ -20,12 +20,12 @@ export class MySQLAtomClient<S extends atom_types.mysql_atom> {
20
20
  ) {}
21
21
 
22
22
  public async getAtom(where: where_types.Where<S>): Promise<S | null> {
23
- const {query, map} = sql.param.compose_select({
23
+ const query = sql.build.select({
24
24
  table: this.name,
25
25
  where,
26
26
  limit: '1',
27
27
  });
28
- const [rows] = await this.client.exe(query, map);
28
+ const [rows] = await this.client.exe(query);
29
29
  if (Array.isArray(rows) && rows[0]) {
30
30
  return rows[0] as S;
31
31
  }
@@ -41,13 +41,13 @@ export class MySQLAtomClient<S extends atom_types.mysql_atom> {
41
41
  order?: sql_types.OrderBy;
42
42
  limit?: string;
43
43
  }): Promise<S[]> {
44
- const {query, map} = sql.param.compose_select({
44
+ const query = sql.build.select({
45
45
  table: this.name,
46
46
  where,
47
47
  order,
48
48
  limit,
49
49
  });
50
- const [rows] = await this.client.exe(query, map);
50
+ const [rows] = await this.client.exe(query);
51
51
  if (Array.isArray(rows)) {
52
52
  return rows as S[];
53
53
  }
@@ -59,12 +59,12 @@ export class MySQLAtomClient<S extends atom_types.mysql_atom> {
59
59
  for(const [key, _] of Object.entries(shape)){
60
60
  columns.push(key as keyof S);
61
61
  }
62
- const {query, query_records} = sql.param.compose_insert({
62
+ const query = sql.build.insert({
63
63
  table: this.name,
64
64
  columns,
65
65
  records: [shape]
66
66
  });
67
- const response = await this.client.exe(query, query_records);
67
+ const response = await this.client.exe(query);
68
68
  return response;
69
69
  }
70
70
 
@@ -76,12 +76,12 @@ export class MySQLAtomClient<S extends atom_types.mysql_atom> {
76
76
  for(const [key, _] of Object.entries(shapes[0])){
77
77
  columns.push(key as keyof S);
78
78
  }
79
- const {query, query_records} = sql.param.compose_insert({
79
+ const query = sql.build.insert({
80
80
  table: this.name,
81
81
  columns,
82
82
  records: shapes
83
83
  });
84
- const response = await this.client.exe(query, query_records);
84
+ const response = await this.client.exe(query);
85
85
  return response;
86
86
  }
87
87
 
@@ -89,21 +89,21 @@ export class MySQLAtomClient<S extends atom_types.mysql_atom> {
89
89
  atom: Partial<S>,
90
90
  where?: where_types.Where<S>,
91
91
  ): Promise<any> {
92
- const {query, map} = sql.param.compose_update({
92
+ const query = sql.build.update({
93
93
  table: this.name,
94
94
  where,
95
95
  update: atom
96
96
  });
97
- const response = await this.client.exe(query, map);
97
+ const response = await this.client.exe(query);
98
98
  return response;
99
99
  }
100
100
 
101
101
  public async deleteAtoms(where: where_types.Where<S>): Promise<any> {
102
- const {query, map} = sql.param.compose_delete({
102
+ const query = sql.build.del({
103
103
  table: this.name,
104
104
  where,
105
105
  });
106
- const response = await this.client.exe(query, map);
106
+ const response = await this.client.exe(query);
107
107
  return response;
108
108
  }
109
109
  }
@@ -20,12 +20,12 @@ export class PostgreSQLAtomClient<S extends atom_types.postgresql_atom> {
20
20
  ) {}
21
21
 
22
22
  public async getAtom(where: where_types.Where<S>): Promise<S | null> {
23
- const {query, map} = sql.param.compose_select({
23
+ const query = sql.build.select({
24
24
  table: this.name,
25
25
  where,
26
26
  limit: '1',
27
27
  });
28
- const [rows] = await this.client.exe(query, map);
28
+ const [rows] = await this.client.exe(query);
29
29
  if (Array.isArray(rows) && rows[0]) {
30
30
  return rows[0] as S;
31
31
  }
@@ -41,13 +41,13 @@ export class PostgreSQLAtomClient<S extends atom_types.postgresql_atom> {
41
41
  order?: sql_types.OrderBy;
42
42
  limit?: string;
43
43
  }): Promise<S[]> {
44
- const {query, map} = sql.param.compose_select({
44
+ const query = sql.build.select({
45
45
  table: this.name,
46
46
  where,
47
47
  order,
48
48
  limit,
49
49
  });
50
- const [rows] = await this.client.exe(query, map);
50
+ const [rows] = await this.client.exe(query);
51
51
  if (Array.isArray(rows)) {
52
52
  return rows as S[];
53
53
  }
@@ -59,12 +59,12 @@ export class PostgreSQLAtomClient<S extends atom_types.postgresql_atom> {
59
59
  for(const [key, _] of Object.entries(shape)){
60
60
  columns.push(key as keyof S);
61
61
  }
62
- const {query, query_records} = sql.param.compose_insert({
62
+ const query = sql.build.insert({
63
63
  table: this.name,
64
64
  columns,
65
65
  records: [shape]
66
66
  });
67
- const response = await this.client.exe(query, query_records);
67
+ const response = await this.client.exe(query);
68
68
  return response;
69
69
  }
70
70
 
@@ -76,12 +76,12 @@ export class PostgreSQLAtomClient<S extends atom_types.postgresql_atom> {
76
76
  for(const [key, _] of Object.entries(shapes[0])){
77
77
  columns.push(key as keyof S);
78
78
  }
79
- const {query, query_records} = sql.param.compose_insert({
79
+ const query = sql.build.insert({
80
80
  table: this.name,
81
81
  columns,
82
82
  records: shapes
83
83
  });
84
- const response = await this.client.exe(query, query_records);
84
+ const response = await this.client.exe(query);
85
85
  return response;
86
86
  }
87
87
 
@@ -89,21 +89,21 @@ export class PostgreSQLAtomClient<S extends atom_types.postgresql_atom> {
89
89
  atom: Partial<S>,
90
90
  where?: where_types.Where<S>,
91
91
  ): Promise<any> {
92
- const {query, map} = sql.param.compose_update({
92
+ const query = sql.build.update({
93
93
  table: this.name,
94
94
  where,
95
95
  update: atom
96
96
  });
97
- const response = await this.client.exe(query, map);
97
+ const response = await this.client.exe(query);
98
98
  return response;
99
99
  }
100
100
 
101
101
  public async deleteAtoms(where: where_types.Where<S>): Promise<any> {
102
- const {query, map} = sql.param.compose_delete({
102
+ const query = sql.build.del({
103
103
  table: this.name,
104
104
  where,
105
105
  });
106
- const response = await this.client.exe(query, map);
106
+ const response = await this.client.exe(query);
107
107
  return response;
108
108
  }
109
109
  }
@@ -38,16 +38,16 @@ export class MongoDBClient {
38
38
  try {
39
39
  // Check if the client is already connected by pinging the database
40
40
  await this.client.db().command({ping: 1});
41
- log.trace('MongoDB is already connected.');
41
+ log.debug('MongoDB is already connected.');
42
42
  } catch (error) {
43
43
  log.trace('Connecting to MongoDB...');
44
44
  await this.client.connect();
45
- log.trace('MongoDB connected.');
45
+ log.debug('MongoDB connected.');
46
46
  }
47
47
  }
48
48
  public async disconnect() {
49
- log.trace('Disconnecting...');
49
+ log.trace('Disconnecting from MongoDB...');
50
50
  await this.client.close();
51
- log.trace('Disconnected.');
51
+ log.debug('MongoDB disconnected.');
52
52
  }
53
53
  }
@@ -8,6 +8,7 @@
8
8
 
9
9
  import mysql from 'mysql2/promise';
10
10
  import {log} from '../log/index';
11
+ import {SQLStatement} from '../sql/template';
11
12
 
12
13
  export type MySQLClientParams = {
13
14
  uri: string;
@@ -31,12 +32,18 @@ export class MySQLClient {
31
32
  });
32
33
  }
33
34
  }
34
- public async exe(sql: string, values?: any) {
35
+ public async exe(sql: string | SQLStatement, values?: any): Promise<any[]> {
36
+ // Handle SQLStatement objects
37
+ if (sql instanceof SQLStatement) {
38
+ const {sql: query, values: params} = sql.mysql();
39
+ return this.exe(query, params);
40
+ }
41
+
35
42
  const with_values =
36
43
  typeof values !== 'undefined'
37
44
  ? ` with values [${Object.entries(values)}]`
38
45
  : '';
39
- log.trace(`Excuting query '${sql}'${with_values}`);
46
+ log.debug(`Excuting query '${sql}'${with_values}`);
40
47
  if (this.pool) {
41
48
  return await this._execute_from_pool_connection(sql, values);
42
49
  }
@@ -8,6 +8,7 @@
8
8
 
9
9
  import {Pool, PoolClient} from 'pg';
10
10
  import {log} from '../log/index';
11
+ import {SQLStatement} from '../sql/template';
11
12
 
12
13
  export type PostgreSQLClientParams = {
13
14
  uri: string;
@@ -26,14 +27,22 @@ export class PostgreSQLClient {
26
27
  });
27
28
  }
28
29
  }
29
- public async exe(sql: string, values?: any) {
30
+ public async exe(sql: string | SQLStatement, values?: any): Promise<any[]> {
31
+ // Handle SQLStatement objects
32
+ if (sql instanceof SQLStatement) {
33
+ const {sql: query, values: params} = sql.postgres();
34
+ // Convert backticks to double quotes for PostgreSQL
35
+ const pgQuery = query.replace(/`/g, '"');
36
+ return this.exe(pgQuery, params);
37
+ }
38
+
30
39
  // Convert named parameters object to positional parameters array
31
40
  const {query, paramValues} = this._convert_named_to_positional(sql, values);
32
41
  const with_values =
33
42
  typeof paramValues !== 'undefined' && paramValues.length > 0
34
43
  ? ` with values [${paramValues}]`
35
44
  : '';
36
- log.trace(`Excuting query '${query}'${with_values}`);
45
+ log.debug(`Excuting query '${query}'${with_values}`);
37
46
  if (this.pool) {
38
47
  return await this._execute_from_pool_connection(query, paramValues);
39
48
  }
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Simplified SQL query builder
3
+ * Much simpler than full_query.ts + param_query.ts
4
+ *
5
+ * Key differences:
6
+ * - Uses template literals internally (no string replacement bugs)
7
+ * - Direct parameterization (no two-stage conversion)
8
+ * - Each value gets its own parameter (no duplicate value issues)
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+
13
+ import {SQL, SQLStatement} from './template';
14
+ import * as atom_types from '../types/atom';
15
+ import * as where_types from '../types/where';
16
+ import * as sql_types from '../types/sql';
17
+
18
+ /**
19
+ * Build a SELECT query
20
+ */
21
+ export function select<S extends atom_types.atom>({
22
+ projection = ['*'],
23
+ table,
24
+ where,
25
+ order,
26
+ limit,
27
+ }: {
28
+ projection?: string[];
29
+ table: string;
30
+ where?: where_types.Where<S>;
31
+ order?: sql_types.OrderBy;
32
+ limit?: string;
33
+ }): SQLStatement {
34
+ // Start with SELECT columns FROM table
35
+ const cols = projection.join(', ');
36
+ let query = new SQLStatement([`SELECT ${cols} FROM \`${table}\``], []);
37
+
38
+ // Add WHERE clause
39
+ if (where && Object.keys(where).length > 0) {
40
+ const whereStmt = _where(where);
41
+ query.append(' WHERE ').append(whereStmt);
42
+ }
43
+
44
+ // Add ORDER BY
45
+ if (order && Object.keys(order).length > 0) {
46
+ query.append(_orderBy(order));
47
+ }
48
+
49
+ // Add LIMIT
50
+ if (limit) {
51
+ query.append(_limit(limit));
52
+ }
53
+
54
+ return query;
55
+ }
56
+
57
+ /**
58
+ * Build an INSERT query
59
+ */
60
+ export function insert<S extends atom_types.atom>({
61
+ table,
62
+ columns,
63
+ records,
64
+ }: {
65
+ table: string;
66
+ columns: (keyof S)[];
67
+ records: Partial<S>[];
68
+ }): SQLStatement {
69
+ const colNames = columns.map(c => `\`${String(c)}\``).join(', ');
70
+ let query = new SQLStatement([`INSERT INTO \`${table}\` (${colNames}) VALUES `], []);
71
+
72
+ // Add each record as (?, ?, ?) with actual values
73
+ for (let i = 0; i < records.length; i++) {
74
+ if (i > 0) query.append(', ');
75
+
76
+ query.append('(');
77
+ for (let j = 0; j < columns.length; j++) {
78
+ if (j > 0) query.append(', ');
79
+ const col = columns[j];
80
+ const rec = records[i];
81
+ if (rec && col) {
82
+ const value = rec[col];
83
+ query.append(SQL`${value}`);
84
+ }
85
+ }
86
+ query.append(')');
87
+ }
88
+
89
+ return query;
90
+ }
91
+
92
+ /**
93
+ * Build an UPDATE query
94
+ */
95
+ export function update<S extends atom_types.atom>({
96
+ table,
97
+ update,
98
+ where,
99
+ }: {
100
+ table: string;
101
+ update: Partial<S>;
102
+ where?: where_types.Where<S>;
103
+ }): SQLStatement {
104
+ let query = new SQLStatement([`UPDATE \`${table}\` SET `], []);
105
+
106
+ // Add SET clause
107
+ const keys = Object.keys(update);
108
+ for (let i = 0; i < keys.length; i++) {
109
+ if (i > 0) query.append(', ');
110
+ const key = keys[i];
111
+ if (key) {
112
+ const value = (update as any)[key];
113
+ query.append(`${key} = `).append(SQL`${value}`);
114
+ }
115
+ }
116
+
117
+ // Add WHERE clause
118
+ if (where) {
119
+ query.append(' WHERE ').append(_where(where));
120
+ }
121
+
122
+ return query;
123
+ }
124
+
125
+ /**
126
+ * Build a DELETE query
127
+ */
128
+ export function del<S extends atom_types.atom>({
129
+ table,
130
+ where,
131
+ }: {
132
+ table: string;
133
+ where?: where_types.Where<S>;
134
+ }): SQLStatement {
135
+ let query = new SQLStatement([`DELETE FROM \`${table}\``], []);
136
+
137
+ if (where) {
138
+ query.append(' WHERE ').append(_where(where));
139
+ }
140
+
141
+ return query;
142
+ }
143
+
144
+ /**
145
+ * Build WHERE clause
146
+ */
147
+ function _where<S extends atom_types.atom>(where: where_types.Where<S>): SQLStatement {
148
+ const parts: SQLStatement[] = [];
149
+
150
+ for (const [key, value] of Object.entries(where)) {
151
+ // Root operators: $and, $or, $nor
152
+ if (sql_types.root_operators.includes(key as sql_types.RootOperator)) {
153
+ if (!Array.isArray(value)) {
154
+ throw new Error(`Root operator '${key}' must have an Array as value`);
155
+ }
156
+
157
+ const subclauses = value.map(el => _where(el));
158
+ const op = key === '$and' ? ' AND ' : key === '$or' ? ' OR ' : ' NOR ';
159
+
160
+ let combined = new SQLStatement(['('], []);
161
+ const firstClause = subclauses[0];
162
+ if (firstClause) {
163
+ combined.append(firstClause);
164
+ }
165
+ for (let i = 1; i < subclauses.length; i++) {
166
+ const clause = subclauses[i];
167
+ if (clause) {
168
+ combined.append(op).append(clause);
169
+ }
170
+ }
171
+ combined.append(')');
172
+ parts.push(combined);
173
+ continue;
174
+ }
175
+
176
+ // RegExp
177
+ if (value instanceof RegExp) {
178
+ parts.push(new SQLStatement([`\`${key}\` REGEXP `], []).append(SQL`${value.source}`));
179
+ continue;
180
+ }
181
+
182
+ // Filter operators: {age: {$gte: 18}}
183
+ if (value && typeof value === 'object' && !(value instanceof Date)) {
184
+ if (Object.keys(value).length === 0) {
185
+ throw new Error('Cannot compare to empty object value');
186
+ }
187
+ parts.push(_filter(key, value as sql_types.Operator));
188
+ continue;
189
+ }
190
+
191
+ // Simple equality
192
+ parts.push(new SQLStatement([`\`${key}\` = `], []).append(SQL`${value}`));
193
+ }
194
+
195
+ // Join with AND
196
+ if (parts.length === 0) {
197
+ return new SQLStatement([''], []);
198
+ }
199
+ let result = parts[0]!;
200
+ for (let i = 1; i < parts.length; i++) {
201
+ const part = parts[i];
202
+ if (part) {
203
+ result.append(' AND ').append(part);
204
+ }
205
+ }
206
+ return result;
207
+ }
208
+
209
+ /**
210
+ * Build filter operators like {$gte: 18, $lte: 65}
211
+ */
212
+ function _filter(column: string, operator: sql_types.Operator): SQLStatement {
213
+ const parts: SQLStatement[] = [];
214
+
215
+ for (const [key, value] of Object.entries(operator)) {
216
+ switch (key) {
217
+ case '$eq':
218
+ parts.push(new SQLStatement([`\`${column}\` = `], []).append(SQL`${value}`));
219
+ break;
220
+ case '$gt':
221
+ parts.push(new SQLStatement([`\`${column}\` > `], []).append(SQL`${value}`));
222
+ break;
223
+ case '$gte':
224
+ parts.push(new SQLStatement([`\`${column}\` >= `], []).append(SQL`${value}`));
225
+ break;
226
+ case '$lt':
227
+ parts.push(new SQLStatement([`\`${column}\` < `], []).append(SQL`${value}`));
228
+ break;
229
+ case '$lte':
230
+ parts.push(new SQLStatement([`\`${column}\` <= `], []).append(SQL`${value}`));
231
+ break;
232
+ case '$ne':
233
+ parts.push(new SQLStatement([`\`${column}\` <> `], []).append(SQL`${value}`));
234
+ break;
235
+ case '$in':
236
+ if (Array.isArray(value) && value.length > 0) {
237
+ let stmt = new SQLStatement([`\`${column}\` IN (`], []);
238
+ stmt.append(SQL`${value[0]}`);
239
+ for (let i = 1; i < value.length; i++) {
240
+ stmt.append(', ').append(SQL`${value[i]}`);
241
+ }
242
+ stmt.append(')');
243
+ parts.push(stmt);
244
+ }
245
+ break;
246
+ case '$nin':
247
+ if (Array.isArray(value) && value.length > 0) {
248
+ let stmt = new SQLStatement([`\`${column}\` NOT IN (`], []);
249
+ stmt.append(SQL`${value[0]}`);
250
+ for (let i = 1; i < value.length; i++) {
251
+ stmt.append(', ').append(SQL`${value[i]}`);
252
+ }
253
+ stmt.append(')');
254
+ parts.push(stmt);
255
+ }
256
+ break;
257
+ case '$exists':
258
+ const suffix = value === true ? ' IS NOT NULL' : ' IS NULL';
259
+ parts.push(new SQLStatement([`\`${column}\`${suffix}`], []));
260
+ break;
261
+ case '$not':
262
+ const notClause = _filter(column, value);
263
+ parts.push(new SQLStatement([`\`${column}\` NOT (`], []).append(notClause).append(')'));
264
+ break;
265
+ default:
266
+ throw new Error(`Invalid filter operator: ${key}`);
267
+ }
268
+ }
269
+
270
+ // Join with AND
271
+ if (parts.length === 0) {
272
+ return new SQLStatement([''], []);
273
+ }
274
+ let result = parts[0]!;
275
+ for (let i = 1; i < parts.length; i++) {
276
+ const part = parts[i];
277
+ if (part) {
278
+ result.append(' AND ').append(part);
279
+ }
280
+ }
281
+ return result;
282
+ }
283
+
284
+ /**
285
+ * Build ORDER BY clause (returns plain string - no params)
286
+ */
287
+ function _orderBy(order: sql_types.OrderBy): string {
288
+ const VALID_COLUMN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
289
+ const VALID_DIR = ['asc', 'desc'];
290
+
291
+ const parts: string[] = [];
292
+ for (const [col, dir] of Object.entries(order)) {
293
+ if (!VALID_COLUMN.test(col)) {
294
+ throw new Error(`Invalid column name in ORDER BY: "${col}"`);
295
+ }
296
+ const normalized = dir.toLowerCase();
297
+ if (!VALID_DIR.includes(normalized)) {
298
+ throw new Error(`Invalid direction in ORDER BY: "${dir}"`);
299
+ }
300
+ parts.push(`\`${col}\` ${normalized.toUpperCase()}`);
301
+ }
302
+
303
+ return ` ORDER BY ${parts.join(', ')}`;
304
+ }
305
+
306
+ /**
307
+ * Build LIMIT clause (returns plain string - no params)
308
+ */
309
+ function _limit(limit: string): string {
310
+ const pattern = /^(\d+)(?:\s*,\s*(\d+)|\s+OFFSET\s+(\d+))?$/i;
311
+ if (!pattern.test(limit)) {
312
+ throw new Error(`Invalid LIMIT clause: "${limit}"`);
313
+ }
314
+ return ` LIMIT ${limit}`;
315
+ }
@@ -6,8 +6,7 @@
6
6
  *
7
7
  */
8
8
 
9
- import * as full from './full_query';
10
- export {full};
9
+ import * as build from './builder';
10
+ export {build};
11
11
 
12
- import * as param from './param_query';
13
- export {param};
12
+ export {SQL, SQLStatement} from './template';
@@ -0,0 +1,100 @@
1
+ /**
2
+ * SQL template string builder
3
+ * Simplified implementation inspired by sql-template-strings
4
+ *
5
+ * This provides a secure way to build SQL queries using template literals.
6
+ * Values are never embedded into SQL strings, preventing SQL injection.
7
+ *
8
+ * Usage:
9
+ * const name = 'John';
10
+ * const query = SQL`SELECT * FROM users WHERE name = ${name}`;
11
+ *
12
+ * // For MySQL:
13
+ * query.mysql() // { sql: "SELECT * FROM users WHERE name = ?", values: ['John'] }
14
+ *
15
+ * // For PostgreSQL:
16
+ * query.postgres() // { sql: "SELECT * FROM users WHERE name = $1", values: ['John'] }
17
+ *
18
+ * @packageDocumentation
19
+ */
20
+
21
+ export class SQLStatement {
22
+ private strings: string[];
23
+ private values: any[];
24
+
25
+ constructor(strings: string[], values: any[]) {
26
+ this.strings = strings;
27
+ this.values = values;
28
+ }
29
+
30
+ /**
31
+ * Returns MySQL/MariaDB-compatible query with ? placeholders
32
+ */
33
+ mysql(): { sql: string; values: any[] } {
34
+ return {
35
+ sql: this.strings.join('?'),
36
+ values: this.values,
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Returns PostgreSQL-compatible query with $1, $2, ... placeholders
42
+ */
43
+ postgres(): { sql: string; values: any[] } {
44
+ const sql = this.strings.reduce((prev, curr, i) => {
45
+ if (i === 0) return curr;
46
+ return prev + '$' + i + curr;
47
+ }, '');
48
+ return {
49
+ sql,
50
+ values: this.values,
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Append another SQL statement or string
56
+ */
57
+ append(statement: SQLStatement | string): this {
58
+ if (statement instanceof SQLStatement) {
59
+ // Merge the last string of this with the first string of statement
60
+ const lastIdx = this.strings.length - 1;
61
+ const firstString = statement.strings[0] || '';
62
+ this.strings[lastIdx] = (this.strings[lastIdx] || '') + firstString;
63
+ this.strings.push(...statement.strings.slice(1));
64
+ this.values.push(...statement.values);
65
+ } else {
66
+ // Just append a string to the last segment
67
+ const lastIdx = this.strings.length - 1;
68
+ this.strings[lastIdx] = (this.strings[lastIdx] || '') + statement;
69
+ }
70
+ return this;
71
+ }
72
+
73
+ /**
74
+ * Get the raw values array
75
+ */
76
+ getValues(): any[] {
77
+ return this.values;
78
+ }
79
+
80
+ /**
81
+ * Get the number of parameters
82
+ */
83
+ get paramCount(): number {
84
+ return this.values.length;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Tagged template function for SQL queries
90
+ *
91
+ * @example
92
+ * const name = 'John';
93
+ * const age = 30;
94
+ * const query = SQL`SELECT * FROM users WHERE name = ${name} AND age = ${age}`;
95
+ */
96
+ export function SQL(strings: TemplateStringsArray, ...values: any[]): SQLStatement {
97
+ return new SQLStatement(Array.from(strings), values);
98
+ }
99
+
100
+ export default SQL;