@lakeql/query-builder 0.1.4 → 0.2.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/dist/index.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { GraphQLResolveInfo } from "graphql";
2
2
  import { CompiledQuery, ExpressionBuilder, ExpressionWrapper, SelectExpression, SqlBool } from "kysely";
3
-
4
3
  //#region src/index.d.ts
5
4
  /**
6
5
  * Logical operators for combining filter conditions.
@@ -33,13 +32,17 @@ interface FieldOptions {
33
32
  /**
34
33
  * Defines the sort order for a query result.
35
34
  *
36
- * @template TableDefinition - The table type used to constrain the `field` to valid column names.
35
+ * @template TableDefinition - The table type used to constrain the `field` to valid column names. See [Introduction](/docs/query-builder/overview/introduction) for details.
37
36
  */
38
37
  interface SortInput<TableDefinition> {
39
- /** Column name to sort by. Constrained to valid column names from the table type. */
38
+ /**
39
+ * Column name to sort by. Constrained to valid column names from the table type.
40
+ *
41
+ * @see {@link https://kysely-org.github.io/kysely-apidoc/types/SelectExpression.html | Kysely API Docs - SelectExpression}
42
+ */
40
43
  field: SelectExpression<KyselyDatabase<TableDefinition>, keyof KyselyDatabase<TableDefinition>>;
41
- /** Sort direction — `"asc"` for ascending, `"desc"` for descending. */
42
- direction: string;
44
+ /** Sort direction */
45
+ direction: "asc" | "desc";
43
46
  }
44
47
  /**
45
48
  * Controls offset-based pagination.
@@ -73,7 +76,7 @@ interface KyselyDatabase<TableDefinition> {
73
76
  /**
74
77
  * Parameters for generating a Trino-compatible SQL query from GraphQL resolve info.
75
78
  *
76
- * @template TableDefinition - The table type definition used by Kysely for type-safe column references.
79
+ * @template TableDefinition - The table type definition used by Kysely for type-safe column references. See [Introduction](/docs/query-builder/overview/introduction) for details.
77
80
  */
78
81
  interface GenerateQueryProps<TableDefinition> {
79
82
  /** The Trino catalog name (e.g. "hive"). */
@@ -82,7 +85,11 @@ interface GenerateQueryProps<TableDefinition> {
82
85
  schema: string;
83
86
  /** The table name to query. */
84
87
  table: string;
85
- /** The columns to select, derived from GraphQL field selections. */
88
+ /**
89
+ * The columns to select, derived from GraphQL field selections.
90
+ *
91
+ * @see {@link https://kysely-org.github.io/kysely-apidoc/types/SelectExpression.html | Kysely API Docs - SelectExpression}
92
+ */
86
93
  selectFields: SelectExpression<KyselyDatabase<TableDefinition>, keyof KyselyDatabase<TableDefinition>>[];
87
94
  /** The user-provided filter query (WHERE clause). */
88
95
  userQuery: Where;
@@ -97,9 +104,16 @@ interface GenerateQueryProps<TableDefinition> {
97
104
  }
98
105
  /**
99
106
  * Parameters for building a single field comparison expression.
107
+ *
108
+ * @template TableDefinition - The table type definition. See [Introduction](/docs/query-builder/overview/introduction) for details.
100
109
  */
101
110
  interface GetFieldQueryProps<TableDefinition> {
102
- /** The Kysely expression builder for constructing SQL expressions. */
111
+ /**
112
+ * The Kysely expression builder for constructing SQL expressions.
113
+ *
114
+ * @see {@link https://kysely.dev/docs/recipes/expressions | Kysely Docs - Expressions}
115
+ * @see {@link https://kysely-org.github.io/kysely-apidoc/interfaces/ExpressionBuilder.html | Kysely API Docs - ExpressionBuilder}
116
+ */
103
117
  eb: ExpressionBuilder<KyselyDatabase<TableDefinition>, "tablename">;
104
118
  /** The column name to filter on. */
105
119
  fieldName: string;
@@ -112,14 +126,9 @@ interface GetFieldQueryProps<TableDefinition> {
112
126
  * Builds a single field comparison expression for use in WHERE clauses.
113
127
  *
114
128
  * @param props - The field query parameters.
115
- * @returns A Kysely expression wrapper representing the comparison.
129
+ * @returns A Kysely [ExpressionWrapper](https://kysely-org.github.io/kysely-apidoc/classes/ExpressionWrapper.html) representing the comparison.
116
130
  */
117
- declare function getFieldQuery<TableDefinition>({
118
- eb,
119
- fieldName,
120
- operator,
121
- value
122
- }: GetFieldQueryProps<TableDefinition>): ExpressionWrapper<KyselyDatabase<TableDefinition>, "tablename", SqlBool>;
131
+ declare function getFieldQuery<TableDefinition>({ eb, fieldName, operator, value }: GetFieldQueryProps<TableDefinition>): ExpressionWrapper<KyselyDatabase<TableDefinition>, "tablename", SqlBool>;
123
132
  /**
124
133
  * Normalizes filter objects by ensuring each field is in its own object within and/or arrays.
125
134
  *
@@ -143,33 +152,26 @@ declare function normalizeUserQuery(userQuery: Where): Where;
143
152
  /**
144
153
  * Generates a compiled SQL query with pagination metadata for Trino.
145
154
  *
146
- * Returns a Kysely `CompiledQuery` containing the generated SQL and parameters.
147
155
  * The query uses two CTEs internally:
148
156
  * - `total_count` — provides `total_records` (total matching rows before paging)
149
157
  * - `records` — the actual rows based on `selectFields`, filtering, sorting, and paging
150
158
  *
151
159
  * The compiled query is executed by the Trino client which returns the row data.
152
160
  *
153
- * @returns A compiled SQL query ready for execution via the Trino client.
154
- */
155
- declare function generateQuery<TableDefinition>({
156
- catalog,
157
- schema,
158
- table,
159
- selectFields,
160
- userQuery,
161
- sorting,
162
- paging,
163
- transformFields,
164
- dateFields
165
- }: GenerateQueryProps<TableDefinition>): CompiledQuery<{
161
+ * @returns A [CompiledQuery](https://kysely-org.github.io/kysely-apidoc/interfaces/CompiledQuery.html) ready for execution via the Trino client.
162
+ */
163
+ declare function generateQuery<TableDefinition>({ catalog, schema, table, selectFields, userQuery, sorting, paging, transformFields, dateFields }: GenerateQueryProps<TableDefinition>): CompiledQuery<{
166
164
  total_records: number;
167
165
  }>;
168
166
  /**
169
167
  * Parameters for formatting a compiled SQL query.
170
168
  */
171
169
  interface FormatQueryProps<T> {
172
- /** The compiled Kysely query to format. */
170
+ /**
171
+ * The compiled Kysely query to format.
172
+ *
173
+ * @see {@link https://kysely-org.github.io/kysely-apidoc/interfaces/CompiledQuery.html | Kysely API Docs - CompiledQuery}
174
+ */
173
175
  query: CompiledQuery<T>;
174
176
  }
175
177
  /**
@@ -180,16 +182,25 @@ interface FormatQueryProps<T> {
180
182
  * @param props - The format query parameters.
181
183
  * @returns The formatted SQL string with interpolated parameters.
182
184
  */
183
- declare function formatQuery<T>({
184
- query
185
- }: FormatQueryProps<T>): string;
185
+ declare function formatQuery<T>({ query }: FormatQueryProps<T>): string;
186
+ interface GetSelectFieldProps {
187
+ /**
188
+ * The GraphQL resolve info from the resolver.
189
+ *
190
+ * @see {@link https://the-guild.dev/graphql/tools/docs/api/utils/src/interfaces/graphqlresolveinfo | GraphQL Tools - GraphQLResolveInfo}
191
+ */
192
+ graphqlInfo: GraphQLResolveInfo;
193
+ /**
194
+ * When true, looks inside the `nodes` selection (for Connection types).
195
+ * @default false
196
+ */
197
+ withNodes?: boolean;
198
+ }
186
199
  /**
187
200
  * Extracts selected field names from a GraphQL resolve info object.
188
201
  *
189
- * @param graphqlInfo - The GraphQL resolve info from the resolver.
190
- * @param withNodes - When true, looks inside the `nodes` selection (for Connection types).
191
202
  * @returns An array of field names to use as SELECT columns.
192
203
  */
193
- declare function getSelectFields<T>(graphqlInfo: GraphQLResolveInfo, withNodes?: boolean): GenerateQueryProps<T>["selectFields"];
204
+ declare function getSelectFields<T>({ graphqlInfo, withNodes }: GetSelectFieldProps): GenerateQueryProps<T>["selectFields"];
194
205
  //#endregion
195
- export { Field, FieldOptions, FormatQueryProps, GenerateQueryProps, GetFieldQueryProps, PagingInput, SortInput, Where, WhereOperator, formatQuery, generateQuery, getFieldQuery, getSelectFields, normalizeFilter, normalizeUserQuery };
206
+ export { Field, FieldOptions, FormatQueryProps, GenerateQueryProps, GetFieldQueryProps, GetSelectFieldProps, PagingInput, SortInput, Where, WhereOperator, formatQuery, generateQuery, getFieldQuery, getSelectFields, normalizeFilter, normalizeUserQuery };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{GraphQLError as e}from"graphql";import{DummyDriver as t,Kysely as n,PostgresAdapter as r,PostgresIntrospector as i,PostgresQueryCompiler as a,sql as o}from"kysely";import{format as s}from"sql-formatter";const c=()=>new n({dialect:{createAdapter(){return new r},createDriver(){return new t},createIntrospector(e){return new i(e)},createQueryCompiler(){return new a}}});let l=function(e){return e.AND=`and`,e.OR=`or`,e}({});const u={eq:`=`,gt:`>`,gte:`>=`,in:`in`,is:`=`,isnot:`!=`,lt:`<`,lte:`<=`,neq:`!=`,notin:`not in`};function d({eb:e,fieldName:t,operator:n,value:r}){let i=t,a=n.toLowerCase();switch(a){case`eq`:case`neq`:case`lt`:case`lte`:case`gt`:case`gte`:case`is`:case`isnot`:return e(o.id(i),u[a]??`=`,r);case`in`:case`notin`:return e(o.id(i),u[a]??`=`,r);case`like`:return e(o.id(i),`like`,`%${r}%`);case`notlike`:return e.not(e(o.id(i),`like`,`%${r}%`));case`startswith`:return e(o.id(i),`like`,`${r}%`);case`notstartswith`:return e.not(e(o.id(i),`like`,`${r}%`));case`endswith`:return e(o.id(i),`like`,`%${r}`);case`notendswith`:return e.not(e(o.id(i),`like`,`%${r}`));default:return e(o.id(i),`=`,r)}}function f({eb:e,query:t,transformFields:n}){let r=[];for(let[i,a]of Object.entries(t))switch(i.toLowerCase()){case`and`:for(let t of a){let i=f({eb:e,query:t,transformFields:n});i!==void 0&&r.push(i)}return e.and(r);case`or`:for(let t of a){let i=f({eb:e,query:t,transformFields:n});i&&r.push(i)}return e.or(r);default:{let[t,r]=Object.entries(a).flat();return d({eb:e,fieldName:n?.[i]??i,operator:t,value:r})}}}function p(e){if(!e)return e;let t={...e};return t.and&&Array.isArray(t.and)&&(t.and=t.and.flatMap(e=>{let t=e,n=Object.keys(t),r=n.filter(e=>e===`and`||e===`or`),i=n.filter(e=>e!==`and`&&e!==`or`);if(i.length>0&&r.length>0){let e=i.map(e=>({[e]:t[e]})),n=r.map(e=>({[e]:p({[e]:t[e]})[e]}));return[...e,...n]}if(i.length>1&&r.length===0)return i.map(e=>({[e]:t[e]}));if(r.length>0){let e={...t};for(let n of r)e[n]=p({[n]:t[n]})[n];return e}return e})),t.or&&Array.isArray(t.or)&&(t.or=t.or.flatMap(e=>{let t=e,n=Object.keys(t),r=n.filter(e=>e===`and`||e===`or`),i=n.filter(e=>e!==`and`&&e!==`or`);if(i.length>0&&r.length>0){let e=i.map(e=>({[e]:t[e]})),n=r.map(e=>({[e]:p({[e]:t[e]})[e]}));return[...e,...n]}if(i.length>1&&r.length===0)return i.map(e=>({[e]:t[e]}));if(r.length>0){let e={...t};for(let n of r)e[n]=p({[n]:t[n]})[n];return e}return e})),t}function m(e){let t=Object.keys(e);return t.length===1&&(t[0]===`and`||t[0]===`or`)?e:{and:Object.entries(e).map(([e,t])=>({[e]:t}))}}function h({catalog:e,schema:t,table:n,selectFields:r,userQuery:i,sorting:a,paging:s,transformFields:l,dateFields:u=[]}){let d=c(),{ref:h}=d.dynamic;return d.with(`total_count`,r=>r.selectFrom(o.id(e,t,n)).select(({fn:e})=>e.countAll().as(`total_records`)).$if(Object.keys(i).length>0,e=>e.where(e=>f({eb:e,query:p(m(i)),transformFields:l})??e.and([])))).with(`records`,c=>{let d=c.selectFrom(o.id(e,t,n)).select(r.map(e=>u.includes(e)?l?.[e]?o.raw(`to_unixtime(${l[e]})`).as(l[e]):o.raw(`to_unixtime(${e})`).as(e):l?.[e]?o.id(l[e]).as(l[e]):typeof e==`string`?h(e):e)).$if(Object.keys(i).length>0,e=>e.where(e=>f({eb:e,query:p(m(i)),transformFields:l})??e.and([])));for(let e of a){let t=l?.[e.field]||e.field;d=d.orderBy(t,e.direction.toLowerCase())}return d=!s?.offset||s.offset<=0?d.modifyEnd(o.raw(`FETCH FIRST ${s?.limit??100} ROWS ONLY`)):d.offset(s.offset??100).modifyEnd(o.raw(`FETCH NEXT ${s.limit??100} ROWS ONLY`)),d}).selectFrom(`total_count`).selectAll().fullJoin(`records`,e=>e.onTrue()).compile()}function g({query:e}){let t={};for(let[n,r]of e.parameters.entries())t[n+1]=String(typeof r==`string`?`'${r}'`:r);return s(e.sql,{keywordCase:`upper`,language:`postgresql`,params:t,tabWidth:0,useTabs:!1})}function _(t,n=!1){return t.fieldNodes.flatMap(t=>{let r=t.selectionSet?.selections,i=[];if(n){let t=r.find(e=>e.name.value===`nodes`);if(!t)throw new e("No fields to return defined. Please specify at least one field inside `nodes`.");i=t.selectionSet?.selections??[]}else i=t.selectionSet?.selections??[];return i.flatMap(e=>e.name.value)})}export{l as WhereOperator,g as formatQuery,h as generateQuery,d as getFieldQuery,_ as getSelectFields,p as normalizeFilter,m as normalizeUserQuery};
1
+ import{GraphQLError as e}from"graphql";import{DummyDriver as t,Kysely as n,PostgresAdapter as r,PostgresIntrospector as i,PostgresQueryCompiler as a,sql as o}from"kysely";import{format as s}from"sql-formatter";const c=()=>new n({dialect:{createAdapter(){return new r},createDriver(){return new t},createIntrospector(e){return new i(e)},createQueryCompiler(){return new a}}});let l=function(e){return e.AND=`and`,e.OR=`or`,e}({});const u={eq:`=`,gt:`>`,gte:`>=`,in:`in`,is:`=`,isnot:`!=`,lt:`<`,lte:`<=`,neq:`!=`,notin:`not in`};function d({eb:e,fieldName:t,operator:n,value:r}){let i=t,a=n.toLowerCase();switch(a){case`eq`:case`neq`:case`lt`:case`lte`:case`gt`:case`gte`:case`is`:case`isnot`:return e(o.id(i),u[a]??`=`,r);case`in`:case`notin`:return e(o.id(i),u[a]??`=`,r);case`like`:return e(o.id(i),`like`,`%${r}%`);case`notlike`:return e.not(e(o.id(i),`like`,`%${r}%`));case`startswith`:return e(o.id(i),`like`,`${r}%`);case`notstartswith`:return e.not(e(o.id(i),`like`,`${r}%`));case`endswith`:return e(o.id(i),`like`,`%${r}`);case`notendswith`:return e.not(e(o.id(i),`like`,`%${r}`));default:return e(o.id(i),`=`,r)}}function f({eb:e,query:t,transformFields:n}){let r=[];for(let[i,a]of Object.entries(t))switch(i.toLowerCase()){case`and`:for(let t of a){let i=f({eb:e,query:t,transformFields:n});i!==void 0&&r.push(i)}return e.and(r);case`or`:for(let t of a){let i=f({eb:e,query:t,transformFields:n});i&&r.push(i)}return e.or(r);default:{let[t,r]=Object.entries(a).flat();return d({eb:e,fieldName:n?.[i]??i,operator:t,value:r})}}}function p(e){if(!e)return e;let t={...e};return t.and&&Array.isArray(t.and)&&(t.and=t.and.flatMap(e=>{let t=e,n=Object.keys(t),r=n.filter(e=>e===`and`||e===`or`),i=n.filter(e=>e!==`and`&&e!==`or`);if(i.length>0&&r.length>0){let e=i.map(e=>({[e]:t[e]})),n=r.map(e=>({[e]:p({[e]:t[e]})[e]}));return[...e,...n]}if(i.length>1&&r.length===0)return i.map(e=>({[e]:t[e]}));if(r.length>0){let e={...t};for(let n of r)e[n]=p({[n]:t[n]})[n];return e}return e})),t.or&&Array.isArray(t.or)&&(t.or=t.or.flatMap(e=>{let t=e,n=Object.keys(t),r=n.filter(e=>e===`and`||e===`or`),i=n.filter(e=>e!==`and`&&e!==`or`);if(i.length>0&&r.length>0){let e=i.map(e=>({[e]:t[e]})),n=r.map(e=>({[e]:p({[e]:t[e]})[e]}));return[...e,...n]}if(i.length>1&&r.length===0)return i.map(e=>({[e]:t[e]}));if(r.length>0){let e={...t};for(let n of r)e[n]=p({[n]:t[n]})[n];return e}return e})),t}function m(e){let t=Object.keys(e);return t.length===1&&(t[0]===`and`||t[0]===`or`)?e:{and:Object.entries(e).map(([e,t])=>({[e]:t}))}}function h({catalog:e,schema:t,table:n,selectFields:r,userQuery:i,sorting:a,paging:s,transformFields:l,dateFields:u=[]}){let d=c(),{ref:h}=d.dynamic;return d.with(`total_count`,r=>r.selectFrom(o.id(e,t,n)).select(({fn:e})=>e.countAll().as(`total_records`)).$if(Object.keys(i).length>0,e=>e.where(e=>f({eb:e,query:p(m(i)),transformFields:l})??e.and([])))).with(`records`,c=>{let d=c.selectFrom(o.id(e,t,n)).select(r.map(e=>u.includes(e)?l?.[e]?o.raw(`to_unixtime(${l[e]})`).as(l[e]):o.raw(`to_unixtime(${e})`).as(e):l?.[e]?o.id(l[e]).as(l[e]):typeof e==`string`?h(e):e)).$if(Object.keys(i).length>0,e=>e.where(e=>f({eb:e,query:p(m(i)),transformFields:l})??e.and([])));for(let e of a){let t=l?.[e.field]||e.field;d=d.orderBy(t,e.direction.toLowerCase())}return d=!s?.offset||s.offset<=0?d.modifyEnd(o.raw(`FETCH FIRST ${s?.limit??100} ROWS ONLY`)):d.offset(s.offset??100).modifyEnd(o.raw(`FETCH NEXT ${s.limit??100} ROWS ONLY`)),d}).selectFrom(`total_count`).selectAll().fullJoin(`records`,e=>e.onTrue()).compile()}function g({query:e}){let t={};for(let[n,r]of e.parameters.entries())t[n+1]=String(typeof r==`string`?`'${r}'`:r);return s(e.sql,{keywordCase:`upper`,language:`postgresql`,params:t,tabWidth:0,useTabs:!1})}function _({graphqlInfo:t,withNodes:n=!1}){return t.fieldNodes.flatMap(t=>{let r=t.selectionSet?.selections,i=[];if(n){let t=r.find(e=>e.name.value===`nodes`);if(!t)throw new e("No fields to return defined. Please specify at least one field inside `nodes`.");i=t.selectionSet?.selections??[]}else i=t.selectionSet?.selections??[];return i.flatMap(e=>e.name.value)})}export{l as WhereOperator,g as formatQuery,h as generateQuery,d as getFieldQuery,_ as getSelectFields,p as normalizeFilter,m as normalizeUserQuery};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lakeql/query-builder",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Kysely-based SQL query builder that translates GraphQL queries into Trino SQL",
5
5
  "keywords": [
6
6
  "graphql",
@@ -41,16 +41,16 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "date-fns": "4.4.0",
44
- "kysely": "0.29.2",
45
- "sql-formatter": "15.8.1"
44
+ "kysely": "0.29.4",
45
+ "sql-formatter": "15.8.2"
46
46
  },
47
47
  "devDependencies": {
48
- "@pothos/core": "4.12.0",
49
- "@types/node": "24.13.2",
48
+ "@pothos/core": "4.13.1",
49
+ "@types/node": "24.13.3",
50
50
  "graphql": "16.14.2",
51
- "tsdown": "0.22.3",
51
+ "tsdown": "0.22.13",
52
52
  "typescript": "6.0.3",
53
- "vitest": "4.1.9",
53
+ "vitest": "4.1.10",
54
54
  "@lakeql/tsconfig": "0.1.0"
55
55
  },
56
56
  "scripts": {