@maestro-js/sql 1.0.0-alpha.0

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.
package/README.md ADDED
@@ -0,0 +1,311 @@
1
+ # @maestro-js/sql
2
+
3
+ Composable SQL query builders for constructing parameterized WHERE clauses and Common Table Expressions (CTEs). Standalone utility package with no runtime dependencies on other maestro packages.
4
+
5
+ ## Quick Setup
6
+
7
+ ```ts
8
+ import { Sql } from '@maestro-js/sql'
9
+
10
+ // Dynamic WHERE clause
11
+ const where = Sql.where()
12
+ where.and('status = ?', ['active'])
13
+ where.and('price > ?', [50])
14
+ await Db.select(`SELECT * FROM products WHERE ${where.query}`, where.parameters)
15
+
16
+ // CTE wrapping a Db service
17
+ const withTotals = Sql.cte({
18
+ orderTotals: { subquery: '(SELECT userId, SUM(amount) as total FROM orders GROUP BY userId)', params: [] }
19
+ }, Db)
20
+ await withTotals.select('SELECT * FROM orderTotals WHERE total > ?', [500])
21
+ ```
22
+
23
+ ## WHERE Clause Builder
24
+
25
+ Build parameterized SQL WHERE clauses incrementally with `Sql.where()`. Conditions are appended via `and()` and `or()`. If no conditions are added, `query` returns `'TRUE'`.
26
+
27
+ ### Basic Conditions
28
+
29
+ ```ts
30
+ const where = Sql.where()
31
+ where.and('status = ?', ['active'])
32
+ where.and('created_at > ?', ['2024-01-01'])
33
+ // where.query => 'status = ? AND created_at > ?'
34
+ // where.parameters => ['active', '2024-01-01']
35
+ ```
36
+
37
+ ### Mixed AND/OR
38
+
39
+ ```ts
40
+ const where = Sql.where()
41
+ where.and('a = ?', [1])
42
+ where.or('b = ?', [2])
43
+ where.and('c = ?', [3])
44
+ // where.query => 'a = ? OR b = ? AND c = ?'
45
+ // where.parameters => [1, 2, 3]
46
+ ```
47
+
48
+ ### Nested Groups (Parenthesized Sub-Conditions)
49
+
50
+ Pass a callback to `and()` or `or()` to create parenthesized groups using a sub-builder:
51
+
52
+ ```ts
53
+ const where = Sql.where()
54
+ where.and('status = ?', ['active'])
55
+ where.and(sub => {
56
+ sub.or('role = ?', ['admin'])
57
+ sub.or('role = ?', ['editor'])
58
+ })
59
+ // where.query => 'status = ? AND (role = ? OR role = ?)'
60
+ // where.parameters => ['active', 'admin', 'editor']
61
+ ```
62
+
63
+ ### Deeply Nested Groups
64
+
65
+ ```ts
66
+ const where = Sql.where()
67
+ where.and('a = ?', [1])
68
+ where.or(qb => {
69
+ qb.and('b = ?', [2])
70
+ qb.or(iqb => {
71
+ iqb.and('c = ?', [3])
72
+ iqb.and('d = ?', [4])
73
+ })
74
+ })
75
+ // where.query => 'a = ? OR (b = ? OR (c = ? AND d = ?))'
76
+ // where.parameters => [1, 2, 3, 4]
77
+ ```
78
+
79
+ ### IN Clauses
80
+
81
+ ```ts
82
+ const where = Sql.where()
83
+ where.and('id IN (?)', [[1, 2, 3]])
84
+ // where.query => 'id IN (?)'
85
+ // where.parameters => [[1, 2, 3]]
86
+ ```
87
+
88
+ ### Cloning
89
+
90
+ Pass an existing builder to `Sql.where()` to clone it. The clone is independent of the original:
91
+
92
+ ```ts
93
+ const original = Sql.where()
94
+ original.and('a = ?', [1])
95
+
96
+ const clone = Sql.where(original)
97
+ clone.and('b = ?', [2])
98
+
99
+ // original.query => 'a = ?'
100
+ // clone.query => 'a = ? AND b = ?'
101
+ ```
102
+
103
+ ### Empty Sub-Builder
104
+
105
+ If a callback adds no conditions, the group is silently skipped:
106
+
107
+ ```ts
108
+ const where = Sql.where()
109
+ where.and('a = ?', [1])
110
+ where.and(sub => {
111
+ // no conditions added
112
+ })
113
+ // where.query => 'a = ?'
114
+ ```
115
+
116
+ ## CTE Builder (Common Table Expressions)
117
+
118
+ Wrap a Db service with `Sql.cte()` so every query method auto-prepends the `WITH` clause and merges CTE parameters before the query parameters.
119
+
120
+ ### Basic CTE
121
+
122
+ ```ts
123
+ const withStats = Sql.cte({
124
+ userStats: {
125
+ subquery: '(SELECT userId, COUNT(*) as total FROM orders GROUP BY userId)',
126
+ params: []
127
+ }
128
+ }, Db)
129
+
130
+ const results = await withStats.select('SELECT * FROM userStats WHERE total > ?', [10])
131
+ // Executes: WITH userStats AS (SELECT userId, COUNT(*) ...) SELECT * FROM userStats WHERE total > ?
132
+ // Parameters: [10]
133
+ ```
134
+
135
+ ### Multiple CTEs
136
+
137
+ ```ts
138
+ const builder = Sql.cte({
139
+ activeUsers: {
140
+ subquery: '(SELECT * FROM users WHERE active = ?)',
141
+ params: [true]
142
+ },
143
+ recentOrders: {
144
+ subquery: '(SELECT * FROM orders WHERE created_at > ?)',
145
+ params: ['2024-01-01']
146
+ }
147
+ }, Db)
148
+
149
+ await builder.select('SELECT * FROM activeUsers JOIN recentOrders ON activeUsers.id = recentOrders.userId')
150
+ // WITH activeUsers AS (...), recentOrders AS (...) SELECT ...
151
+ // Parameters: [true, '2024-01-01']
152
+ ```
153
+
154
+ ### Recursive CTEs
155
+
156
+ Set `recursive: true` on any expression to trigger `WITH RECURSIVE`:
157
+
158
+ ```ts
159
+ const builder = Sql.cte({
160
+ hierarchy: {
161
+ subquery: '(SELECT id, parentId, name FROM categories WHERE parentId IS NULL UNION ALL SELECT c.id, c.parentId, c.name FROM categories c JOIN hierarchy h ON c.parentId = h.id)',
162
+ params: [],
163
+ recursive: true
164
+ }
165
+ }, Db)
166
+ // WITH RECURSIVE hierarchy AS (...)
167
+ ```
168
+
169
+ ### Dynamic Parameters
170
+
171
+ Pass a function for `params` to resolve values at query time:
172
+
173
+ ```ts
174
+ const builder = Sql.cte({
175
+ filtered: {
176
+ subquery: '(SELECT * FROM items WHERE tenant_id = ?)',
177
+ params: () => [getCurrentTenantId()]
178
+ }
179
+ }, Db)
180
+ ```
181
+
182
+ ### Re-Wrapping with fromDbService
183
+
184
+ Use `fromDbService()` to apply the same CTEs to a different Db service (e.g., inside a transaction):
185
+
186
+ ```ts
187
+ const cteBuilder = Sql.cte({ ... }, Db)
188
+ const txResult = await Db.transaction(async () => {
189
+ const txCte = cteBuilder.fromDbService(TxDb)
190
+ return txCte.select('SELECT * FROM ...')
191
+ })
192
+ ```
193
+
194
+ ### Wrapped Methods
195
+
196
+ The CTE builder exposes all standard Db methods: `select`, `selectIterable`, `insert`, `update`, `delete`, `statement`, `scalar`, `format`, `transaction`.
197
+
198
+ ## API Reference
199
+
200
+ ### Sql.where(existing?)
201
+
202
+ Create a new WhereClauseBuilder. Pass an existing builder to clone it.
203
+
204
+ ```ts
205
+ function WhereClauseBuilder(existing?: WhereClauseBuilder): WhereClauseBuilder
206
+ ```
207
+
208
+ **WhereClauseBuilder interface:**
209
+
210
+ | Member | Type | Description |
211
+ |--------------|--------------------------------------------------------|----------------------------------------------------------|
212
+ | `and()` | `(condition: string \| Callback, params?: any[]) => void` | Append condition with AND, or parenthesized group via callback |
213
+ | `or()` | `(condition: string \| Callback, params?: any[]) => void` | Append condition with OR, or parenthesized group via callback |
214
+ | `query` | `string` (readonly getter) | Assembled clause string, or `'TRUE'` if empty |
215
+ | `parameters` | `any[]` (readonly getter) | Parameter array matching `?` placeholders in query |
216
+
217
+ ### Sql.cte(expressions, db)
218
+
219
+ Create a CTE builder wrapping a Db service.
220
+
221
+ ```ts
222
+ function CommonTableExpressionBuilder(
223
+ commonTableExpressions: CommonTableExpressionSet,
224
+ db: CteDbService
225
+ ): CteDbService & { fromDbService(db: CteDbService): CteDbService }
226
+ ```
227
+
228
+ **CommonTableExpressionSet:**
229
+
230
+ ```ts
231
+ interface CommonTableExpressionSet {
232
+ [name: string]: {
233
+ subquery: string // CTE body wrapped in parentheses
234
+ params?: any[] | (() => any[]) // Static array or dynamic resolver
235
+ recursive?: boolean // Triggers WITH RECURSIVE
236
+ }
237
+ }
238
+ ```
239
+
240
+ ### Type Exports
241
+
242
+ Access types through the `Sql` namespace:
243
+
244
+ ```ts
245
+ Sql.cte.Expression // CommonTableExpressionConfig
246
+ Sql.cte.ExpressionSet // CommonTableExpressionSet
247
+ Sql.cte.DbService // CteDbService
248
+ Sql.where.Builder // WhereClauseBuilder
249
+ Sql.where.Callback // WhereClauseCallback
250
+ ```
251
+
252
+ `Sql.commonTableExpression` is an alias for `Sql.cte` (both the function and its namespace types).
253
+
254
+ ## Important: Avoid Table Name Aliases
255
+
256
+ When writing SQL queries with these builders, avoid table name aliases when possible. Write full table names for clarity:
257
+
258
+ ```ts
259
+ // Preferred
260
+ where.and('users.status = ?', ['active'])
261
+ await builder.select('SELECT users.id, orders.total FROM users JOIN orders ON users.id = orders.userId')
262
+
263
+ // Avoid
264
+ where.and('u.status = ?', ['active'])
265
+ await builder.select('SELECT u.id, o.total FROM users u JOIN orders o ON u.id = o.userId')
266
+ ```
267
+
268
+ ## Testing
269
+
270
+ ```bash
271
+ # Run tests
272
+ pnpm --filter @maestro-js/sql test
273
+
274
+ # Single test file
275
+ cd packages/sql && npx beartest ./tests/where-clause.test.ts
276
+
277
+ # Typecheck
278
+ pnpm --filter @maestro-js/sql typecheck
279
+ ```
280
+
281
+ Tests use `beartest-js` with `expect` for assertions. Test file location: `packages/sql/tests/where-clause.test.ts`.
282
+
283
+ ## Cross-Package Integration
284
+
285
+ - **No runtime dependencies** on other maestro packages
286
+ - **Works with @maestro-js/db**: SQL builders produce `{ query, parameters }` pairs for Db to execute. The CTE builder wraps a Db service (or any object matching `CteDbService`) to auto-prepend WITH clauses.
287
+ - **@maestro-js/db is a devDependency** only, used for the `CteDbService` type interface
288
+
289
+ ### Typical Usage with Db
290
+
291
+ ```ts
292
+ import { Sql } from '@maestro-js/sql'
293
+ import { Db } from '@maestro-js/db'
294
+
295
+ // WHERE builder feeds into Db.select
296
+ const where = Sql.where()
297
+ where.and('active = ?', [true])
298
+ if (minPrice) where.and('price >= ?', [minPrice])
299
+ if (category) where.and('category = ?', [category])
300
+
301
+ const products = await Db.select(`SELECT * FROM products WHERE ${where.query}`, where.parameters)
302
+
303
+ // CTE builder wraps Db directly
304
+ const withMetrics = Sql.cte({
305
+ dailySales: {
306
+ subquery: '(SELECT DATE(created_at) as day, SUM(amount) as total FROM orders GROUP BY DATE(created_at))',
307
+ params: []
308
+ }
309
+ }, Db)
310
+ const trends = await withMetrics.select('SELECT * FROM dailySales WHERE total > ?', [1000])
311
+ ```
@@ -0,0 +1,203 @@
1
+ /** Map of CTE names to their subquery, parameters, and recursive flag */
2
+ interface CommonTableExpressionSet {
3
+ [name: string]: {
4
+ /** The CTE body, wrapped in parentheses (e.g. `'(SELECT * FROM users WHERE active = ?)'`) */
5
+ subquery: string;
6
+ /** Parameters for the subquery placeholders — pass a function for dynamic resolution */
7
+ params?: any[] | (() => any[]);
8
+ /** Marks this CTE as recursive — triggers `WITH RECURSIVE` for the entire statement */
9
+ recursive?: boolean;
10
+ };
11
+ }
12
+ /** Merged configuration for a single CTE after parameter resolution */
13
+ interface CommonTableExpressionConfig {
14
+ subquery: string;
15
+ params?: any[];
16
+ recursive?: boolean;
17
+ }
18
+ /**
19
+ * Db service interface that CTE builders accept and return.
20
+ *
21
+ * Mirrors the core Db service methods. The CTE builder wraps each method to
22
+ * automatically prepend the `WITH` clause and merge CTE parameters before
23
+ * the query parameters.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const withTotals = Sql.cte({
28
+ * orderTotals: { subquery: '(SELECT userId, SUM(amount) as total FROM orders GROUP BY userId)', params: [] }
29
+ * }, Db)
30
+ * await withTotals.select('SELECT * FROM orderTotals WHERE total > ?', [500])
31
+ * ```
32
+ */
33
+ interface CteDbService {
34
+ /** Executes a SELECT query, returning an array of typed rows */
35
+ select<T = Record<string, unknown>>(query: string, parameters?: any[]): Promise<T[]>;
36
+ /** Executes a SELECT query, returning an async iterable for streaming results */
37
+ selectIterable<T = Record<string, unknown>>(query: string, parameters?: any[]): any;
38
+ /** Executes an INSERT query, returning affected row count and insert ID */
39
+ insert(query: string, parameters?: any[]): Promise<{
40
+ affectedRows: number;
41
+ insertId: number;
42
+ }>;
43
+ /** Executes an UPDATE query, returning affected and changed row counts */
44
+ update(query: string, parameters?: any[]): Promise<{
45
+ affectedRows: number;
46
+ changedRows: number;
47
+ }>;
48
+ /** Executes a DELETE query, returning affected row count */
49
+ delete(query: string, parameters?: any[]): Promise<{
50
+ affectedRows: number;
51
+ }>;
52
+ /** Executes an arbitrary SQL statement */
53
+ statement<T = unknown>(query: string, parameters?: any[]): Promise<T>;
54
+ /** Executes a query and returns a single scalar value, or `null` */
55
+ scalar<T = unknown>(query: string, parameters?: any[]): Promise<T | null>;
56
+ /** Executes the callback within a database transaction */
57
+ transaction: <T>(callback: () => Promise<T>) => Promise<T>;
58
+ /** Formats a query with interpolated parameters for debugging */
59
+ format: (query: string, params?: any[]) => string;
60
+ }
61
+ /**
62
+ * Creates a CTE builder that wraps a Db service with Common Table Expressions.
63
+ *
64
+ * Every query method on the returned object automatically prepends the `WITH`
65
+ * clause and merges CTE parameters before the query parameters. Use
66
+ * `fromDbService()` to re-wrap a different Db service with the same CTEs.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const withStats = Sql.cte({
71
+ * userStats: { subquery: '(SELECT userId, COUNT(*) as total FROM orders GROUP BY userId)', params: [] }
72
+ * }, Db)
73
+ * const results = await withStats.select('SELECT * FROM userStats WHERE total > ?', [10])
74
+ * ```
75
+ */
76
+ declare function CommonTableExpressionBuilder(commonTableExpressions: CommonTableExpressionSet, db: CteDbService): {
77
+ fromDbService: (db: CteDbService) => CteDbService;
78
+ select: <T = Record<string, unknown>>(query: string, parameters?: any[]) => Promise<T[]>;
79
+ selectIterable: <T = Record<string, unknown>>(query: string, parameters?: any[]) => any;
80
+ insert: (query: string, parameters?: any[]) => Promise<{
81
+ affectedRows: number;
82
+ insertId: number;
83
+ }>;
84
+ update: (query: string, parameters?: any[]) => Promise<{
85
+ affectedRows: number;
86
+ changedRows: number;
87
+ }>;
88
+ delete: (query: string, parameters?: any[]) => Promise<{
89
+ affectedRows: number;
90
+ }>;
91
+ statement: <T = unknown>(query: string, parameters?: any[]) => Promise<T>;
92
+ scalar: <T = unknown>(query: string, parameters?: any[]) => Promise<T | null>;
93
+ format: (query: string, params?: any[]) => string;
94
+ transaction: <T>(callback: () => Promise<T>) => Promise<T>;
95
+ };
96
+
97
+ /** Callback that receives a sub-builder for creating parenthesized condition groups */
98
+ type WhereClauseCallback = (builder: WhereClauseBuilder) => void;
99
+ /**
100
+ * Mutable builder for constructing parameterized SQL WHERE clauses.
101
+ *
102
+ * Conditions are appended incrementally with {@link and} and {@link or}. Pass a
103
+ * callback to either method to wrap conditions in parentheses. Read {@link query}
104
+ * and {@link parameters} to get the assembled clause and its matching parameter array.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * const where = Sql.where()
109
+ * where.and('status = ?', ['active'])
110
+ * where.and(sub => {
111
+ * sub.or('role = ?', ['admin'])
112
+ * sub.or('role = ?', ['editor'])
113
+ * })
114
+ * where.query // 'status = ? AND (role = ? OR role = ?)'
115
+ * where.parameters // ['active', 'admin', 'editor']
116
+ * ```
117
+ */
118
+ interface WhereClauseBuilder {
119
+ /** Appends a condition joined by `AND`, or a parenthesized group when given a callback */
120
+ and(condition: string | WhereClauseCallback, params?: any[]): void;
121
+ /** Appends a condition joined by `OR`, or a parenthesized group when given a callback */
122
+ or(condition: string | WhereClauseCallback, params?: any[]): void;
123
+ /** Returns the assembled WHERE clause string, or `'TRUE'` if no conditions were added */
124
+ readonly query: string;
125
+ /** Returns the parameter array matching the `?` placeholders in {@link query} */
126
+ readonly parameters: any[];
127
+ }
128
+ /**
129
+ * Creates a new WHERE clause builder for constructing parameterized SQL conditions.
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * const builder = WhereClauseBuilder()
134
+ * builder.and('a = ?', [1])
135
+ * builder.and('b IN (?)', [[2, 3]])
136
+ * builder.or(qb => {
137
+ * qb.and('c = ?', [4])
138
+ * qb.or('d = ?', [5])
139
+ * })
140
+ * // a = ? AND b IN (?) OR (c = ? OR d = ?)
141
+ * // params: [1, [2, 3], 4, 5]
142
+ * ```
143
+ */
144
+ declare function WhereClauseBuilder(existing?: WhereClauseBuilder): WhereClauseBuilder;
145
+
146
+ /**
147
+ * Composable SQL query builders for dynamic WHERE clauses and Common Table Expressions.
148
+ * Use `Sql.where()` for parameterized filters and `Sql.cte()` to wrap a Db service with CTEs.
149
+ */
150
+ declare const Sql: {
151
+ cte: typeof CommonTableExpressionBuilder;
152
+ commonTableExpression: typeof CommonTableExpressionBuilder;
153
+ where: typeof WhereClauseBuilder;
154
+ };
155
+ /**
156
+ * Composable SQL query builders for dynamic WHERE clauses and Common Table Expressions.
157
+ *
158
+ * The WHERE builder produces `{ query, parameters }` pairs for safe, incremental filter
159
+ * construction. The CTE builder wraps a Db service so every query method automatically
160
+ * prepends `WITH` expressions and merges parameters.
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * // Dynamic WHERE clause
165
+ * const where = Sql.where()
166
+ * where.and('status = ?', ['active'])
167
+ * where.and('price > ?', [50])
168
+ * await Db.select(`SELECT * FROM products WHERE ${where.query}`, where.parameters)
169
+ *
170
+ * // CTE wrapping a Db service
171
+ * const withTotals = Sql.cte({
172
+ * orderTotals: { subquery: '(SELECT userId, SUM(amount) as total FROM orders GROUP BY userId)', params: [] }
173
+ * }, Db)
174
+ * await withTotals.select('SELECT * FROM orderTotals WHERE total > ?', [500])
175
+ * ```
176
+ */
177
+ declare namespace Sql {
178
+ namespace cte {
179
+ /** Configuration for a single CTE — subquery, parameters, and optional recursive flag */
180
+ type Expression = CommonTableExpressionConfig;
181
+ /** Map of CTE names to their configurations */
182
+ type ExpressionSet = CommonTableExpressionSet;
183
+ /** Db service interface that CTE builders accept and return */
184
+ type DbService = CteDbService;
185
+ }
186
+ /** Alias for {@link cte} — same types under the full name `commonTableExpression` */
187
+ namespace commonTableExpression {
188
+ /** Configuration for a single CTE — subquery, parameters, and optional recursive flag */
189
+ type Expression = CommonTableExpressionConfig;
190
+ /** Map of CTE names to their configurations */
191
+ type ExpressionSet = CommonTableExpressionSet;
192
+ /** Db service interface that CTE builders accept and return */
193
+ type DbService = CteDbService;
194
+ }
195
+ namespace where {
196
+ /** Mutable builder with `and()`, `or()`, `query`, and `parameters` for constructing WHERE clauses */
197
+ type Builder = WhereClauseBuilder;
198
+ /** Callback that receives a sub-builder for creating parenthesized condition groups */
199
+ type Callback = WhereClauseCallback;
200
+ }
201
+ }
202
+
203
+ export { Sql };
package/dist/index.js ADDED
@@ -0,0 +1,115 @@
1
+ // src/cte.ts
2
+ function mergeExpressions(commonTableExpressions) {
3
+ const queries = Object.entries(commonTableExpressions).map(([name, cte]) => {
4
+ const params2 = typeof cte.params === "function" ? cte.params() : cte.params;
5
+ return { statement: `${name} AS ${cte.subquery}`, params: params2 ?? [], recursive: cte.recursive };
6
+ });
7
+ const params = queries.flatMap((q) => q.params);
8
+ const subquery = `WITH ${queries.some((q) => q.recursive) ? "RECURSIVE " : ""}${queries.map((q) => q.statement).join(", ")}`;
9
+ return { params, subquery };
10
+ }
11
+ function CommonTableExpressionBuilder(commonTableExpressions, db) {
12
+ const cte = mergeExpressions(commonTableExpressions);
13
+ function getQueryAndParams(query, params = []) {
14
+ const q = `${cte.subquery} ${query}`;
15
+ const p = [...cte.params ?? [], ...params];
16
+ return [q, p];
17
+ }
18
+ function fromDbService(db2) {
19
+ return {
20
+ transaction: db2.transaction,
21
+ format(query, params) {
22
+ return db2.format(...getQueryAndParams(query, params));
23
+ },
24
+ select(query, params) {
25
+ return db2.select(...getQueryAndParams(query, params));
26
+ },
27
+ selectIterable(query, params) {
28
+ return db2.selectIterable(...getQueryAndParams(query, params));
29
+ },
30
+ insert(query, params) {
31
+ return db2.insert(...getQueryAndParams(query, params));
32
+ },
33
+ update(query, params) {
34
+ return db2.update(...getQueryAndParams(query, params));
35
+ },
36
+ delete(query, params) {
37
+ return db2.delete(...getQueryAndParams(query, params));
38
+ },
39
+ statement(query, params) {
40
+ return db2.statement(...getQueryAndParams(query, params));
41
+ },
42
+ scalar(query, params) {
43
+ return db2.scalar(...getQueryAndParams(query, params));
44
+ }
45
+ };
46
+ }
47
+ const facade = fromDbService(db);
48
+ return {
49
+ fromDbService,
50
+ select: facade.select,
51
+ selectIterable: facade.selectIterable,
52
+ insert: facade.insert,
53
+ update: facade.update,
54
+ delete: facade.delete,
55
+ statement: facade.statement,
56
+ scalar: facade.scalar,
57
+ format: facade.format,
58
+ transaction: facade.transaction
59
+ };
60
+ }
61
+
62
+ // src/where-clause.ts
63
+ function WhereClauseBuilder(existing) {
64
+ let clause = existing?.query === "TRUE" ? void 0 : existing?.query;
65
+ const params = [...existing?.parameters || []];
66
+ function add(glue, condition, newParams = []) {
67
+ let conditionClause;
68
+ let conditionParams;
69
+ if (typeof condition === "function") {
70
+ const subBuilder = WhereClauseBuilder();
71
+ condition(subBuilder);
72
+ const subQuery = subBuilder.query;
73
+ if (subQuery === "TRUE") {
74
+ return;
75
+ }
76
+ conditionClause = `(${subQuery})`;
77
+ conditionParams = subBuilder.parameters;
78
+ } else {
79
+ conditionClause = condition;
80
+ conditionParams = newParams;
81
+ }
82
+ if (!clause) {
83
+ clause = conditionClause;
84
+ } else {
85
+ clause = `${clause} ${glue} ${conditionClause}`;
86
+ }
87
+ params.push(...conditionParams);
88
+ }
89
+ function and(condition, params2 = []) {
90
+ add("AND", condition, params2);
91
+ }
92
+ function or(condition, params2 = []) {
93
+ add("OR", condition, params2);
94
+ }
95
+ return {
96
+ and,
97
+ or,
98
+ get query() {
99
+ return clause || "TRUE";
100
+ },
101
+ get parameters() {
102
+ return params.length > 0 ? params : [];
103
+ }
104
+ };
105
+ }
106
+
107
+ // src/index.ts
108
+ var Sql = {
109
+ cte: CommonTableExpressionBuilder,
110
+ commonTableExpression: CommonTableExpressionBuilder,
111
+ where: WhereClauseBuilder
112
+ };
113
+ export {
114
+ Sql
115
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@maestro-js/sql",
3
+ "description": "Composable SQL query builders for dynamic WHERE clauses and Common Table Expressions (CTEs). Use when working with @maestro-js/sql, building dynamic SQL filters, constructing parameterized WHERE clauses, wrapping Db services with CTEs, or generating safe parameterized SQL fragments. Key capabilities: WhereClauseBuilder for incremental AND/OR conditions with nested sub-groups, CommonTableExpressionBuilder for wrapping a Db service so all queries auto-prepend WITH clauses, cloning builders, recursive CTEs, and dynamic parameter resolution.",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/index.d.ts",
8
+ "default": "./dist/index.js"
9
+ }
10
+ },
11
+ "devDependencies": {
12
+ "@maestro-js/db": "1.0.0-alpha.0"
13
+ },
14
+ "version": "1.0.0-alpha.0",
15
+ "publishConfig": {
16
+ "access": "restricted"
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "license": "UNLICENSED",
22
+ "engines": {
23
+ "node": ">=22.18.0"
24
+ },
25
+ "repository": "https://github.com/Marcato-Partners/maestro-js",
26
+ "scripts": {
27
+ "build": "tsup --config ../../tsup.config.ts",
28
+ "typecheck": "tsc --noEmit",
29
+ "test": "beartest ./tests/**/*",
30
+ "format": "prettier --write src/ tests/",
31
+ "lint": "prettier --check src/ tests/"
32
+ }
33
+ }