@cleverbrush/knex-schema 3.0.0 → 3.1.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.
@@ -101,6 +101,19 @@ export declare const dbExtension: import("@cleverbrush/schema").ExtensionDescrip
101
101
  }>;
102
102
  export declare const string: {
103
103
  (): import("@cleverbrush/schema").CleanExtended<StringSchemaBuilder<string, true, false, false, {
104
+ email(this: StringSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
105
+ url(this: StringSchemaBuilder, optsOrError?: {
106
+ protocols?: string[];
107
+ } | import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
108
+ uuid(this: StringSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
109
+ ip(this: StringSchemaBuilder, opts?: {
110
+ version?: "v4" | "v6";
111
+ }, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
112
+ trim(this: StringSchemaBuilder): StringSchemaBuilder<string, true, false, false, {}>;
113
+ toLowerCase(this: StringSchemaBuilder): StringSchemaBuilder<string, true, false, false, {}>;
114
+ nonempty(this: StringSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
115
+ oneOf(this: StringSchemaBuilder, ...args: any[]): StringSchemaBuilder<string, true, false, false, {}>;
116
+ } & {
104
117
  /**
105
118
  * Override the SQL column name for this property.
106
119
  *
@@ -112,6 +125,19 @@ export declare const string: {
112
125
  */
113
126
  hasColumnName(this: StringSchemaBuilder<any, any, any, any, any>, name: string): SchemaBuilder<any, any, any, false, {}>;
114
127
  }>, {
128
+ email(this: StringSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
129
+ url(this: StringSchemaBuilder, optsOrError?: {
130
+ protocols?: string[];
131
+ } | import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
132
+ uuid(this: StringSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
133
+ ip(this: StringSchemaBuilder, opts?: {
134
+ version?: "v4" | "v6";
135
+ }, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
136
+ trim(this: StringSchemaBuilder): StringSchemaBuilder<string, true, false, false, {}>;
137
+ toLowerCase(this: StringSchemaBuilder): StringSchemaBuilder<string, true, false, false, {}>;
138
+ nonempty(this: StringSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
139
+ oneOf(this: StringSchemaBuilder, ...args: any[]): StringSchemaBuilder<string, true, false, false, {}>;
140
+ } & {
115
141
  /**
116
142
  * Override the SQL column name for this property.
117
143
  *
@@ -124,6 +150,19 @@ export declare const string: {
124
150
  hasColumnName(this: StringSchemaBuilder<any, any, any, any, any>, name: string): SchemaBuilder<any, any, any, false, {}>;
125
151
  }>;
126
152
  <T extends string>(equals: T): import("@cleverbrush/schema").CleanExtended<StringSchemaBuilder<T, true, false, false, {
153
+ email(this: StringSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
154
+ url(this: StringSchemaBuilder, optsOrError?: {
155
+ protocols?: string[];
156
+ } | import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
157
+ uuid(this: StringSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
158
+ ip(this: StringSchemaBuilder, opts?: {
159
+ version?: "v4" | "v6";
160
+ }, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
161
+ trim(this: StringSchemaBuilder): StringSchemaBuilder<string, true, false, false, {}>;
162
+ toLowerCase(this: StringSchemaBuilder): StringSchemaBuilder<string, true, false, false, {}>;
163
+ nonempty(this: StringSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
164
+ oneOf(this: StringSchemaBuilder, ...args: any[]): StringSchemaBuilder<string, true, false, false, {}>;
165
+ } & {
127
166
  /**
128
167
  * Override the SQL column name for this property.
129
168
  *
@@ -135,6 +174,19 @@ export declare const string: {
135
174
  */
136
175
  hasColumnName(this: StringSchemaBuilder<any, any, any, any, any>, name: string): SchemaBuilder<any, any, any, false, {}>;
137
176
  }>, {
177
+ email(this: StringSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
178
+ url(this: StringSchemaBuilder, optsOrError?: {
179
+ protocols?: string[];
180
+ } | import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
181
+ uuid(this: StringSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
182
+ ip(this: StringSchemaBuilder, opts?: {
183
+ version?: "v4" | "v6";
184
+ }, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
185
+ trim(this: StringSchemaBuilder): StringSchemaBuilder<string, true, false, false, {}>;
186
+ toLowerCase(this: StringSchemaBuilder): StringSchemaBuilder<string, true, false, false, {}>;
187
+ nonempty(this: StringSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<StringSchemaBuilder>): StringSchemaBuilder<string, true, false, false, {}>;
188
+ oneOf(this: StringSchemaBuilder, ...args: any[]): StringSchemaBuilder<string, true, false, false, {}>;
189
+ } & {
138
190
  /**
139
191
  * Override the SQL column name for this property.
140
192
  *
@@ -149,12 +201,24 @@ export declare const string: {
149
201
  };
150
202
  export declare const number: {
151
203
  (): import("@cleverbrush/schema").CleanExtended<NumberSchemaBuilder<number, true, false, false, {
204
+ positive(this: NumberSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
205
+ negative(this: NumberSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
206
+ finite(this: NumberSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
207
+ multipleOf(this: NumberSchemaBuilder, n: number, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
208
+ oneOf(this: NumberSchemaBuilder, ...args: any[]): NumberSchemaBuilder<number, true, false, false, {}>;
209
+ } & {
152
210
  /**
153
211
  * Override the SQL column name for this property.
154
212
  * @param name - The SQL column name.
155
213
  */
156
214
  hasColumnName(this: NumberSchemaBuilder<any, any, any, any, any>, name: string): SchemaBuilder<any, any, any, false, {}>;
157
215
  }>, {
216
+ positive(this: NumberSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
217
+ negative(this: NumberSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
218
+ finite(this: NumberSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
219
+ multipleOf(this: NumberSchemaBuilder, n: number, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
220
+ oneOf(this: NumberSchemaBuilder, ...args: any[]): NumberSchemaBuilder<number, true, false, false, {}>;
221
+ } & {
158
222
  /**
159
223
  * Override the SQL column name for this property.
160
224
  * @param name - The SQL column name.
@@ -162,12 +226,24 @@ export declare const number: {
162
226
  hasColumnName(this: NumberSchemaBuilder<any, any, any, any, any>, name: string): SchemaBuilder<any, any, any, false, {}>;
163
227
  }>;
164
228
  <T extends number>(equals: T): import("@cleverbrush/schema").CleanExtended<NumberSchemaBuilder<T, true, false, false, {
229
+ positive(this: NumberSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
230
+ negative(this: NumberSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
231
+ finite(this: NumberSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
232
+ multipleOf(this: NumberSchemaBuilder, n: number, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
233
+ oneOf(this: NumberSchemaBuilder, ...args: any[]): NumberSchemaBuilder<number, true, false, false, {}>;
234
+ } & {
165
235
  /**
166
236
  * Override the SQL column name for this property.
167
237
  * @param name - The SQL column name.
168
238
  */
169
239
  hasColumnName(this: NumberSchemaBuilder<any, any, any, any, any>, name: string): SchemaBuilder<any, any, any, false, {}>;
170
240
  }>, {
241
+ positive(this: NumberSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
242
+ negative(this: NumberSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
243
+ finite(this: NumberSchemaBuilder, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
244
+ multipleOf(this: NumberSchemaBuilder, n: number, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<NumberSchemaBuilder>): NumberSchemaBuilder<number, true, false, false, {}>;
245
+ oneOf(this: NumberSchemaBuilder, ...args: any[]): NumberSchemaBuilder<number, true, false, false, {}>;
246
+ } & {
171
247
  /**
172
248
  * Override the SQL column name for this property.
173
249
  * @param name - The SQL column name.
@@ -223,12 +299,18 @@ export declare const object: <P extends Record<string, SchemaBuilder<any, any, a
223
299
  hasTableName(this: ObjectSchemaBuilder<any, any, any, any, any, any, any>, name: string): ObjectSchemaBuilder<any, any, any, any, any, any, any>;
224
300
  }>;
225
301
  export declare const array: <TElementSchema extends SchemaBuilder<any, any, any, any, any>>(elementSchema?: TElementSchema | undefined) => import("@cleverbrush/schema").CleanExtended<ArraySchemaBuilder<TElementSchema, true, false, undefined, false, {
302
+ nonempty(this: ArraySchemaBuilder<any>, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<ArraySchemaBuilder<any>>): ArraySchemaBuilder<any, true, false, undefined, false, {}, any[] | unknown[]>;
303
+ unique(this: ArraySchemaBuilder<any>, keyFn?: (item: any) => unknown, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<ArraySchemaBuilder<any>>): ArraySchemaBuilder<any, true, false, undefined, false, {}, any[] | unknown[]>;
304
+ } & {
226
305
  /**
227
306
  * Override the SQL column name for this property.
228
307
  * @param name - The SQL column name.
229
308
  */
230
309
  hasColumnName(this: ArraySchemaBuilder<any, any, any, any, any, any, any>, name: string): SchemaBuilder<any, any, any, false, {}>;
231
310
  }, TElementSchema extends undefined ? any[] : TElementSchema extends SchemaBuilder<infer T1, infer T2 extends boolean, false, false, {}> ? (T2 extends true ? T1 : T1 | undefined)[] : never>, {
311
+ nonempty(this: ArraySchemaBuilder<any>, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<ArraySchemaBuilder<any>>): ArraySchemaBuilder<any, true, false, undefined, false, {}, any[] | unknown[]>;
312
+ unique(this: ArraySchemaBuilder<any>, keyFn?: (item: any) => unknown, errorMessage?: import("@cleverbrush/schema").ValidationErrorMessageProvider<ArraySchemaBuilder<any>>): ArraySchemaBuilder<any, true, false, undefined, false, {}, any[] | unknown[]>;
313
+ } & {
232
314
  /**
233
315
  * Override the SQL column name for this property.
234
316
  * @param name - The SQL column name.
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{ObjectSchemaBuilder as K,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as f}from"@cleverbrush/schema";import{defineExtension as J,withExtensions as O}from"@cleverbrush/schema";function y(r){return this.withExtension("columnName",r)}function F(r){return this.withExtension("tableName",r)}var L=J({string:{hasColumnName(r){return y.call(this,r)}},number:{hasColumnName(r){return y.call(this,r)}},boolean:{hasColumnName(r){return y.call(this,r)}},date:{hasColumnName(r){return y.call(this,r)}},any:{hasColumnName(r){return y.call(this,r)}},func:{hasColumnName(r){return y.call(this,r)}},array:{hasColumnName(r){return y.call(this,r)}},union:{hasColumnName(r){return y.call(this,r)}},generic:{hasColumnName(r){return y.call(this,r)}},object:{hasTableName(r){return F.call(this,r)}}}),h=O(L),q=h.string,$=h.number,A=h.boolean,V=h.date,D=h.object,U=h.array,Y=h.union,G=h.func,H=h.any;function w(r,e){let n=r.getExtension("columnName");return typeof n=="string"?n:e}function g(r){let e=r.getExtension("tableName");if(typeof e!="string")throw new Error('Schema does not have a table name. Use .hasTableName("table_name") to set one.');return e}var N=new WeakMap;function d(r){let e=N.get(r);if(e)return e;let t=r.introspect().properties??{},a=new Map,o=new Map;for(let s of Object.keys(t)){let l=t[s],u=w(l,s);a.set(s,u),o.set(u,s)}let i={propToCol:a,colToProp:o};return N.set(r,i),i}function m(r,e,n){if(typeof r=="string"){if(!r)throw new Error(`${n} must be a non-empty string`);let{propToCol:t}=d(e);return t.get(r)??r}if(typeof r=="function"){let t=K.getPropertiesFor(e),a=r(t);if(!a||typeof a!="object"||!(f in a))throw new Error(`${n} accessor must return a valid property descriptor`);let o=a[f],s=e.introspect().properties??{};for(let l of Object.keys(s)){let u=t[l];if(u&&u[f]===o){let{propToCol:c}=d(e);return c.get(l)??l}}throw new Error(`${n} accessor did not match any property in the schema`)}throw new Error(`${n} must be a string or a property descriptor accessor function`)}function z(r,e,n){if(typeof r=="string"){if(!r)throw new Error(`${n} must be a non-empty string`);return r}if(typeof r=="function"){let t=K.getPropertiesFor(e),a=r(t);if(!a||typeof a!="object"||!(f in a))throw new Error(`${n} accessor must return a valid property descriptor`);let o=a[f],s=e.introspect().properties??{};for(let l of Object.keys(s)){let u=t[l];if(u&&u[f]===o)return l}throw new Error(`${n} accessor did not match any property in the schema`)}throw new Error(`${n} must be a string or a property descriptor accessor function`)}var p={date_from_json:r=>r&&new Date(Date.parse(r))};function v(r,e){if(typeof r=="string"){if(typeof p[r]!="function")throw new Error(`unknown mapper "${r}"`);return p[r](e)}if(typeof r=="function")return r(e);throw new Error("couldn't map value")}function b(r,e){if(!e)throw new Error("mappers should be an object");if(!r||typeof r!="object"||r===null)return r;let n={};for(let t of Object.keys(r))n[t]=e[t]?v(e[t],r[t]):r[t];return n}function x(r,e,n){for(let t of e){let a=t.as;t.mappers&&typeof r[a]<"u"&&r[a]!==null&&(r[a]=b(r[a],t.mappers))}for(let t of n){let a=t.as;if(Array.isArray(r[a]))for(let o=0;o<r[a].length;o++)r[a][o]&&t.mappers&&(r[a][o]=b(r[a][o],t.mappers))}return r}import{ObjectSchemaBuilder as X,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as Z}from"@cleverbrush/schema";function j(r,e){if(r.foreignQuery)return"toKnexQuery"in r.foreignQuery?r.foreignQuery.toKnexQuery():r.foreignQuery;let n=g(r.foreignSchema);return e(n)}function _(r,e,n){let t=m(r.localColumn,e,"localColumn"),a=m(r.foreignColumn,r.foreignSchema,"foreignColumn");if(typeof r.as!="string"||!r.as)throw new Error("as must be a non-empty string");let o=j(r,n),i=r.required!==!1;return r.mappers!==void 0&&E(r.mappers),{localColumn:t,foreignColumn:a,as:r.as,required:i,foreignQuery:o,mappers:r.mappers}}function M(r,e,n){let t=m(r.localColumn,e,"localColumn"),a=m(r.foreignColumn,r.foreignSchema,"foreignColumn");if(typeof r.as!="string"||!r.as)throw new Error("as must be a non-empty string");let o=j(r,n);r.mappers!==void 0&&E(r.mappers);let i=Number.isFinite(r.limit)&&r.limit>0?r.limit:null,s=Number.isFinite(r.offset)&&r.offset>0?r.offset:null,l=r.orderBy?{column:m(r.orderBy.column,r.foreignSchema,"orderBy.column"),direction:r.orderBy.direction??"asc"}:null;return{localColumn:t,foreignColumn:a,as:r.as,foreignQuery:o,limit:i,offset:s,orderBy:l,mappers:r.mappers}}function E(r){if(typeof r!="object"||r===null)throw new Error("mappers must be an object");for(let e of Object.keys(r)){let n=r[e];if(typeof n=="string"){if(typeof p[n]!="function")throw new Error(`mapper for "${e}": unknown built-in mapper name "${n}"`)}else if(typeof n!="function")throw new Error(`mapper for "${e}" must be a function or a built-in mapper name`)}}function C(r){let e=new Set;for(let n of r){if(e.has(n.as))throw new Error(`duplicate field name: ${n.as}`);e.add(n.as)}}var T=class r{#o;#e;#i;#t=[];#l;#a=null;constructor(e,n,t){this.#o=e,this.#i=n,this.#l=g(n),this.#e=t??e(this.#l)}#r(e,n="column"){return m(e,this.#i,n)}joinOne(e){let n=_(e,this.#i,this.#o);return this.#t.push({type:"one",...n}),C(this.#t),this}joinMany(e){let n=M(e,this.#i,this.#o);return this.#t.push({type:"many",...n}),C(this.#t),this}where(e,...n){if(typeof e=="function"&&!this.#h(e))this.#e.where(e,...n);else if(typeof e=="object"&&e!==null&&!("toSQL"in e)){let t=this.#y(e);this.#e.where(t,...n)}else{let t=this.#n(e);this.#e.where(t,...n)}return this}andWhere(e,...n){if(typeof e=="function"&&!this.#h(e))this.#e.andWhere(e,...n);else if(typeof e=="object"&&e!==null&&!("toSQL"in e)){let t=this.#y(e);this.#e.andWhere(t,...n)}else{let t=this.#n(e);this.#e.andWhere(t,...n)}return this}orWhere(e,...n){if(typeof e=="function"&&!this.#h(e))this.#e.orWhere(e,...n);else if(typeof e=="object"&&e!==null&&!("toSQL"in e)){let t=this.#y(e);this.#e.orWhere(t,...n)}else{let t=this.#n(e);this.#e.orWhere(t,...n)}return this}whereNot(e,...n){if(typeof e=="function"&&!this.#h(e))this.#e.whereNot(e,...n);else if(typeof e=="object"&&e!==null&&!("toSQL"in e)){let t=this.#y(e);this.#e.whereNot(t,...n)}else{let t=this.#n(e);this.#e.whereNot(t,...n)}return this}whereIn(e,n){return this.#e.whereIn(this.#r(e,"whereIn"),n),this}whereNotIn(e,n){return this.#e.whereNotIn(this.#r(e,"whereNotIn"),n),this}orWhereIn(e,n){return this.#e.orWhereIn(this.#r(e,"orWhereIn"),n),this}orWhereNotIn(e,n){return this.#e.orWhereNotIn(this.#r(e,"orWhereNotIn"),n),this}whereNull(e){return this.#e.whereNull(this.#r(e,"whereNull")),this}whereNotNull(e){return this.#e.whereNotNull(this.#r(e,"whereNotNull")),this}orWhereNull(e){return this.#e.orWhereNull(this.#r(e,"orWhereNull")),this}orWhereNotNull(e){return this.#e.orWhereNotNull(this.#r(e,"orWhereNotNull")),this}whereBetween(e,n){return this.#e.whereBetween(this.#r(e,"whereBetween"),n),this}whereNotBetween(e,n){return this.#e.whereNotBetween(this.#r(e,"whereNotBetween"),n),this}whereLike(e,n){return this.#e.whereLike(this.#r(e,"whereLike"),n),this}whereILike(e,n){return this.#e.whereILike(this.#r(e,"whereILike"),n),this}whereRaw(e,...n){return this.#e.whereRaw(e,...n),this}whereExists(e){return this.#e.whereExists(e),this}orderBy(e,n){let t=this.#n(e);return this.#e.orderBy(t,n),this}orderByRaw(e,...n){return this.#e.orderByRaw(e,...n),this}groupBy(...e){let n=e.map(t=>this.#n(t));return this.#e.groupBy(...n),this}groupByRaw(e,...n){return this.#e.groupByRaw(e,...n),this}having(e,n,t){let a=this.#n(e);return this.#e.having(a,n,t),this}havingRaw(e,...n){return this.#e.havingRaw(e,...n),this}limit(e){return this.#e.limit(e),this}offset(e){return this.#e.offset(e),this}select(...e){let n=e.map(t=>this.#n(t));this.#e.select(...n),this.#a??=[];for(let t of n)typeof t=="string"&&this.#a.push(t);return this}distinct(...e){let n=e.map(t=>this.#n(t));return this.#e.distinct(...n),this}count(e){return e?this.#e.count(this.#n(e)):this.#e.count(),this}countDistinct(e){return e?this.#e.countDistinct(this.#n(e)):this.#e.countDistinct(),this}min(e){return this.#e.min(this.#n(e)),this}max(e){return this.#e.max(this.#n(e)),this}sum(e){return this.#e.sum(this.#n(e)),this}avg(e){return this.#e.avg(this.#n(e)),this}async insert(e){let n=this.#c(e),[t]=await this.#o(this.#l).insert(n).returning("*");return this.#u(t)}async insertMany(e){let n=e.map(a=>this.#c(a));return(await this.#o(this.#l).insert(n).returning("*")).map(a=>this.#u(a))}async update(e){let n=this.#c(e);return(await this.#e.update(n).returning("*")).map(a=>this.#u(a))}async delete(){return this.#e.delete()}apply(e){return e(this.#e),this}transacting(e){let n=new r(e,this.#i,this.#e.clone().transacting(e));for(let t of this.#t)n.#t.push({...t,foreignQuery:t.foreignQuery.clone().transacting(e)});return n.#a=this.#a?[...this.#a]:null,n}#s(){if(this.#t.length===0)return this.#e;let e=this.#o,n=this.#t,t=[...new Set(n.map(s=>s.localColumn))],a=this.#e,o=[];if(this.#a!==null){let s=new Set(this.#a);if(o=t.filter(l=>!s.has(l)),o.length>0){a=this.#e.clone();for(let l of o)a.column(l)}}let i=e.queryBuilder().with("originalQuery",a);if(o.length>0&&this.#a!==null)for(let s of this.#a)i.select(e.raw(":originalQuery:.:col: as :col:",{originalQuery:"originalQuery",col:s}));else i.select("originalQuery.*");i.from(e.raw(":originalQuery:",{originalQuery:"originalQuery"}));for(let s=0;s<n.length;s++){let l=n[s],u=`eagerRelation${s}`;l.type==="one"?this.#f(i,l,u):this.#d(i,l,u,s)}return i}#f(e,n,t){let a=this.#o,o=n.foreignQuery,i=o._single?.table;if(!i)throw new Error(`Could not determine table name from foreignQuery for "${n.as}". Make sure foreignQuery is created via knex("tableName").`);e.select(a.raw(":relationAlias:.:as:->0 as :as:",{relationAlias:t,as:n.as}));let s=a.from(o.as(i)).select(a.raw(":foreignTable:.:foreignColumn:",{foreignTable:i,foreignColumn:n.foreignColumn})).select(a.raw("jsonb_agg(:foreignTable:) as :as:",{foreignTable:i,as:n.as})).groupByRaw(":foreignTable:.:foreignColumn:",{foreignTable:i,foreignColumn:n.foreignColumn}).as(t),l=n.required?"join":"leftJoin";e[l](s,function(){this.on(a.raw(":relationAlias:.:foreignColumn: = :originalQuery:.:localColumn:",{originalQuery:"originalQuery",relationAlias:t,foreignColumn:n.foreignColumn,localColumn:n.localColumn}))})}#d(e,n,t,a){let o=this.#o,i=`withFilter${a}`,s=n.limit!==null&&n.limit>0||n.offset!==null&&n.offset>0,l=n.orderBy?n.orderBy.column:n.foreignColumn,u=n.orderBy?n.orderBy.direction:"asc";s?e.with(i,o.from(n.foreignQuery.clone().whereIn(n.foreignColumn,o.from(o.raw(":originalQuery:",{originalQuery:"originalQuery"})).distinct(n.localColumn)).as(`__wf_inner_${a}`)).select(`__wf_inner_${a}.*`).select(o.raw(`row_number() over(partition by :foreignColumn: order by :orderByColumn: ${u}) as "__rn__"`,{foreignColumn:n.foreignColumn,orderByColumn:l}))):e.with(i,n.foreignQuery.clone().whereIn(n.foreignColumn,o.from(o.raw(":originalQuery:",{originalQuery:"originalQuery"})).distinct(n.localColumn)));let c=o.from(i);if(s){let S=n.limit!==null&&n.limit>0,W=n.offset!==null&&n.offset>0,Q=n.offset??0,P=Q+(n.limit??0),I=S&&W?'"__rn__" > :offset and "__rn__" <= :limit':S?'"__rn__" <= :limit':'"__rn__" > :offset';c.whereRaw(I,{limit:P,offset:Q}),c.select(o.raw(":foreignColumn:",{foreignColumn:n.foreignColumn})),c.select(o.raw(`coalesce(jsonb_agg(to_jsonb(:filterName:) - '__rn__' order by "__rn__"), '[]'::jsonb) as :as:`,{filterName:i,as:n.as}))}else{c.select(o.raw(":foreignColumn:",{foreignColumn:n.foreignColumn}));let S=n.orderBy?`jsonb_agg(:filterName: order by :filterName:.:orderByColumn: ${u})`:"jsonb_agg(:filterName:)";c.select(o.raw(`coalesce(${S}, '[]'::jsonb) as :as:`,n.orderBy?{filterName:i,orderByColumn:l,as:n.as}:{filterName:i,as:n.as}))}c.groupByRaw(":foreignColumn:",{foreignColumn:n.foreignColumn});let k=c.as(t);e.select(o.raw("coalesce(:relationAlias:.:as:, '[]'::jsonb) as :as:",{relationAlias:t,as:n.as})),e.leftJoin(k,function(){this.on(o.raw(":relationAlias:.:foreignColumn: = :originalQuery:.:localColumn:",{relationAlias:t,foreignColumn:n.foreignColumn,originalQuery:"originalQuery",localColumn:n.localColumn}))})}#u(e){if(!e)return e;let{colToProp:n}=d(this.#i),t={};for(let[a,o]of Object.entries(e)){let i=n.get(a);i?t[i]=o:t[a]=o}return t}#m(e){let n=this.#t.filter(o=>o.type==="one"),t=this.#t.filter(o=>o.type==="many"),a=x(e,n,t);return this.#u(a)}#c(e){let{propToCol:n}=d(this.#i),t={};for(let[a,o]of Object.entries(e)){let i=n.get(a);i?t[i]=o:t[a]=o}return t}#y(e){return this.#c(e)}#n(e){return typeof e=="string"?this.#r(e,"column"):typeof e=="function"?this.#r(e,"column"):e}#h(e){try{let n=X.getPropertiesFor(this.#i),t=e(n);if(t&&typeof t=="object"&&Z in t)return!0}catch{}return!1}toQuery(){return this.#s().toQuery()}toKnexQuery(){return this.#s()}toString(){return this.#s().toString()}async execute(){let n=await this.#s();return n?Array.isArray(n)?n.map(t=>this.#m(t)):[this.#m(n)]:[]}async first(){let n=await this.#s().first();if(n)return this.#m(n)}then(e,n){return this.execute().then(e,n)}};function R(r,e,n){return new T(r,e,n)}function B(r){function e(n,t){return t?R(r,n,t):R(r,n)}return e.withTransaction=n=>B(n),e.transaction=n=>r.transaction(t=>n(B(t))),e}export{p as MAPPERS,T as SchemaQueryBuilder,H as any,U as array,A as boolean,d as buildColumnMap,x as clearRow,B as createQuery,V as date,L as dbExtension,G as func,w as getColumnName,g as getTableName,b as mapObject,v as mapValue,$ as number,D as object,R as query,m as resolveColumnRef,z as resolvePropertyKey,q as string,Y as union};
1
+ import{ObjectSchemaBuilder as K,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as f}from"@cleverbrush/schema";import{arrayExtensions as J,defineExtension as O,numberExtensions as F,stringExtensions as q,withExtensions as $}from"@cleverbrush/schema";function y(r){return this.withExtension("columnName",r)}function A(r){return this.withExtension("tableName",r)}var L=O({string:{hasColumnName(r){return y.call(this,r)}},number:{hasColumnName(r){return y.call(this,r)}},boolean:{hasColumnName(r){return y.call(this,r)}},date:{hasColumnName(r){return y.call(this,r)}},any:{hasColumnName(r){return y.call(this,r)}},func:{hasColumnName(r){return y.call(this,r)}},array:{hasColumnName(r){return y.call(this,r)}},union:{hasColumnName(r){return y.call(this,r)}},generic:{hasColumnName(r){return y.call(this,r)}},object:{hasTableName(r){return A.call(this,r)}}}),h=$(q,F,J,L),V=h.string,D=h.number,U=h.boolean,Y=h.date,G=h.object,H=h.array,z=h.union,X=h.func,Z=h.any;function w(r,e){let n=r.getExtension("columnName");return typeof n=="string"?n:e}function g(r){let e=r.getExtension("tableName");if(typeof e!="string")throw new Error('Schema does not have a table name. Use .hasTableName("table_name") to set one.');return e}var N=new WeakMap;function d(r){let e=N.get(r);if(e)return e;let t=r.introspect().properties??{},a=new Map,o=new Map;for(let s of Object.keys(t)){let l=t[s],u=w(l,s);a.set(s,u),o.set(u,s)}let i={propToCol:a,colToProp:o};return N.set(r,i),i}function m(r,e,n){if(typeof r=="string"){if(!r)throw new Error(`${n} must be a non-empty string`);let{propToCol:t}=d(e);return t.get(r)??r}if(typeof r=="function"){let t=K.getPropertiesFor(e),a=r(t);if(!a||typeof a!="object"||!(f in a))throw new Error(`${n} accessor must return a valid property descriptor`);let o=a[f],s=e.introspect().properties??{};for(let l of Object.keys(s)){let u=t[l];if(u&&u[f]===o){let{propToCol:c}=d(e);return c.get(l)??l}}throw new Error(`${n} accessor did not match any property in the schema`)}throw new Error(`${n} must be a string or a property descriptor accessor function`)}function ee(r,e,n){if(typeof r=="string"){if(!r)throw new Error(`${n} must be a non-empty string`);return r}if(typeof r=="function"){let t=K.getPropertiesFor(e),a=r(t);if(!a||typeof a!="object"||!(f in a))throw new Error(`${n} accessor must return a valid property descriptor`);let o=a[f],s=e.introspect().properties??{};for(let l of Object.keys(s)){let u=t[l];if(u&&u[f]===o)return l}throw new Error(`${n} accessor did not match any property in the schema`)}throw new Error(`${n} must be a string or a property descriptor accessor function`)}var p={date_from_json:r=>r&&new Date(Date.parse(r))};function v(r,e){if(typeof r=="string"){if(typeof p[r]!="function")throw new Error(`unknown mapper "${r}"`);return p[r](e)}if(typeof r=="function")return r(e);throw new Error("couldn't map value")}function b(r,e){if(!e)throw new Error("mappers should be an object");if(!r||typeof r!="object"||r===null)return r;let n={};for(let t of Object.keys(r))n[t]=e[t]?v(e[t],r[t]):r[t];return n}function x(r,e,n){for(let t of e){let a=t.as;t.mappers&&typeof r[a]<"u"&&r[a]!==null&&(r[a]=b(r[a],t.mappers))}for(let t of n){let a=t.as;if(Array.isArray(r[a]))for(let o=0;o<r[a].length;o++)r[a][o]&&t.mappers&&(r[a][o]=b(r[a][o],t.mappers))}return r}import{ObjectSchemaBuilder as ne,SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR as re}from"@cleverbrush/schema";function j(r,e){if(r.foreignQuery)return"toKnexQuery"in r.foreignQuery?r.foreignQuery.toKnexQuery():r.foreignQuery;let n=g(r.foreignSchema);return e(n)}function _(r,e,n){let t=m(r.localColumn,e,"localColumn"),a=m(r.foreignColumn,r.foreignSchema,"foreignColumn");if(typeof r.as!="string"||!r.as)throw new Error("as must be a non-empty string");let o=j(r,n),i=r.required!==!1;return r.mappers!==void 0&&M(r.mappers),{localColumn:t,foreignColumn:a,as:r.as,required:i,foreignQuery:o,mappers:r.mappers}}function E(r,e,n){let t=m(r.localColumn,e,"localColumn"),a=m(r.foreignColumn,r.foreignSchema,"foreignColumn");if(typeof r.as!="string"||!r.as)throw new Error("as must be a non-empty string");let o=j(r,n);r.mappers!==void 0&&M(r.mappers);let i=Number.isFinite(r.limit)&&r.limit>0?r.limit:null,s=Number.isFinite(r.offset)&&r.offset>0?r.offset:null,l=r.orderBy?{column:m(r.orderBy.column,r.foreignSchema,"orderBy.column"),direction:r.orderBy.direction??"asc"}:null;return{localColumn:t,foreignColumn:a,as:r.as,foreignQuery:o,limit:i,offset:s,orderBy:l,mappers:r.mappers}}function M(r){if(typeof r!="object"||r===null)throw new Error("mappers must be an object");for(let e of Object.keys(r)){let n=r[e];if(typeof n=="string"){if(typeof p[n]!="function")throw new Error(`mapper for "${e}": unknown built-in mapper name "${n}"`)}else if(typeof n!="function")throw new Error(`mapper for "${e}" must be a function or a built-in mapper name`)}}function C(r){let e=new Set;for(let n of r){if(e.has(n.as))throw new Error(`duplicate field name: ${n.as}`);e.add(n.as)}}var T=class r{#o;#e;#i;#t=[];#l;#a=null;constructor(e,n,t){this.#o=e,this.#i=n,this.#l=g(n),this.#e=t??e(this.#l)}#r(e,n="column"){return m(e,this.#i,n)}joinOne(e){let n=_(e,this.#i,this.#o);return this.#t.push({type:"one",...n}),C(this.#t),this}joinMany(e){let n=E(e,this.#i,this.#o);return this.#t.push({type:"many",...n}),C(this.#t),this}where(e,...n){if(typeof e=="function"&&!this.#h(e))this.#e.where(e,...n);else if(typeof e=="object"&&e!==null&&!("toSQL"in e)){let t=this.#y(e);this.#e.where(t,...n)}else{let t=this.#n(e);this.#e.where(t,...n)}return this}andWhere(e,...n){if(typeof e=="function"&&!this.#h(e))this.#e.andWhere(e,...n);else if(typeof e=="object"&&e!==null&&!("toSQL"in e)){let t=this.#y(e);this.#e.andWhere(t,...n)}else{let t=this.#n(e);this.#e.andWhere(t,...n)}return this}orWhere(e,...n){if(typeof e=="function"&&!this.#h(e))this.#e.orWhere(e,...n);else if(typeof e=="object"&&e!==null&&!("toSQL"in e)){let t=this.#y(e);this.#e.orWhere(t,...n)}else{let t=this.#n(e);this.#e.orWhere(t,...n)}return this}whereNot(e,...n){if(typeof e=="function"&&!this.#h(e))this.#e.whereNot(e,...n);else if(typeof e=="object"&&e!==null&&!("toSQL"in e)){let t=this.#y(e);this.#e.whereNot(t,...n)}else{let t=this.#n(e);this.#e.whereNot(t,...n)}return this}whereIn(e,n){return this.#e.whereIn(this.#r(e,"whereIn"),n),this}whereNotIn(e,n){return this.#e.whereNotIn(this.#r(e,"whereNotIn"),n),this}orWhereIn(e,n){return this.#e.orWhereIn(this.#r(e,"orWhereIn"),n),this}orWhereNotIn(e,n){return this.#e.orWhereNotIn(this.#r(e,"orWhereNotIn"),n),this}whereNull(e){return this.#e.whereNull(this.#r(e,"whereNull")),this}whereNotNull(e){return this.#e.whereNotNull(this.#r(e,"whereNotNull")),this}orWhereNull(e){return this.#e.orWhereNull(this.#r(e,"orWhereNull")),this}orWhereNotNull(e){return this.#e.orWhereNotNull(this.#r(e,"orWhereNotNull")),this}whereBetween(e,n){return this.#e.whereBetween(this.#r(e,"whereBetween"),n),this}whereNotBetween(e,n){return this.#e.whereNotBetween(this.#r(e,"whereNotBetween"),n),this}whereLike(e,n){return this.#e.whereLike(this.#r(e,"whereLike"),n),this}whereILike(e,n){return this.#e.whereILike(this.#r(e,"whereILike"),n),this}whereRaw(e,...n){return this.#e.whereRaw(e,...n),this}whereExists(e){return this.#e.whereExists(e),this}orderBy(e,n){let t=this.#n(e);return this.#e.orderBy(t,n),this}orderByRaw(e,...n){return this.#e.orderByRaw(e,...n),this}groupBy(...e){let n=e.map(t=>this.#n(t));return this.#e.groupBy(...n),this}groupByRaw(e,...n){return this.#e.groupByRaw(e,...n),this}having(e,n,t){let a=this.#n(e);return this.#e.having(a,n,t),this}havingRaw(e,...n){return this.#e.havingRaw(e,...n),this}limit(e){return this.#e.limit(e),this}offset(e){return this.#e.offset(e),this}select(...e){let n=e.map(t=>this.#n(t));this.#e.select(...n),this.#a??=[];for(let t of n)typeof t=="string"&&this.#a.push(t);return this}distinct(...e){let n=e.map(t=>this.#n(t));return this.#e.distinct(...n),this}count(e){return e?this.#e.count(this.#n(e)):this.#e.count(),this}countDistinct(e){return e?this.#e.countDistinct(this.#n(e)):this.#e.countDistinct(),this}min(e){return this.#e.min(this.#n(e)),this}max(e){return this.#e.max(this.#n(e)),this}sum(e){return this.#e.sum(this.#n(e)),this}avg(e){return this.#e.avg(this.#n(e)),this}async insert(e){let n=this.#c(e),[t]=await this.#o(this.#l).insert(n).returning("*");return this.#u(t)}async insertMany(e){let n=e.map(a=>this.#c(a));return(await this.#o(this.#l).insert(n).returning("*")).map(a=>this.#u(a))}async update(e){let n=this.#c(e);return(await this.#e.update(n).returning("*")).map(a=>this.#u(a))}async delete(){return this.#e.delete()}apply(e){return e(this.#e),this}transacting(e){let n=new r(e,this.#i,this.#e.clone().transacting(e));for(let t of this.#t)n.#t.push({...t,foreignQuery:t.foreignQuery.clone().transacting(e)});return n.#a=this.#a?[...this.#a]:null,n}#s(){if(this.#t.length===0)return this.#e;let e=this.#o,n=this.#t,t=[...new Set(n.map(s=>s.localColumn))],a=this.#e,o=[];if(this.#a!==null){let s=new Set(this.#a);if(o=t.filter(l=>!s.has(l)),o.length>0){a=this.#e.clone();for(let l of o)a.column(l)}}let i=e.queryBuilder().with("originalQuery",a);if(o.length>0&&this.#a!==null)for(let s of this.#a)i.select(e.raw(":originalQuery:.:col: as :col:",{originalQuery:"originalQuery",col:s}));else i.select("originalQuery.*");i.from(e.raw(":originalQuery:",{originalQuery:"originalQuery"}));for(let s=0;s<n.length;s++){let l=n[s],u=`eagerRelation${s}`;l.type==="one"?this.#f(i,l,u):this.#d(i,l,u,s)}return i}#f(e,n,t){let a=this.#o,o=n.foreignQuery,i=o._single?.table;if(!i)throw new Error(`Could not determine table name from foreignQuery for "${n.as}". Make sure foreignQuery is created via knex("tableName").`);e.select(a.raw(":relationAlias:.:as:->0 as :as:",{relationAlias:t,as:n.as}));let s=a.from(o.as(i)).select(a.raw(":foreignTable:.:foreignColumn:",{foreignTable:i,foreignColumn:n.foreignColumn})).select(a.raw("jsonb_agg(:foreignTable:) as :as:",{foreignTable:i,as:n.as})).groupByRaw(":foreignTable:.:foreignColumn:",{foreignTable:i,foreignColumn:n.foreignColumn}).as(t),l=n.required?"join":"leftJoin";e[l](s,function(){this.on(a.raw(":relationAlias:.:foreignColumn: = :originalQuery:.:localColumn:",{originalQuery:"originalQuery",relationAlias:t,foreignColumn:n.foreignColumn,localColumn:n.localColumn}))})}#d(e,n,t,a){let o=this.#o,i=`withFilter${a}`,s=n.limit!==null&&n.limit>0||n.offset!==null&&n.offset>0,l=n.orderBy?n.orderBy.column:n.foreignColumn,u=n.orderBy?n.orderBy.direction:"asc";s?e.with(i,o.from(n.foreignQuery.clone().whereIn(n.foreignColumn,o.from(o.raw(":originalQuery:",{originalQuery:"originalQuery"})).distinct(n.localColumn)).as(`__wf_inner_${a}`)).select(`__wf_inner_${a}.*`).select(o.raw(`row_number() over(partition by :foreignColumn: order by :orderByColumn: ${u}) as "__rn__"`,{foreignColumn:n.foreignColumn,orderByColumn:l}))):e.with(i,n.foreignQuery.clone().whereIn(n.foreignColumn,o.from(o.raw(":originalQuery:",{originalQuery:"originalQuery"})).distinct(n.localColumn)));let c=o.from(i);if(s){let S=n.limit!==null&&n.limit>0,W=n.offset!==null&&n.offset>0,Q=n.offset??0,P=Q+(n.limit??0),I=S&&W?'"__rn__" > :offset and "__rn__" <= :limit':S?'"__rn__" <= :limit':'"__rn__" > :offset';c.whereRaw(I,{limit:P,offset:Q}),c.select(o.raw(":foreignColumn:",{foreignColumn:n.foreignColumn})),c.select(o.raw(`coalesce(jsonb_agg(to_jsonb(:filterName:) - '__rn__' order by "__rn__"), '[]'::jsonb) as :as:`,{filterName:i,as:n.as}))}else{c.select(o.raw(":foreignColumn:",{foreignColumn:n.foreignColumn}));let S=n.orderBy?`jsonb_agg(:filterName: order by :filterName:.:orderByColumn: ${u})`:"jsonb_agg(:filterName:)";c.select(o.raw(`coalesce(${S}, '[]'::jsonb) as :as:`,n.orderBy?{filterName:i,orderByColumn:l,as:n.as}:{filterName:i,as:n.as}))}c.groupByRaw(":foreignColumn:",{foreignColumn:n.foreignColumn});let k=c.as(t);e.select(o.raw("coalesce(:relationAlias:.:as:, '[]'::jsonb) as :as:",{relationAlias:t,as:n.as})),e.leftJoin(k,function(){this.on(o.raw(":relationAlias:.:foreignColumn: = :originalQuery:.:localColumn:",{relationAlias:t,foreignColumn:n.foreignColumn,originalQuery:"originalQuery",localColumn:n.localColumn}))})}#u(e){if(!e)return e;let{colToProp:n}=d(this.#i),t={};for(let[a,o]of Object.entries(e)){let i=n.get(a);i?t[i]=o:t[a]=o}return t}#m(e){let n=this.#t.filter(o=>o.type==="one"),t=this.#t.filter(o=>o.type==="many"),a=x(e,n,t);return this.#u(a)}#c(e){let{propToCol:n}=d(this.#i),t={};for(let[a,o]of Object.entries(e)){let i=n.get(a);i?t[i]=o:t[a]=o}return t}#y(e){return this.#c(e)}#n(e){return typeof e=="string"?this.#r(e,"column"):typeof e=="function"?this.#r(e,"column"):e}#h(e){try{let n=ne.getPropertiesFor(this.#i),t=e(n);if(t&&typeof t=="object"&&re in t)return!0}catch{}return!1}toQuery(){return this.#s().toQuery()}toKnexQuery(){return this.#s()}toString(){return this.#s().toString()}async execute(){let n=await this.#s();return n?Array.isArray(n)?n.map(t=>this.#m(t)):[this.#m(n)]:[]}async first(){let n=await this.#s().first();if(n)return this.#m(n)}then(e,n){return this.execute().then(e,n)}};function R(r,e,n){return new T(r,e,n)}function B(r){function e(n,t){return t?R(r,n,t):R(r,n)}return e.withTransaction=n=>B(n),e.transaction=n=>r.transaction(t=>n(B(t))),e}export{p as MAPPERS,T as SchemaQueryBuilder,Z as any,H as array,U as boolean,d as buildColumnMap,x as clearRow,B as createQuery,Y as date,L as dbExtension,X as func,w as getColumnName,g as getTableName,b as mapObject,v as mapValue,D as number,G as object,R as query,m as resolveColumnRef,ee as resolvePropertyKey,V as string,z as union};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/columns.ts","../src/extension.ts","../src/mappers.ts","../src/SchemaQueryBuilder.ts","../src/validate.ts"],"sourcesContent":["// @cleverbrush/knex-schema — Column resolution and mapping\n\nimport type { SchemaBuilder } from '@cleverbrush/schema';\nimport {\n ObjectSchemaBuilder,\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n} from '@cleverbrush/schema';\nimport { getColumnName } from './extension.js';\nimport type { ColumnRef } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Column map cache (schema → { propToCol, colToProp })\n// ---------------------------------------------------------------------------\n\ninterface ColumnMapResult {\n propToCol: Map<string, string>;\n colToProp: Map<string, string>;\n}\n\nconst columnMapCache = new WeakMap<object, ColumnMapResult>();\n\n/**\n * Build a bidirectional column map from an ObjectSchemaBuilder's properties.\n * Uses `getExtension('columnName')` per property, falling back to the property key.\n * Result is cached per schema instance via WeakMap.\n */\nexport function buildColumnMap(\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>\n): ColumnMapResult {\n const cached = columnMapCache.get(schema);\n if (cached) return cached;\n\n const introspected = schema.introspect() as any;\n const properties: Record<\n string,\n SchemaBuilder<any, any, any>\n > = introspected.properties ?? {};\n\n const propToCol = new Map<string, string>();\n const colToProp = new Map<string, string>();\n\n for (const propKey of Object.keys(properties)) {\n const propSchema = properties[propKey];\n const colName = getColumnName(propSchema, propKey);\n\n propToCol.set(propKey, colName);\n colToProp.set(colName, propKey);\n }\n\n const result: ColumnMapResult = { propToCol, colToProp };\n columnMapCache.set(schema, result);\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Resolve a ColumnRef (string | accessor) to a SQL column name\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a ColumnRef to a plain SQL column name.\n *\n * - String refs are treated as **property keys** and translated to column\n * names via the column map.\n * - Function refs (property accessor) are resolved via PropertyDescriptorTree,\n * then translated to column names.\n */\nexport function resolveColumnRef(\n ref: ColumnRef<any>,\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n label: string\n): string {\n if (typeof ref === 'string') {\n if (!ref) throw new Error(`${label} must be a non-empty string`);\n const { propToCol } = buildColumnMap(schema);\n const col = propToCol.get(ref);\n // If the string is a known property key, return its column name;\n // otherwise assume it's already a raw column name and pass through.\n return col ?? ref;\n }\n\n if (typeof ref === 'function') {\n const tree = ObjectSchemaBuilder.getPropertiesFor(schema as any);\n const descriptor = ref(tree as any);\n\n if (\n !descriptor ||\n typeof descriptor !== 'object' ||\n !(SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR in descriptor)\n ) {\n throw new Error(\n `${label} accessor must return a valid property descriptor`\n );\n }\n\n const inner = (descriptor as any)[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR];\n const introspected = schema.introspect() as any;\n const properties = introspected.properties ?? {};\n\n for (const propName of Object.keys(properties)) {\n const propDescriptor = (tree as any)[propName];\n if (\n propDescriptor &&\n (propDescriptor as any)[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR] ===\n inner\n ) {\n // Found the matching property — resolve to column name\n const { propToCol } = buildColumnMap(schema);\n return propToCol.get(propName) ?? propName;\n }\n }\n\n throw new Error(\n `${label} accessor did not match any property in the schema`\n );\n }\n\n throw new Error(\n `${label} must be a string or a property descriptor accessor function`\n );\n}\n\n/**\n * Resolve a ColumnRef to the **property key** (not the column name).\n * Used for result mapping.\n */\nexport function resolvePropertyKey(\n ref: ColumnRef<any>,\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n label: string\n): string {\n if (typeof ref === 'string') {\n if (!ref) throw new Error(`${label} must be a non-empty string`);\n return ref;\n }\n\n if (typeof ref === 'function') {\n const tree = ObjectSchemaBuilder.getPropertiesFor(schema as any);\n const descriptor = ref(tree as any);\n\n if (\n !descriptor ||\n typeof descriptor !== 'object' ||\n !(SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR in descriptor)\n ) {\n throw new Error(\n `${label} accessor must return a valid property descriptor`\n );\n }\n\n const inner = (descriptor as any)[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR];\n const introspected = schema.introspect() as any;\n const properties = introspected.properties ?? {};\n\n for (const propName of Object.keys(properties)) {\n const propDescriptor = (tree as any)[propName];\n if (\n propDescriptor &&\n (propDescriptor as any)[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR] ===\n inner\n ) {\n return propName;\n }\n }\n\n throw new Error(\n `${label} accessor did not match any property in the schema`\n );\n }\n\n throw new Error(\n `${label} must be a string or a property descriptor accessor function`\n );\n}\n","// @cleverbrush/knex-schema — Schema extension: hasColumnName / hasTableName\n\nimport type {\n AnySchemaBuilder,\n ArraySchemaBuilder,\n BooleanSchemaBuilder,\n DateSchemaBuilder,\n FunctionSchemaBuilder,\n GenericSchemaBuilder,\n NumberSchemaBuilder,\n ObjectSchemaBuilder,\n SchemaBuilder,\n StringSchemaBuilder,\n UnionSchemaBuilder\n} from '@cleverbrush/schema';\nimport { defineExtension, withExtensions } from '@cleverbrush/schema';\n\n// ---------------------------------------------------------------------------\n// Shared implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Stores the SQL column name for a schema property using the schema extension\n * system. Consumed by {@link getColumnName} and the query builder's column\n * resolution logic.\n */\nfunction hasColumnName(this: SchemaBuilder<any, any, any>, name: string) {\n return this.withExtension('columnName', name);\n}\n\n/**\n * Stores the SQL table name for an `ObjectSchemaBuilder` using the schema\n * extension system. Required for {@link query} to build queries — throws at\n * query creation time if not set.\n */\nfunction hasTableName(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n name: string\n) {\n return this.withExtension('tableName', name);\n}\n\n// ---------------------------------------------------------------------------\n// Extension definition\n// ---------------------------------------------------------------------------\n\n/**\n * Schema extension that adds database-mapping metadata to schema builders.\n *\n * Import the typed factory functions (`string`, `number`, `object`, etc.) from\n * this package instead of from `@cleverbrush/schema` to gain access to the\n * `.hasColumnName()` and `.hasTableName()` methods.\n *\n * @example\n * ```ts\n * import { object, string, number } from '@cleverbrush/knex-schema';\n *\n * const UserSchema = object({\n * id: number(),\n * firstName: string().hasColumnName('first_name'),\n * lastName: string().hasColumnName('last_name'),\n * createdAt: date().hasColumnName('created_at'),\n * }).hasTableName('users');\n * ```\n */\nexport const dbExtension = defineExtension({\n string: {\n /**\n * Override the SQL column name for this property.\n *\n * By default the property key is used as the column name. Call\n * `.hasColumnName('sql_col')` when the database column differs from the\n * schema property name (e.g. camelCase property → snake_case column).\n *\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: StringSchemaBuilder<any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n number: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n boolean: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: BooleanSchemaBuilder<any, any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n date: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: DateSchemaBuilder<any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n any: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: AnySchemaBuilder<any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n func: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: FunctionSchemaBuilder<any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n array: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: ArraySchemaBuilder<any, any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n union: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: UnionSchemaBuilder<any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n generic: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: GenericSchemaBuilder<any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n object: {\n /**\n * Set the SQL table name for this object schema.\n *\n * Required before creating a {@link query} builder — throws at\n * query creation time when not set.\n *\n * @param name - The SQL table name (e.g. `'users'`).\n */\n hasTableName(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n name: string\n ) {\n return hasTableName.call(this, name);\n }\n }\n});\n\n// ---------------------------------------------------------------------------\n// Extended factory functions\n// ---------------------------------------------------------------------------\n\nconst extended = withExtensions(dbExtension);\n\nexport const string = extended.string;\nexport const number = extended.number;\nexport const boolean = extended.boolean;\nexport const date = extended.date;\nexport const object = extended.object;\nexport const array = extended.array;\nexport const union = extended.union;\nexport const func = extended.func;\nexport const any = extended.any;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get the SQL column name for a schema property.\n * Returns the `hasColumnName()` value if set, otherwise falls back to `propertyKey`.\n */\nexport function getColumnName(\n schema: SchemaBuilder<any, any, any>,\n propertyKey: string\n): string {\n const col = schema.getExtension('columnName');\n return typeof col === 'string' ? col : propertyKey;\n}\n\n/**\n * Get the SQL table name from an ObjectSchemaBuilder.\n * Throws if `hasTableName()` was never called.\n */\nexport function getTableName(\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>\n): string {\n const table = schema.getExtension('tableName');\n if (typeof table !== 'string') {\n throw new Error(\n 'Schema does not have a table name. Use .hasTableName(\"table_name\") to set one.'\n );\n }\n return table;\n}\n","// @cleverbrush/knex-schema — Post-load value transformers (from knex-eager)\n\nimport type { ValidatedSpec } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Built-in named mappers\n// ---------------------------------------------------------------------------\n\n/**\n * Built-in named value mapper functions.\n *\n * Each entry maps a string key to a transformation function. Pass the key as\n * the `mapper` argument to {@link mapValue} instead of a custom function.\n *\n * Currently available:\n * - `date_from_json` — converts a JSON date string (`string | null`) to a\n * JavaScript `Date` object (or passes through falsy values unchanged).\n */\nexport const MAPPERS: Record<string, (value: any) => any> = {\n date_from_json: (value: any) => {\n if (!value) return value;\n return new Date(Date.parse(value));\n }\n};\n\n// ---------------------------------------------------------------------------\n// mapValue — apply a mapper (function or built-in name) to a value\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a single mapper to a value.\n *\n * @param mapper - Either a transformation function `(v: any) => any`, or a\n * string key referencing one of the {@link MAPPERS} built-ins (e.g.\n * `'date_from_json'`).\n * @param value - The raw value to transform.\n * @returns The transformed value.\n *\n * @throws If `mapper` is a string that does not exist in {@link MAPPERS}.\n */\nexport function mapValue(mapper: ((v: any) => any) | string, value: any): any {\n if (typeof mapper === 'string') {\n if (typeof MAPPERS[mapper] !== 'function') {\n throw new Error(`unknown mapper \"${mapper}\"`);\n }\n return MAPPERS[mapper](value);\n }\n if (typeof mapper === 'function') {\n return mapper(value);\n }\n throw new Error(\"couldn't map value\");\n}\n\n// ---------------------------------------------------------------------------\n// mapObject — apply mappers to all matching keys of an object\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a map of per-key transformations to an object, returning a new object\n * with the transformed values.\n *\n * Keys not present in `mappers` are copied through unchanged. Keys present in\n * `mappers` are passed through {@link mapValue}.\n *\n * @param obj - The source object (e.g. a raw database row).\n * @param mappers - A `Record` mapping property keys to mapper functions or\n * {@link MAPPERS} built-in names.\n * @returns A shallow copy of `obj` with the specified values transformed.\n *\n * @throws If `obj` is `null` or a non-object, it is returned as-is.\n */\nexport function mapObject<T extends Record<string, any>>(\n obj: T,\n mappers: Record<string, ((v: any) => any) | string>\n): T {\n if (!mappers) throw new Error('mappers should be an object');\n if (!obj || typeof obj !== 'object' || obj === null) return obj;\n\n const result = {} as Record<string, any>;\n for (const key of Object.keys(obj)) {\n result[key] = mappers[key]\n ? mapValue(mappers[key], obj[key])\n : obj[key];\n }\n return result as T;\n}\n\n// ---------------------------------------------------------------------------\n// clearRow — apply mappers to joined data in a result row\n// ---------------------------------------------------------------------------\n\n/**\n * Apply value mappers to the joined fields of a result row in place.\n *\n * After an eager-loaded query resolves, joined objects and arrays may contain\n * raw database values that need transformation (e.g. date strings → `Date`).\n * This function iterates over the one-to-one and one-to-many specs, applies\n * the `mappers` defined on each spec to the nested data, and returns the\n * mutated row.\n *\n * This is an internal helper used by {@link SchemaQueryBuilder}'s result\n * mapping pipeline. Exported to allow custom post-processing if needed.\n *\n * @param row - The raw result row (mutated in place).\n * @param oneSpecs - Validated one-to-one join specs with optional `mappers`.\n * @param manySpecs - Validated one-to-many join specs with optional `mappers`.\n * @returns The mutated `row` object.\n */\nexport function clearRow(\n row: Record<string, any>,\n oneSpecs: Array<ValidatedSpec & { type: 'one' }>,\n manySpecs: Array<ValidatedSpec & { type: 'many' }>\n): Record<string, any> {\n for (const spec of oneSpecs) {\n const fieldName = spec.as;\n if (\n spec.mappers &&\n typeof row[fieldName] !== 'undefined' &&\n row[fieldName] !== null\n ) {\n row[fieldName] = mapObject(row[fieldName], spec.mappers);\n }\n }\n\n for (const spec of manySpecs) {\n const fieldName = spec.as;\n if (Array.isArray(row[fieldName])) {\n for (let k = 0; k < row[fieldName].length; k++) {\n if (row[fieldName][k]) {\n if (spec.mappers) {\n row[fieldName][k] = mapObject(\n row[fieldName][k],\n spec.mappers\n );\n }\n }\n }\n }\n }\n\n return row;\n}\n","// @cleverbrush/knex-schema — SchemaQueryBuilder\n\nimport type { InferType } from '@cleverbrush/schema';\nimport {\n ObjectSchemaBuilder,\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n} from '@cleverbrush/schema';\nimport type { Knex } from 'knex';\nimport { buildColumnMap, resolveColumnRef } from './columns.js';\nimport { getTableName } from './extension.js';\nimport { clearRow } from './mappers.js';\nimport type {\n ColumnRef,\n InsertType,\n JoinManySpec,\n JoinOneSpec,\n ValidatedSpec,\n WithJoinedMany,\n WithJoinedOne\n} from './types.js';\nimport {\n validateJoinMany,\n validateJoinOne,\n validateUniqueFieldNames\n} from './validate.js';\n\n// ---------------------------------------------------------------------------\n// SchemaQueryBuilder\n// ---------------------------------------------------------------------------\n\n/**\n * Type-safe, schema-driven query builder for Knex.\n *\n * `SchemaQueryBuilder` wraps a Knex.QueryBuilder and adds:\n * - **Type-safe column references** — pass a property accessor (`t => t.name`)\n * or a string property name; both are resolved to the correct SQL column\n * through the schema's `hasColumnName()` metadata automatically.\n * - **Eager loading without N+1** — {@link joinOne} and {@link joinMany} use\n * PostgreSQL CTEs and `jsonb_agg` to load related rows in a single query.\n * - **Bidirectional result mapping** — rows returned from Postgres (column\n * names) are converted back to schema property names before being returned.\n * - **Thenable protocol** — the builder itself is `await`-able so you can\n * write `await query(db, Schema)` without calling {@link execute} explicitly.\n *\n * Create instances via the {@link query} factory function rather than\n * calling the constructor directly.\n *\n * @typeParam TLocalSchema - The `ObjectSchemaBuilder` describing the main table.\n * @typeParam TResult - The inferred row type, widened automatically as joins\n * are registered via {@link joinOne} / {@link joinMany}.\n *\n * @example\n * ```ts\n * import knex from 'knex';\n * import { query, object, string, number } from '@cleverbrush/knex-schema';\n *\n * const UserSchema = object({\n * id: number(),\n * name: string(),\n * age: number().optional(),\n * }).hasTableName('users');\n *\n * const db = knex({ client: 'pg', connection: process.env.DB_URL });\n *\n * // Fetch all users older than 18, ordered by name\n * const adults = await query(db, UserSchema)\n * .where(t => t.age, '>', 18)\n * .orderBy(t => t.name);\n * // adults: Array<{ id: number; name: string; age?: number }>\n * ```\n */\nexport class SchemaQueryBuilder<\n TLocalSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n TResult\n> {\n readonly #knex: Knex;\n readonly #baseQuery: Knex.QueryBuilder;\n readonly #localSchema: TLocalSchema;\n readonly #specs: ValidatedSpec[] = [];\n readonly #tableName: string;\n /**\n * Tracks the SQL column names that were explicitly passed to `.select()`.\n * `null` means no explicit select was made (SELECT *).\n */\n #explicitSelects: string[] | null = null;\n\n /**\n * @param knex - A configured Knex instance.\n * @param localSchema - The `ObjectSchemaBuilder` for the primary table.\n * Must have a table name set via `.hasTableName()`.\n * @param baseQuery - Optional pre-configured `Knex.QueryBuilder` to use as\n * the base query instead of the default `knex(tableName)`. Useful when you\n * need custom joins, CTEs, or other Knex features not exposed by this API.\n */\n constructor(\n knex: Knex,\n localSchema: TLocalSchema,\n baseQuery?: Knex.QueryBuilder\n ) {\n this.#knex = knex;\n this.#localSchema = localSchema;\n this.#tableName = getTableName(localSchema);\n this.#baseQuery = baseQuery ?? knex(this.#tableName);\n }\n\n // =======================================================================\n // Private helpers\n // =======================================================================\n\n #resolveColumn(ref: any, label = 'column'): string {\n return resolveColumnRef(\n ref as ColumnRef<any>,\n this.#localSchema,\n label\n );\n }\n\n // =======================================================================\n // Relation methods — eager loading (absorbed from knex-eager)\n // =======================================================================\n\n /**\n * Eager-load a single related row (one-to-one / many-to-one relationship).\n *\n * The related rows are fetched using a single CTE + `jsonb_agg` — no N+1\n * queries. The related object is attached to each result row under the\n * field name specified by `spec.as`.\n *\n * @param spec - Join specification. Key fields:\n * - `foreignSchema` — the `ObjectSchemaBuilder` of the related table.\n * - `localColumn` — the local column that holds the foreign-table reference.\n * - `foreignColumn` — the primary/unique key on the foreign table.\n * - `as` — the property name to attach the related object under.\n * - `required` — if `true` (default), rows without a matching related\n * record are excluded (inner join); if `false`, they are included with\n * `null` (left join).\n * - `foreignQuery` — optional pre-filtered `Knex.QueryBuilder` for the\n * foreign table (e.g. to apply scopes).\n *\n * @returns `this` (with an updated `TResult` type that includes the new field)\n * for chaining.\n *\n * @example\n * ```ts\n * const PostSchema = object({\n * id: number(),\n * title: string(),\n * authorId: number(),\n * }).hasTableName('posts');\n *\n * const AuthorSchema = object({\n * id: number(),\n * name: string(),\n * }).hasTableName('authors');\n *\n * const posts = await query(db, PostSchema)\n * .joinOne({\n * foreignSchema: AuthorSchema,\n * localColumn: t => t.authorId,\n * foreignColumn: t => t.id,\n * as: 'author',\n * });\n * // posts[0].author.name — typed as string ✓\n * ```\n */\n joinOne<\n TForeignSchema extends ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >,\n TFieldName extends string,\n TRequired extends boolean = true\n >(\n spec: JoinOneSpec<TLocalSchema, TForeignSchema, TFieldName, TRequired>\n ): SchemaQueryBuilder<\n TLocalSchema,\n WithJoinedOne<TResult, TFieldName, TForeignSchema, TRequired>\n > {\n const validated = validateJoinOne(spec, this.#localSchema, this.#knex);\n this.#specs.push({ type: 'one' as const, ...validated });\n validateUniqueFieldNames(this.#specs);\n return this as any;\n }\n\n /**\n * Eager-load a collection of related rows (one-to-many relationship).\n *\n * Related rows are fetched via a single CTE + `jsonb_agg` query. The\n * collection is attached to each result row under the field name specified\n * by `spec.as`. Supports `limit`, `offset`, and `orderBy` per-parent\n * using a `row_number()` window function to avoid fetching the full\n * relation before slicing.\n *\n * @param spec - Join specification. Key fields:\n * - `foreignSchema` — the `ObjectSchemaBuilder` of the related table.\n * - `localColumn` — the primary/unique key on the local table.\n * - `foreignColumn` — the column on the foreign table that references `localColumn`.\n * - `as` — the property name to attach the array under.\n * - `limit` / `offset` — optional pagination per parent row.\n * - `orderBy` — optional `{ column, direction }` for the sub-collection.\n * - `foreignQuery` — optional pre-filtered `Knex.QueryBuilder`.\n *\n * @returns `this` (with an updated `TResult` type that includes the new field)\n * for chaining.\n *\n * @example\n * ```ts\n * const UserSchema = object({\n * id: number(),\n * name: string(),\n * }).hasTableName('users');\n *\n * const PostSchema = object({\n * id: number(),\n * title: string(),\n * authorId: number(),\n * }).hasTableName('posts');\n *\n * const users = await query(db, UserSchema)\n * .joinMany({\n * foreignSchema: PostSchema,\n * localColumn: t => t.id,\n * foreignColumn: t => t.authorId,\n * as: 'posts',\n * limit: 5,\n * orderBy: { column: t => t.id, direction: 'desc' },\n * });\n * // users[0].posts — typed as Array<{ id: number; title: string; authorId: number }>\n * ```\n */\n joinMany<\n TForeignSchema extends ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >,\n TFieldName extends string\n >(\n spec: JoinManySpec<TLocalSchema, TForeignSchema, TFieldName>\n ): SchemaQueryBuilder<\n TLocalSchema,\n WithJoinedMany<TResult, TFieldName, TForeignSchema>\n > {\n const validated = validateJoinMany(spec, this.#localSchema, this.#knex);\n this.#specs.push({ type: 'many' as const, ...validated });\n validateUniqueFieldNames(this.#specs);\n return this as any;\n }\n\n // =======================================================================\n // WHERE methods\n // =======================================================================\n\n /**\n * Add a `WHERE` clause to the query.\n *\n * Accepts a column reference, an optional operator, and a value:\n * - `where(t => t.age, '>', 18)` — property accessor + operator + value.\n * - `where('age', 18)` — string key + value (defaults to `=`).\n * - `where({ name: 'Alice' })` — record object; property keys are mapped\n * to column names automatically.\n * - `where(builder => { ... })` — Knex sub-builder callback for grouped\n * conditions.\n * - `where(knex.raw('...'))` — raw SQL expression.\n *\n * Multiple `.where()` calls are combined with `AND`.\n *\n * @returns `this` for chaining.\n */\n where(column: ColumnRef<TLocalSchema>, operator: string, value: any): this;\n where(column: ColumnRef<TLocalSchema>, value: any): this;\n where(raw: Knex.Raw, operator: string, value: any): this;\n where(callback: (builder: Knex.QueryBuilder) => void): this;\n where(record: Record<string, any>): this;\n where(raw: Knex.Raw): this;\n where(\n columnOrRaw:\n | ColumnRef<TLocalSchema>\n | Knex.Raw\n | Record<string, any>\n | ((builder: Knex.QueryBuilder) => void),\n ...args: any[]\n ): this {\n if (\n typeof columnOrRaw === 'function' &&\n !this.#isColumnAccessor(columnOrRaw)\n ) {\n (this.#baseQuery.where as any)(columnOrRaw, ...args);\n } else if (\n typeof columnOrRaw === 'object' &&\n columnOrRaw !== null &&\n !('toSQL' in columnOrRaw)\n ) {\n // Record<string, any> — map property keys to column names\n const mapped = this.#mapRecordToColumns(\n columnOrRaw as Record<string, any>\n );\n (this.#baseQuery.where as any)(mapped, ...args);\n } else {\n const col = this.#resolveColumnArg(columnOrRaw);\n (this.#baseQuery.where as any)(col, ...args);\n }\n return this;\n }\n\n /**\n * Alias for {@link where} — explicitly adds an `AND WHERE` clause.\n * Identical to calling `.where()` when no logical-OR grouping is needed.\n * @returns `this` for chaining.\n */\n andWhere(\n column: ColumnRef<TLocalSchema>,\n operator: string,\n value: any\n ): this;\n andWhere(column: ColumnRef<TLocalSchema>, value: any): this;\n andWhere(record: Record<string, any>): this;\n andWhere(callback: (builder: Knex.QueryBuilder) => void): this;\n andWhere(raw: Knex.Raw): this;\n andWhere(\n columnOrRaw:\n | ColumnRef<TLocalSchema>\n | Knex.Raw\n | Record<string, any>\n | ((builder: Knex.QueryBuilder) => void),\n ...args: any[]\n ): this {\n if (\n typeof columnOrRaw === 'function' &&\n !this.#isColumnAccessor(columnOrRaw)\n ) {\n (this.#baseQuery.andWhere as any)(columnOrRaw, ...args);\n } else if (\n typeof columnOrRaw === 'object' &&\n columnOrRaw !== null &&\n !('toSQL' in columnOrRaw)\n ) {\n const mapped = this.#mapRecordToColumns(\n columnOrRaw as Record<string, any>\n );\n (this.#baseQuery.andWhere as any)(mapped, ...args);\n } else {\n const col = this.#resolveColumnArg(columnOrRaw);\n (this.#baseQuery.andWhere as any)(col, ...args);\n }\n return this;\n }\n\n /**\n * Add an `OR WHERE` clause. Use this to create alternative filter branches.\n * @returns `this` for chaining.\n */\n orWhere(\n column: ColumnRef<TLocalSchema>,\n operator: string,\n value: any\n ): this;\n orWhere(column: ColumnRef<TLocalSchema>, value: any): this;\n orWhere(record: Record<string, any>): this;\n orWhere(callback: (builder: Knex.QueryBuilder) => void): this;\n orWhere(raw: Knex.Raw): this;\n orWhere(\n columnOrRaw:\n | ColumnRef<TLocalSchema>\n | Knex.Raw\n | Record<string, any>\n | ((builder: Knex.QueryBuilder) => void),\n ...args: any[]\n ): this {\n if (\n typeof columnOrRaw === 'function' &&\n !this.#isColumnAccessor(columnOrRaw)\n ) {\n (this.#baseQuery.orWhere as any)(columnOrRaw, ...args);\n } else if (\n typeof columnOrRaw === 'object' &&\n columnOrRaw !== null &&\n !('toSQL' in columnOrRaw)\n ) {\n const mapped = this.#mapRecordToColumns(\n columnOrRaw as Record<string, any>\n );\n (this.#baseQuery.orWhere as any)(mapped, ...args);\n } else {\n const col = this.#resolveColumnArg(columnOrRaw);\n (this.#baseQuery.orWhere as any)(col, ...args);\n }\n return this;\n }\n\n /**\n * Add a `WHERE NOT` clause — negates the condition.\n * @returns `this` for chaining.\n */\n whereNot(\n column: ColumnRef<TLocalSchema>,\n operator: string,\n value: any\n ): this;\n whereNot(column: ColumnRef<TLocalSchema>, value: any): this;\n whereNot(record: Record<string, any>): this;\n whereNot(callback: (builder: Knex.QueryBuilder) => void): this;\n whereNot(raw: Knex.Raw): this;\n whereNot(\n columnOrRaw:\n | ColumnRef<TLocalSchema>\n | Knex.Raw\n | Record<string, any>\n | ((builder: Knex.QueryBuilder) => void),\n ...args: any[]\n ): this {\n if (\n typeof columnOrRaw === 'function' &&\n !this.#isColumnAccessor(columnOrRaw)\n ) {\n (this.#baseQuery.whereNot as any)(columnOrRaw, ...args);\n } else if (\n typeof columnOrRaw === 'object' &&\n columnOrRaw !== null &&\n !('toSQL' in columnOrRaw)\n ) {\n const mapped = this.#mapRecordToColumns(\n columnOrRaw as Record<string, any>\n );\n (this.#baseQuery.whereNot as any)(mapped, ...args);\n } else {\n const col = this.#resolveColumnArg(columnOrRaw);\n (this.#baseQuery.whereNot as any)(col, ...args);\n }\n return this;\n }\n\n /**\n * Add a `WHERE column IN (values)` clause.\n * @param column - Column reference (property accessor or string key).\n * @param values - Array of values or a sub-query.\n * @returns `this` for chaining.\n */\n whereIn(\n column: ColumnRef<TLocalSchema>,\n values: readonly any[] | Knex.QueryBuilder\n ): this {\n this.#baseQuery.whereIn(\n this.#resolveColumn(column, 'whereIn'),\n values as any\n );\n return this;\n }\n\n /**\n * Add a `WHERE column NOT IN (values)` clause.\n * @param column - Column reference.\n * @param values - Array of values or a sub-query.\n * @returns `this` for chaining.\n */\n whereNotIn(\n column: ColumnRef<TLocalSchema>,\n values: readonly any[] | Knex.QueryBuilder\n ): this {\n this.#baseQuery.whereNotIn(\n this.#resolveColumn(column, 'whereNotIn'),\n values as any\n );\n return this;\n }\n\n /**\n * Add an `OR WHERE column IN (values)` clause.\n * @returns `this` for chaining.\n */\n orWhereIn(\n column: ColumnRef<TLocalSchema>,\n values: readonly any[] | Knex.QueryBuilder\n ): this {\n (this.#baseQuery as any).orWhereIn(\n this.#resolveColumn(column, 'orWhereIn'),\n values as any\n );\n return this;\n }\n\n /**\n * Add an `OR WHERE column NOT IN (values)` clause.\n * @returns `this` for chaining.\n */\n orWhereNotIn(\n column: ColumnRef<TLocalSchema>,\n values: readonly any[] | Knex.QueryBuilder\n ): this {\n (this.#baseQuery as any).orWhereNotIn(\n this.#resolveColumn(column, 'orWhereNotIn'),\n values as any\n );\n return this;\n }\n\n /**\n * Add a `WHERE column IS NULL` clause.\n * @returns `this` for chaining.\n */\n whereNull(column: ColumnRef<TLocalSchema>): this {\n this.#baseQuery.whereNull(this.#resolveColumn(column, 'whereNull'));\n return this;\n }\n\n /**\n * Add a `WHERE column IS NOT NULL` clause.\n * @returns `this` for chaining.\n */\n whereNotNull(column: ColumnRef<TLocalSchema>): this {\n this.#baseQuery.whereNotNull(\n this.#resolveColumn(column, 'whereNotNull')\n );\n return this;\n }\n\n /**\n * Add an `OR WHERE column IS NULL` clause.\n * @returns `this` for chaining.\n */\n orWhereNull(column: ColumnRef<TLocalSchema>): this {\n (this.#baseQuery as any).orWhereNull(\n this.#resolveColumn(column, 'orWhereNull')\n );\n return this;\n }\n\n /**\n * Add an `OR WHERE column IS NOT NULL` clause.\n * @returns `this` for chaining.\n */\n orWhereNotNull(column: ColumnRef<TLocalSchema>): this {\n (this.#baseQuery as any).orWhereNotNull(\n this.#resolveColumn(column, 'orWhereNotNull')\n );\n return this;\n }\n\n /**\n * Add a `WHERE column BETWEEN low AND high` clause.\n * @param range - A two-element tuple `[low, high]`.\n * @returns `this` for chaining.\n */\n whereBetween(\n column: ColumnRef<TLocalSchema>,\n range: readonly [any, any]\n ): this {\n this.#baseQuery.whereBetween(\n this.#resolveColumn(column, 'whereBetween'),\n range as [any, any]\n );\n return this;\n }\n\n /**\n * Add a `WHERE column NOT BETWEEN low AND high` clause.\n * @param range - A two-element tuple `[low, high]`.\n * @returns `this` for chaining.\n */\n whereNotBetween(\n column: ColumnRef<TLocalSchema>,\n range: readonly [any, any]\n ): this {\n this.#baseQuery.whereNotBetween(\n this.#resolveColumn(column, 'whereNotBetween'),\n range as [any, any]\n );\n return this;\n }\n\n /**\n * Add a case-sensitive `WHERE column LIKE value` clause.\n * @param value - A SQL LIKE pattern (e.g. `'Alice%'`).\n * @returns `this` for chaining.\n */\n whereLike(column: ColumnRef<TLocalSchema>, value: string): this {\n (this.#baseQuery as any).whereLike(\n this.#resolveColumn(column, 'whereLike'),\n value\n );\n return this;\n }\n\n /**\n * Add a case-insensitive `WHERE column ILIKE value` clause (PostgreSQL).\n * @param value - A SQL LIKE pattern (e.g. `'alice%'`).\n * @returns `this` for chaining.\n */\n whereILike(column: ColumnRef<TLocalSchema>, value: string): this {\n (this.#baseQuery as any).whereILike(\n this.#resolveColumn(column, 'whereILike'),\n value\n );\n return this;\n }\n\n /**\n * Add a raw `WHERE` clause. Useful for database-specific expressions.\n * @param sql - Raw SQL string with optional `:binding:` or `?` placeholders.\n * @param bindings - Values for the placeholders.\n * @returns `this` for chaining.\n */\n whereRaw(sql: string, ...bindings: any[]): this {\n this.#baseQuery.whereRaw(sql, ...bindings);\n return this;\n }\n\n /**\n * Add a `WHERE EXISTS (subquery)` clause.\n * @param callback - A Knex query callback or sub-query builder.\n * @returns `this` for chaining.\n */\n whereExists(callback: Knex.QueryCallback | Knex.QueryBuilder): this {\n this.#baseQuery.whereExists(callback as any);\n return this;\n }\n\n // =======================================================================\n // ORDER BY\n // =======================================================================\n\n /**\n * Order the results by a column.\n * @param column - Column reference or raw expression.\n * @param direction - `'asc'` (default) or `'desc'`.\n * @returns `this` for chaining.\n *\n * @example\n * ```ts\n * query(db, UserSchema).orderBy(t => t.name).orderBy(t => t.createdAt, 'desc');\n * ```\n */\n orderBy(\n column: ColumnRef<TLocalSchema> | Knex.Raw,\n direction?: 'asc' | 'desc'\n ): this {\n const col = this.#resolveColumnArg(column);\n this.#baseQuery.orderBy(col as string, direction);\n return this;\n }\n\n /**\n * Order the results by a raw SQL expression.\n * @param sql - Raw SQL (e.g. `'LOWER(name) ASC'`).\n * @returns `this` for chaining.\n */\n orderByRaw(sql: string, ...bindings: any[]): this {\n this.#baseQuery.orderByRaw(sql, ...bindings);\n return this;\n }\n\n // =======================================================================\n // GROUP BY / HAVING\n // =======================================================================\n\n /**\n * Add a `GROUP BY` clause.\n * @param columns - One or more column references or raw expressions.\n * @returns `this` for chaining.\n */\n groupBy(...columns: (ColumnRef<TLocalSchema> | Knex.Raw)[]): this {\n const resolved = columns.map(c => this.#resolveColumnArg(c));\n this.#baseQuery.groupBy(...(resolved as string[]));\n return this;\n }\n\n /**\n * Add a raw `GROUP BY` expression.\n * @returns `this` for chaining.\n */\n groupByRaw(sql: string, ...bindings: any[]): this {\n this.#baseQuery.groupByRaw(sql, ...bindings);\n return this;\n }\n\n /**\n * Add a `HAVING column operator value` clause (used with `GROUP BY`).\n * @returns `this` for chaining.\n */\n having(\n column: ColumnRef<TLocalSchema> | Knex.Raw,\n operator: string,\n value: any\n ): this {\n const col = this.#resolveColumnArg(column);\n this.#baseQuery.having(col as string, operator, value);\n return this;\n }\n\n /**\n * Add a raw `HAVING` expression.\n * @returns `this` for chaining.\n */\n havingRaw(sql: string, ...bindings: any[]): this {\n this.#baseQuery.havingRaw(sql, ...bindings);\n return this;\n }\n\n // =======================================================================\n // PAGINATION\n // =======================================================================\n\n /**\n * Limit the number of rows returned.\n * @param n - Maximum number of rows.\n * @returns `this` for chaining.\n */\n limit(n: number): this {\n this.#baseQuery.limit(n);\n return this;\n }\n\n /**\n * Skip the first `n` rows in the result set (for cursor/offset pagination).\n * @param n - Number of rows to skip.\n * @returns `this` for chaining.\n */\n offset(n: number): this {\n this.#baseQuery.offset(n);\n return this;\n }\n\n // =======================================================================\n // SELECT / DISTINCT\n // =======================================================================\n\n /**\n * Select specific columns instead of `*`. Each column reference is\n * resolved to its SQL column name through the schema.\n * @param columns - One or more column references or raw expressions.\n * @returns `this` for chaining.\n */\n select(...columns: (ColumnRef<TLocalSchema> | Knex.Raw)[]): this {\n const resolved = columns.map(c => this.#resolveColumnArg(c));\n this.#baseQuery.select(...(resolved as string[]));\n // Track the string-resolved columns (not Knex.Raw) for CTE column management\n this.#explicitSelects ??= [];\n for (const r of resolved) {\n if (typeof r === 'string') {\n this.#explicitSelects.push(r);\n }\n }\n return this;\n }\n\n /**\n * Add `DISTINCT` to the select clause. Duplicate rows are eliminated.\n * @param columns - One or more column references or raw expressions.\n * @returns `this` for chaining.\n */\n distinct(...columns: (ColumnRef<TLocalSchema> | Knex.Raw)[]): this {\n const resolved = columns.map(c => this.#resolveColumnArg(c));\n this.#baseQuery.distinct(...(resolved as string[]));\n return this;\n }\n\n // =======================================================================\n // AGGREGATES\n // =======================================================================\n\n /**\n * Add a `COUNT(*)` or `COUNT(column)` aggregate to the select list.\n * @param column - Optional column to count (defaults to `*`).\n * @returns `this` for chaining.\n */\n count(column?: ColumnRef<TLocalSchema> | Knex.Raw): this {\n if (column) {\n this.#baseQuery.count(this.#resolveColumnArg(column) as string);\n } else {\n this.#baseQuery.count();\n }\n return this;\n }\n\n /**\n * Add a `COUNT(DISTINCT column)` aggregate to the select list.\n * @param column - Optional column (defaults to `*`).\n * @returns `this` for chaining.\n */\n countDistinct(column?: ColumnRef<TLocalSchema> | Knex.Raw): this {\n if (column) {\n this.#baseQuery.countDistinct(\n this.#resolveColumnArg(column) as string\n );\n } else {\n this.#baseQuery.countDistinct();\n }\n return this;\n }\n\n /**\n * Add a `MIN(column)` aggregate.\n * @returns `this` for chaining.\n */\n min(column: ColumnRef<TLocalSchema> | Knex.Raw): this {\n this.#baseQuery.min(this.#resolveColumnArg(column) as string);\n return this;\n }\n\n /**\n * Add a `MAX(column)` aggregate.\n * @returns `this` for chaining.\n */\n max(column: ColumnRef<TLocalSchema> | Knex.Raw): this {\n this.#baseQuery.max(this.#resolveColumnArg(column) as string);\n return this;\n }\n\n /**\n * Add a `SUM(column)` aggregate.\n * @returns `this` for chaining.\n */\n sum(column: ColumnRef<TLocalSchema> | Knex.Raw): this {\n this.#baseQuery.sum(this.#resolveColumnArg(column) as string);\n return this;\n }\n\n /**\n * Add an `AVG(column)` aggregate.\n * @returns `this` for chaining.\n */\n avg(column: ColumnRef<TLocalSchema> | Knex.Raw): this {\n this.#baseQuery.avg(this.#resolveColumnArg(column) as string);\n return this;\n }\n\n // =======================================================================\n // WRITE OPERATIONS\n // =======================================================================\n\n /**\n * Insert a single row into the table and return the inserted record.\n *\n * Property keys are mapped to SQL column names via the schema's\n * `hasColumnName()` metadata before the `INSERT` is executed. The\n * returned row is mapped back to property names.\n *\n * @param data - The object to insert. Keys must be valid schema property names.\n * @returns The full inserted row (including database-generated fields).\n *\n * @example\n * ```ts\n * const user = await query(db, UserSchema).insert({ name: 'Alice', age: 30 });\n * // user.id is populated by the database DEFAULT / SERIAL\n * ```\n */\n async insert(data: InsertType<TLocalSchema>): Promise<TResult> {\n const mapped = this.#mapObjectToColumns(data as Record<string, any>);\n const [row] = await this.#knex(this.#tableName)\n .insert(mapped)\n .returning('*');\n return this.#mapRow(row) as TResult;\n }\n\n /**\n * Insert multiple rows in a single `INSERT` statement and return all\n * inserted records.\n *\n * @param data - Array of objects to insert.\n * @returns The full inserted rows in insertion order.\n */\n async insertMany(data: InsertType<TLocalSchema>[]): Promise<TResult[]> {\n const mapped = data.map(d =>\n this.#mapObjectToColumns(d as Record<string, any>)\n );\n const rows = await this.#knex(this.#tableName)\n .insert(mapped)\n .returning('*');\n return rows.map((row: any) => this.#mapRow(row) as TResult);\n }\n\n /**\n * Update all rows that match the current `WHERE` clause and return the\n * updated records.\n *\n * Only the keys present in `data` are updated (partial update). Property\n * keys are resolved to column names automatically.\n *\n * @param data - Partial schema object with fields to update.\n * @returns All rows that were updated.\n *\n * @example\n * ```ts\n * const updated = await query(db, UserSchema)\n * .where(t => t.id, userId)\n * .update({ name: 'Bob' });\n * ```\n */\n async update(data: Partial<InferType<TLocalSchema>>): Promise<TResult[]> {\n const mapped = this.#mapObjectToColumns(data as Record<string, any>);\n const rows = await this.#baseQuery.update(mapped).returning('*');\n return rows.map((row: any) => this.#mapRow(row) as TResult);\n }\n\n /**\n * Delete all rows that match the current `WHERE` clause.\n * @returns The number of rows deleted.\n *\n * @example\n * ```ts\n * const count = await query(db, UserSchema).where(t => t.id, id).delete();\n * ```\n */\n async delete(): Promise<number> {\n return this.#baseQuery.delete();\n }\n\n // =======================================================================\n // ESCAPE HATCH\n // =======================================================================\n\n /**\n * Escape hatch: apply any Knex method to the underlying base query.\n *\n * Use this when you need a Knex feature not exposed by this API (e.g.\n * `forUpdate()`, CTEs, `join()`, `union()`).\n *\n * @param fn - A callback that receives the raw `Knex.QueryBuilder` and\n * may mutate it in place.\n * @returns `this` for chaining.\n *\n * @example\n * ```ts\n * query(db, UserSchema).apply(qb => qb.forUpdate().noWait());\n * ```\n */\n apply(fn: (builder: Knex.QueryBuilder) => void): this {\n fn(this.#baseQuery);\n return this;\n }\n\n // =======================================================================\n // Transaction support\n // =======================================================================\n\n /**\n * Bind this query builder to a Knex transaction.\n *\n * Returns a **new** builder that runs all operations — SELECT, INSERT,\n * UPDATE, DELETE, and eager-loaded sub-queries — within the given\n * transaction. The original builder is left unchanged.\n *\n * Use this when you already have a transaction obtained from\n * `knex.transaction()` and want all operations performed by the returned\n * builder to participate in that transaction.\n *\n * @param trx - The Knex transaction obtained from `knex.transaction()`.\n * @returns A new {@link SchemaQueryBuilder} bound to the transaction.\n *\n * @example\n * ```ts\n * async function createUser(\n * data: InsertType<typeof UserSchema>,\n * trx: Knex.Transaction\n * ) {\n * return query(db, UserSchema).transacting(trx).insert(data);\n * }\n *\n * await db.transaction(async trx => {\n * const user = await createUser({ name: 'Alice' }, trx);\n * await query(db, PostSchema).transacting(trx).insert({ authorId: user.id, title: 'Hello' });\n * });\n * ```\n */\n transacting(\n trx: Knex.Transaction\n ): SchemaQueryBuilder<TLocalSchema, TResult> {\n const builder = new SchemaQueryBuilder<TLocalSchema, TResult>(\n trx as unknown as Knex,\n this.#localSchema,\n this.#baseQuery.clone().transacting(trx)\n );\n for (const spec of this.#specs) {\n builder.#specs.push({\n ...spec,\n foreignQuery: spec.foreignQuery.clone().transacting(trx)\n });\n }\n builder.#explicitSelects = this.#explicitSelects\n ? [...this.#explicitSelects]\n : null;\n return builder;\n }\n\n // =======================================================================\n // CTE-based eager loading query building (from knex-eager)\n // =======================================================================\n\n #buildQuery(): Knex.QueryBuilder {\n if (this.#specs.length === 0) {\n return this.#baseQuery;\n }\n\n const knex = this.#knex;\n const specs = this.#specs;\n\n // Collect all localColumns needed for CTE joins\n const requiredLocalColumns = [\n ...new Set(specs.map(s => s.localColumn))\n ];\n\n // If the caller used .select(...), some localColumns may have been\n // omitted. Clone the base query and ensure those columns are always\n // included in the CTE so the join conditions work at runtime.\n // Track which columns we added so they can be excluded from the\n // final SELECT (preserving the original column set the caller asked for).\n let cteQuery = this.#baseQuery;\n let extraColumns: string[] = [];\n\n if (this.#explicitSelects !== null) {\n const selectedSet = new Set(this.#explicitSelects);\n extraColumns = requiredLocalColumns.filter(\n col => !selectedSet.has(col)\n );\n if (extraColumns.length > 0) {\n cteQuery = this.#baseQuery.clone();\n for (const col of extraColumns) {\n cteQuery.column(col);\n }\n }\n }\n\n // Build the outer query that wraps the CTE.\n // When we added extra columns, select only the original columns + joined\n // aliases (instead of originalQuery.*) so the caller's column set is\n // preserved in the final result.\n const resultQuery = knex.queryBuilder().with('originalQuery', cteQuery);\n\n if (extraColumns.length > 0 && this.#explicitSelects !== null) {\n // Explicit column list: original user selections only\n for (const col of this.#explicitSelects) {\n resultQuery.select(\n knex.raw(':originalQuery:.:col: as :col:', {\n originalQuery: 'originalQuery',\n col\n })\n );\n }\n } else {\n resultQuery.select('originalQuery.*');\n }\n\n resultQuery.from(\n knex.raw(':originalQuery:', {\n originalQuery: 'originalQuery'\n })\n );\n\n for (let i = 0; i < specs.length; i++) {\n const spec = specs[i];\n const relationAlias = `eagerRelation${i}`;\n\n if (spec.type === 'one') {\n this.#buildJoinOne(resultQuery, spec, relationAlias);\n } else {\n this.#buildJoinMany(resultQuery, spec, relationAlias, i);\n }\n }\n\n return resultQuery;\n }\n\n #buildJoinOne(\n resultQuery: Knex.QueryBuilder,\n spec: ValidatedSpec & { type: 'one' },\n relationAlias: string\n ): void {\n const knex = this.#knex;\n const foreignTable = spec.foreignQuery;\n const foreignTableName = (foreignTable as any)._single?.table;\n\n if (!foreignTableName) {\n throw new Error(\n `Could not determine table name from foreignQuery for \"${spec.as}\". ` +\n 'Make sure foreignQuery is created via knex(\"tableName\").'\n );\n }\n\n resultQuery.select(\n knex.raw(':relationAlias:.:as:->0 as :as:', {\n relationAlias,\n as: spec.as\n })\n );\n\n const subquery = knex\n .from(foreignTable.as(foreignTableName))\n .select(\n knex.raw(':foreignTable:.:foreignColumn:', {\n foreignTable: foreignTableName,\n foreignColumn: spec.foreignColumn\n })\n )\n .select(\n knex.raw('jsonb_agg(:foreignTable:) as :as:', {\n foreignTable: foreignTableName,\n as: spec.as\n })\n )\n .groupByRaw(':foreignTable:.:foreignColumn:', {\n foreignTable: foreignTableName,\n foreignColumn: spec.foreignColumn\n })\n .as(relationAlias);\n\n const joinMethod = spec.required ? 'join' : 'leftJoin';\n resultQuery[joinMethod](subquery, function () {\n this.on(\n knex.raw(\n ':relationAlias:.:foreignColumn: = :originalQuery:.:localColumn:',\n {\n originalQuery: 'originalQuery',\n relationAlias,\n foreignColumn: spec.foreignColumn,\n localColumn: spec.localColumn\n }\n )\n );\n });\n }\n\n #buildJoinMany(\n resultQuery: Knex.QueryBuilder,\n spec: ValidatedSpec & { type: 'many' },\n relationAlias: string,\n i: number\n ): void {\n const knex = this.#knex;\n const filterName = `withFilter${i}`;\n\n const hasLimitOffset =\n (spec.limit !== null && spec.limit > 0) ||\n (spec.offset !== null && spec.offset > 0);\n\n const orderByColumn = spec.orderBy\n ? spec.orderBy.column\n : spec.foreignColumn;\n const orderByDirection = spec.orderBy ? spec.orderBy.direction : 'asc';\n\n if (hasLimitOffset) {\n resultQuery.with(\n filterName,\n knex\n .from(\n spec.foreignQuery\n .clone()\n .whereIn(\n spec.foreignColumn,\n knex\n .from(\n knex.raw(':originalQuery:', {\n originalQuery: 'originalQuery'\n })\n )\n .distinct(spec.localColumn)\n )\n .as(`__wf_inner_${i}`)\n )\n .select(`__wf_inner_${i}.*`)\n .select(\n knex.raw(\n `row_number() over(partition by :foreignColumn: order by :orderByColumn: ${orderByDirection}) as \"__rn__\"`,\n {\n foreignColumn: spec.foreignColumn,\n orderByColumn\n }\n )\n )\n );\n } else {\n resultQuery.with(\n filterName,\n spec.foreignQuery.clone().whereIn(\n spec.foreignColumn,\n knex\n .from(\n knex.raw(':originalQuery:', {\n originalQuery: 'originalQuery'\n })\n )\n .distinct(spec.localColumn)\n )\n );\n }\n\n const aggSubquery = knex.from(filterName);\n\n if (hasLimitOffset) {\n const hasLimit = spec.limit !== null && spec.limit > 0;\n const hasOffset = spec.offset !== null && spec.offset > 0;\n const effectiveOffset = spec.offset ?? 0;\n const effectiveLimit = effectiveOffset + (spec.limit ?? 0);\n\n const condition =\n hasLimit && hasOffset\n ? '\"__rn__\" > :offset and \"__rn__\" <= :limit'\n : hasLimit\n ? '\"__rn__\" <= :limit'\n : '\"__rn__\" > :offset';\n\n aggSubquery.whereRaw(condition, {\n limit: effectiveLimit,\n offset: effectiveOffset\n });\n\n aggSubquery.select(\n knex.raw(':foreignColumn:', {\n foreignColumn: spec.foreignColumn\n })\n );\n aggSubquery.select(\n knex.raw(\n \"coalesce(jsonb_agg(to_jsonb(:filterName:) - '__rn__' order by \\\"__rn__\\\"), '[]'::jsonb) as :as:\",\n { filterName, as: spec.as }\n )\n );\n } else {\n aggSubquery.select(\n knex.raw(':foreignColumn:', {\n foreignColumn: spec.foreignColumn\n })\n );\n\n const orderClause = spec.orderBy\n ? `jsonb_agg(:filterName: order by :filterName:.:orderByColumn: ${orderByDirection})`\n : 'jsonb_agg(:filterName:)';\n\n aggSubquery.select(\n knex.raw(\n `coalesce(${orderClause}, '[]'::jsonb) as :as:`,\n spec.orderBy\n ? { filterName, orderByColumn, as: spec.as }\n : { filterName, as: spec.as }\n )\n );\n }\n\n aggSubquery.groupByRaw(':foreignColumn:', {\n foreignColumn: spec.foreignColumn\n });\n\n const subquery = aggSubquery.as(relationAlias);\n\n resultQuery.select(\n knex.raw(\"coalesce(:relationAlias:.:as:, '[]'::jsonb) as :as:\", {\n relationAlias,\n as: spec.as\n })\n );\n\n resultQuery.leftJoin(subquery, function () {\n this.on(\n knex.raw(\n ':relationAlias:.:foreignColumn: = :originalQuery:.:localColumn:',\n {\n relationAlias,\n foreignColumn: spec.foreignColumn,\n originalQuery: 'originalQuery',\n localColumn: spec.localColumn\n }\n )\n );\n });\n }\n\n // =======================================================================\n // Result mapping\n // =======================================================================\n\n /**\n * Map a SQL result row (column names) back to schema property names.\n * Also handles joined fields (which are already named by `as`).\n */\n #mapRow(row: Record<string, any>): Record<string, any> {\n if (!row) return row;\n\n const { colToProp } = buildColumnMap(this.#localSchema);\n const result: Record<string, any> = {};\n\n // Map known columns back to property names\n for (const [colName, value] of Object.entries(row)) {\n const propName = colToProp.get(colName);\n if (propName) {\n result[propName] = value;\n } else {\n // Unknown column (e.g., joined field, raw expression) — pass through\n result[colName] = value;\n }\n }\n\n return result;\n }\n\n /**\n * Clean a row that has eager-loaded relations (apply mappers, then map columns).\n */\n #cleanAndMapRow(row: Record<string, any>): Record<string, any> {\n const oneSpecs = this.#specs.filter(\n (s): s is ValidatedSpec & { type: 'one' } => s.type === 'one'\n );\n const manySpecs = this.#specs.filter(\n (s): s is ValidatedSpec & { type: 'many' } => s.type === 'many'\n );\n const cleaned = clearRow(row, oneSpecs, manySpecs);\n return this.#mapRow(cleaned);\n }\n\n // =======================================================================\n // Column mapping helpers\n // =======================================================================\n\n /**\n * Map a schema-shaped object (property keys) to a SQL object (column names).\n * Used for INSERT / UPDATE.\n */\n #mapObjectToColumns(obj: Record<string, any>): Record<string, any> {\n const { propToCol } = buildColumnMap(this.#localSchema);\n const result: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const colName = propToCol.get(key);\n if (colName) {\n result[colName] = value;\n } else {\n // Unknown key — pass through (could be a raw column)\n result[key] = value;\n }\n }\n\n return result;\n }\n\n /**\n * Map a Record<propertyKey, value> to Record<columnName, value>.\n * Used for `.where({ name: 'John' })` style calls.\n */\n #mapRecordToColumns(record: Record<string, any>): Record<string, any> {\n return this.#mapObjectToColumns(record);\n }\n\n /**\n * Resolve a column argument that could be a ColumnRef, Knex.Raw, or callback.\n * Returns the resolved string or passes through Knex.Raw.\n */\n #resolveColumnArg(col: any): string | Knex.Raw {\n if (typeof col === 'string') {\n return this.#resolveColumn(col, 'column');\n }\n if (typeof col === 'function') {\n // Property descriptor accessor\n return this.#resolveColumn(col, 'column');\n }\n // Knex.Raw — pass through\n return col;\n }\n\n /**\n * Detect if a function is a property descriptor accessor (takes tree, returns descriptor)\n * vs a knex sub-builder callback (takes builder, returns void).\n *\n * Heuristic: property descriptor accessors are arrow functions that access\n * tree properties. We cannot distinguish at runtime, so we try the accessor\n * and fallback to callback if it fails.\n *\n * For safety, we check if the function parameter count can help:\n * - Knex callbacks typically have 1 parameter named `builder` or `qb`\n * - Property accessors typically have 1 parameter named `t` or similar\n *\n * Since both are `(arg) => result`, we use a try/catch approach:\n * attempt to resolve as column accessor first.\n */\n #isColumnAccessor(fn: Function): boolean {\n // Try to invoke the accessor with the property descriptor tree\n try {\n const tree = ObjectSchemaBuilder.getPropertiesFor(\n this.#localSchema as any\n );\n const result = fn(tree);\n // If it returns a valid property descriptor, it's an accessor\n if (\n result &&\n typeof result === 'object' &&\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR in result\n ) {\n return true;\n }\n } catch {\n // Not an accessor\n }\n return false;\n }\n\n // =======================================================================\n // Execution\n // =======================================================================\n\n /**\n * Return the raw SQL string that would be executed, for debugging.\n * Does not execute the query against the database.\n */\n toQuery(): string {\n return this.#buildQuery().toQuery();\n }\n\n /**\n * Returns the underlying Knex query builder. Useful when passing this\n * query as a `foreignQuery` in `.joinOne()` / `.joinMany()`, or any context\n * that expects a raw `Knex.QueryBuilder`.\n */\n toKnexQuery(): Knex.QueryBuilder {\n return this.#buildQuery();\n }\n\n /**\n * Alias for {@link toQuery} — returns the raw SQL string.\n */\n toString(): string {\n return this.#buildQuery().toString();\n }\n\n /**\n * Execute the query and return all matching rows, mapped back to schema\n * property names.\n *\n * @returns A promise that resolves to an array of result objects typed as\n * `TResult[]`.\n *\n * @example\n * ```ts\n * const users = await query(db, UserSchema).execute();\n * ```\n */\n async execute(): Promise<TResult[]> {\n const query = this.#buildQuery();\n const rows = await query;\n\n if (!rows) return [];\n if (!Array.isArray(rows))\n return [this.#cleanAndMapRow(rows)] as TResult[];\n\n return rows.map((row: any) => this.#cleanAndMapRow(row)) as TResult[];\n }\n\n /**\n * Execute the query and return only the first row, or `undefined` if no\n * rows match.\n *\n * @example\n * ```ts\n * const user = await query(db, UserSchema).where(t => t.id, id).first();\n * if (user) { /* ... *\\/ }\n * ```\n */\n async first(): Promise<TResult | undefined> {\n const query = this.#buildQuery().first();\n const row = await query;\n\n if (!row) return undefined;\n return this.#cleanAndMapRow(row) as TResult;\n }\n\n /**\n * Thenable implementation — allows the builder to be awaited directly\n * without calling {@link execute} explicitly.\n *\n * @example\n * ```ts\n * const users = await query(db, UserSchema).where(t => t.name, 'Alice');\n * // Equivalent to: await query(db, UserSchema).where(...).execute()\n * ```\n */\n // biome-ignore lint/suspicious/noThenProperty: intentional thenable for `await builder` support\n then<TReturn1 = TResult[], TReturn2 = never>(\n onfulfilled?:\n | ((value: TResult[]) => TReturn1 | PromiseLike<TReturn1>)\n | null,\n onrejected?: ((reason: any) => TReturn2 | PromiseLike<TReturn2>) | null\n ): Promise<TReturn1 | TReturn2> {\n return this.execute().then(onfulfilled, onrejected);\n }\n}\n\n// ---------------------------------------------------------------------------\n// query() — main entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Create a typed {@link SchemaQueryBuilder} for the table described by `schema`.\n *\n * The schema must have a table name configured via `.hasTableName()`.\n * Column name mappings set via `.hasColumnName()` are applied automatically\n * to all query methods. The returned builder is thenable — you can `await` it\n * directly to execute the query and get `TResult[]`.\n *\n * @param knex - A configured Knex instance.\n * @param schema - The `ObjectSchemaBuilder` describing the table.\n * @returns A new {@link SchemaQueryBuilder} ready for chaining.\n *\n * @example\n * ```ts\n * import knex from 'knex';\n * import { query, object, string, number } from '@cleverbrush/knex-schema';\n *\n * const UserSchema = object({ id: number(), name: string() }).hasTableName('users');\n * const db = knex({ client: 'pg', connection: process.env.DB_URL });\n *\n * const users = await query(db, UserSchema).where(t => t.name, 'like', 'A%');\n * ```\n */\nexport function query<\n TLocalSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>\n>(\n knex: Knex,\n schema: TLocalSchema\n): SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>>;\n\n/**\n * Create a typed {@link SchemaQueryBuilder} from an existing Knex query builder.\n *\n * Use this overload when you need to supply a pre-configured base query —\n * for example one that already has a sub-query, CTE, or a schema scope applied.\n *\n * @param knex - A configured Knex instance.\n * @param schema - The `ObjectSchemaBuilder` describing the table.\n * @param baseQuery - An existing `Knex.QueryBuilder` to use as the base.\n * @returns A new {@link SchemaQueryBuilder} wrapping `baseQuery`.\n *\n * @example\n * ```ts\n * // Use a scoped base query (e.g. soft-delete filter applied globally)\n * const base = db('users').where('deleted_at', null);\n * const activeUsers = await query(db, UserSchema, base).where(t => t.age, '>', 18);\n * ```\n */\nexport function query<\n TLocalSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>\n>(\n knex: Knex,\n schema: TLocalSchema,\n baseQuery: Knex.QueryBuilder\n): SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>>;\n\nexport function query<\n TLocalSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>\n>(\n knex: Knex,\n schema: TLocalSchema,\n baseQuery?: Knex.QueryBuilder\n): SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>> {\n return new SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>>(\n knex,\n schema,\n baseQuery\n );\n}\n\n// ---------------------------------------------------------------------------\n// createQuery() — knex-bound factory\n// ---------------------------------------------------------------------------\n\n/** Bound query function returned by {@link createQuery}. */\nexport interface BoundQuery {\n <\n TLocalSchema extends ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >\n >(\n schema: TLocalSchema\n ): SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>>;\n <\n TLocalSchema extends ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >\n >(\n schema: TLocalSchema,\n baseQuery: Knex.QueryBuilder\n ): SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>>;\n /**\n * Return a version of this bound factory whose queries all run within the\n * given Knex transaction. Equivalent to calling `.transacting(trx)` on\n * each individual builder, but more convenient when every query in a block\n * must share the same transaction.\n *\n * @example\n * ```ts\n * const db = createQuery(knex);\n *\n * await knex.transaction(async trx => {\n * const dbTrx = db.withTransaction(trx);\n * const user = await dbTrx(UserSchema).insert({ name: 'Alice' });\n * await dbTrx(PostSchema).insert({ authorId: user.id, title: 'Hello' });\n * });\n * ```\n */\n withTransaction(trx: Knex.Transaction): BoundQuery;\n /**\n * Start a Knex transaction and run `callback` inside it, passing a\n * transaction-bound `BoundQuery` factory as the argument. The transaction\n * is committed when the callback resolves and rolled back if it rejects.\n *\n * This is the callback-style counterpart to {@link withTransaction} — you\n * don't need to obtain a `Knex.Transaction` object yourself.\n *\n * @param callback - An async function that receives a transaction-bound\n * `BoundQuery` and returns a value. The returned value is forwarded as\n * the resolved value of the outer `Promise`.\n * @returns A `Promise` that resolves with the value returned by `callback`.\n *\n * @example\n * ```ts\n * const db = createQuery(knex);\n *\n * const user = await db.transaction(async dbTrx => {\n * const newUser = await dbTrx(UserSchema).insert({ name: 'Alice' });\n * await dbTrx(PostSchema).insert({ authorId: newUser.id, title: 'Hello' });\n * return newUser;\n * });\n * ```\n */\n transaction<T>(callback: (db: BoundQuery) => Promise<T>): Promise<T>;\n}\n\n/**\n * Bind a Knex instance once and get back a `query(schema)` function that\n * doesn't require repeating the knex argument on every call.\n *\n * @param knex - A configured Knex instance.\n * @returns A bound query factory: `(schema, baseQuery?) => SchemaQueryBuilder`.\n *\n * @example\n * ```ts\n * import Knex from 'knex';\n * import { createQuery } from '@cleverbrush/knex-schema';\n *\n * const knex = Knex({ client: 'pg', connection: process.env.DB_URL });\n * const query = createQuery(knex);\n *\n * // No knex argument needed from here on\n * const users = await query(UserSchema).where(t => t.role, '=', 'admin');\n * const post = await query(PostSchema).where(t => t.id, '=', 42).first();\n *\n * // Optional base query (e.g. soft-delete scope applied globally)\n * const active = query(UserSchema, knex('users').where('deleted_at', null));\n * ```\n */\nexport function createQuery(knexInstance: Knex): BoundQuery {\n function boundQuery<\n TLocalSchema extends ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >\n >(\n schema: TLocalSchema,\n baseQuery?: Knex.QueryBuilder\n ): SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>> {\n return baseQuery\n ? query(knexInstance, schema, baseQuery)\n : query(knexInstance, schema);\n }\n\n (boundQuery as BoundQuery).withTransaction = (\n trx: Knex.Transaction\n ): BoundQuery => createQuery(trx as unknown as Knex);\n\n (boundQuery as BoundQuery).transaction = <T>(\n callback: (db: BoundQuery) => Promise<T>\n ): Promise<T> =>\n knexInstance.transaction(trx =>\n callback(createQuery(trx as unknown as Knex))\n );\n\n return boundQuery as BoundQuery;\n}\n","// @cleverbrush/knex-schema — Validation of join specs\n\nimport type { ObjectSchemaBuilder } from '@cleverbrush/schema';\nimport type { Knex } from 'knex';\nimport { resolveColumnRef } from './columns.js';\nimport { getTableName } from './extension.js';\nimport { MAPPERS } from './mappers.js';\nimport type {\n JoinManySpec,\n JoinOneSpec,\n ValidatedJoinManySpec,\n ValidatedJoinOneSpec\n} from './types.js';\n\n/**\n * Resolve foreignQuery: use the provided one, or auto-derive from\n * the foreign schema's tableName extension.\n * Also normalizes SchemaQueryBuilder instances to raw Knex.QueryBuilder\n * by calling `.toKnexQuery()` if available.\n */\nfunction resolveForeignQuery(\n spec: {\n foreignQuery?: Knex.QueryBuilder | { toKnexQuery(): Knex.QueryBuilder };\n foreignSchema: ObjectSchemaBuilder<any, any, any, any, any, any, any>;\n },\n knex: Knex\n): Knex.QueryBuilder {\n if (spec.foreignQuery) {\n if ('toKnexQuery' in spec.foreignQuery) {\n return spec.foreignQuery.toKnexQuery();\n }\n return spec.foreignQuery as Knex.QueryBuilder;\n }\n\n const tableName = getTableName(spec.foreignSchema);\n return knex(tableName);\n}\n\nexport function validateJoinOne(\n spec: JoinOneSpec<any, any, any, any>,\n localSchema: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n knex: Knex\n): ValidatedJoinOneSpec {\n const localColumn = resolveColumnRef(\n spec.localColumn,\n localSchema,\n 'localColumn'\n );\n const foreignColumn = resolveColumnRef(\n spec.foreignColumn,\n spec.foreignSchema,\n 'foreignColumn'\n );\n if (typeof spec.as !== 'string' || !spec.as) {\n throw new Error('as must be a non-empty string');\n }\n\n const foreignQuery = resolveForeignQuery(spec, knex);\n const required = spec.required !== false;\n\n if (spec.mappers !== undefined) {\n validateMappers(spec.mappers);\n }\n\n return {\n localColumn,\n foreignColumn,\n as: spec.as,\n required,\n foreignQuery,\n mappers: spec.mappers as\n | Record<string, ((value: any) => any) | string>\n | undefined\n };\n}\n\nexport function validateJoinMany(\n spec: JoinManySpec<any, any, any>,\n localSchema: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n knex: Knex\n): ValidatedJoinManySpec {\n const localColumn = resolveColumnRef(\n spec.localColumn,\n localSchema,\n 'localColumn'\n );\n const foreignColumn = resolveColumnRef(\n spec.foreignColumn,\n spec.foreignSchema,\n 'foreignColumn'\n );\n if (typeof spec.as !== 'string' || !spec.as) {\n throw new Error('as must be a non-empty string');\n }\n\n const foreignQuery = resolveForeignQuery(spec, knex);\n\n if (spec.mappers !== undefined) {\n validateMappers(spec.mappers);\n }\n\n const limit =\n Number.isFinite(spec.limit) && (spec.limit as number) > 0\n ? (spec.limit as number)\n : null;\n const offset =\n Number.isFinite(spec.offset) && (spec.offset as number) > 0\n ? (spec.offset as number)\n : null;\n const orderBy = spec.orderBy\n ? {\n column: resolveColumnRef(\n spec.orderBy.column,\n spec.foreignSchema,\n 'orderBy.column'\n ),\n direction: spec.orderBy.direction ?? ('asc' as const)\n }\n : null;\n\n return {\n localColumn,\n foreignColumn,\n as: spec.as,\n foreignQuery,\n limit,\n offset,\n orderBy,\n mappers: spec.mappers as\n | Record<string, ((value: any) => any) | string>\n | undefined\n };\n}\n\nfunction validateMappers(mappers: Record<string, unknown>): void {\n if (typeof mappers !== 'object' || mappers === null) {\n throw new Error('mappers must be an object');\n }\n for (const key of Object.keys(mappers)) {\n const m = mappers[key];\n if (typeof m === 'string') {\n if (typeof MAPPERS[m] !== 'function') {\n throw new Error(\n `mapper for \"${key}\": unknown built-in mapper name \"${m}\"`\n );\n }\n } else if (typeof m !== 'function') {\n throw new Error(\n `mapper for \"${key}\" must be a function or a built-in mapper name`\n );\n }\n }\n}\n\nexport function validateUniqueFieldNames(specs: Array<{ as: string }>): void {\n const seen = new Set<string>();\n for (const spec of specs) {\n if (seen.has(spec.as)) {\n throw new Error(`duplicate field name: ${spec.as}`);\n }\n seen.add(spec.as);\n }\n}\n"],"mappings":"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","names":["ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","defineExtension","withExtensions","hasColumnName","name","hasTableName","dbExtension","extended","string","number","boolean","date","object","array","union","func","any","getColumnName","schema","propertyKey","col","getTableName","table","columnMapCache","buildColumnMap","schema","cached","properties","propToCol","colToProp","propKey","propSchema","colName","getColumnName","result","resolveColumnRef","ref","label","tree","ObjectSchemaBuilder","descriptor","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","inner","propName","propDescriptor","resolvePropertyKey","MAPPERS","value","mapValue","mapper","mapObject","obj","mappers","result","key","clearRow","row","oneSpecs","manySpecs","spec","fieldName","k","ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","resolveForeignQuery","spec","knex","tableName","getTableName","validateJoinOne","localSchema","localColumn","resolveColumnRef","foreignColumn","foreignQuery","required","validateMappers","validateJoinMany","limit","offset","orderBy","mappers","key","m","MAPPERS","validateUniqueFieldNames","specs","seen","SchemaQueryBuilder","_SchemaQueryBuilder","#knex","#baseQuery","#localSchema","#specs","#tableName","#explicitSelects","knex","localSchema","baseQuery","getTableName","#resolveColumn","ref","label","resolveColumnRef","spec","validated","validateJoinOne","validateUniqueFieldNames","validateJoinMany","columnOrRaw","args","#isColumnAccessor","mapped","#mapRecordToColumns","col","#resolveColumnArg","column","values","range","value","sql","bindings","callback","direction","columns","resolved","c","operator","n","r","data","#mapObjectToColumns","row","#mapRow","d","fn","trx","builder","#buildQuery","specs","requiredLocalColumns","cteQuery","extraColumns","selectedSet","resultQuery","i","relationAlias","#buildJoinOne","#buildJoinMany","foreignTable","foreignTableName","subquery","joinMethod","filterName","hasLimitOffset","orderByColumn","orderByDirection","aggSubquery","hasLimit","hasOffset","effectiveOffset","effectiveLimit","condition","orderClause","colToProp","buildColumnMap","result","colName","propName","#cleanAndMapRow","oneSpecs","s","manySpecs","cleaned","clearRow","obj","propToCol","key","record","tree","ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","rows","onfulfilled","onrejected","query","schema","createQuery","knexInstance","boundQuery"]}
1
+ {"version":3,"sources":["../src/columns.ts","../src/extension.ts","../src/mappers.ts","../src/SchemaQueryBuilder.ts","../src/validate.ts"],"sourcesContent":["// @cleverbrush/knex-schema — Column resolution and mapping\n\nimport type { SchemaBuilder } from '@cleverbrush/schema';\nimport {\n ObjectSchemaBuilder,\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n} from '@cleverbrush/schema';\nimport { getColumnName } from './extension.js';\nimport type { ColumnRef } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Column map cache (schema → { propToCol, colToProp })\n// ---------------------------------------------------------------------------\n\ninterface ColumnMapResult {\n propToCol: Map<string, string>;\n colToProp: Map<string, string>;\n}\n\nconst columnMapCache = new WeakMap<object, ColumnMapResult>();\n\n/**\n * Build a bidirectional column map from an ObjectSchemaBuilder's properties.\n * Uses `getExtension('columnName')` per property, falling back to the property key.\n * Result is cached per schema instance via WeakMap.\n */\nexport function buildColumnMap(\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>\n): ColumnMapResult {\n const cached = columnMapCache.get(schema);\n if (cached) return cached;\n\n const introspected = schema.introspect() as any;\n const properties: Record<\n string,\n SchemaBuilder<any, any, any>\n > = introspected.properties ?? {};\n\n const propToCol = new Map<string, string>();\n const colToProp = new Map<string, string>();\n\n for (const propKey of Object.keys(properties)) {\n const propSchema = properties[propKey];\n const colName = getColumnName(propSchema, propKey);\n\n propToCol.set(propKey, colName);\n colToProp.set(colName, propKey);\n }\n\n const result: ColumnMapResult = { propToCol, colToProp };\n columnMapCache.set(schema, result);\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Resolve a ColumnRef (string | accessor) to a SQL column name\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a ColumnRef to a plain SQL column name.\n *\n * - String refs are treated as **property keys** and translated to column\n * names via the column map.\n * - Function refs (property accessor) are resolved via PropertyDescriptorTree,\n * then translated to column names.\n */\nexport function resolveColumnRef(\n ref: ColumnRef<any>,\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n label: string\n): string {\n if (typeof ref === 'string') {\n if (!ref) throw new Error(`${label} must be a non-empty string`);\n const { propToCol } = buildColumnMap(schema);\n const col = propToCol.get(ref);\n // If the string is a known property key, return its column name;\n // otherwise assume it's already a raw column name and pass through.\n return col ?? ref;\n }\n\n if (typeof ref === 'function') {\n const tree = ObjectSchemaBuilder.getPropertiesFor(schema as any);\n const descriptor = ref(tree as any);\n\n if (\n !descriptor ||\n typeof descriptor !== 'object' ||\n !(SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR in descriptor)\n ) {\n throw new Error(\n `${label} accessor must return a valid property descriptor`\n );\n }\n\n const inner = (descriptor as any)[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR];\n const introspected = schema.introspect() as any;\n const properties = introspected.properties ?? {};\n\n for (const propName of Object.keys(properties)) {\n const propDescriptor = (tree as any)[propName];\n if (\n propDescriptor &&\n (propDescriptor as any)[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR] ===\n inner\n ) {\n // Found the matching property — resolve to column name\n const { propToCol } = buildColumnMap(schema);\n return propToCol.get(propName) ?? propName;\n }\n }\n\n throw new Error(\n `${label} accessor did not match any property in the schema`\n );\n }\n\n throw new Error(\n `${label} must be a string or a property descriptor accessor function`\n );\n}\n\n/**\n * Resolve a ColumnRef to the **property key** (not the column name).\n * Used for result mapping.\n */\nexport function resolvePropertyKey(\n ref: ColumnRef<any>,\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n label: string\n): string {\n if (typeof ref === 'string') {\n if (!ref) throw new Error(`${label} must be a non-empty string`);\n return ref;\n }\n\n if (typeof ref === 'function') {\n const tree = ObjectSchemaBuilder.getPropertiesFor(schema as any);\n const descriptor = ref(tree as any);\n\n if (\n !descriptor ||\n typeof descriptor !== 'object' ||\n !(SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR in descriptor)\n ) {\n throw new Error(\n `${label} accessor must return a valid property descriptor`\n );\n }\n\n const inner = (descriptor as any)[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR];\n const introspected = schema.introspect() as any;\n const properties = introspected.properties ?? {};\n\n for (const propName of Object.keys(properties)) {\n const propDescriptor = (tree as any)[propName];\n if (\n propDescriptor &&\n (propDescriptor as any)[SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR] ===\n inner\n ) {\n return propName;\n }\n }\n\n throw new Error(\n `${label} accessor did not match any property in the schema`\n );\n }\n\n throw new Error(\n `${label} must be a string or a property descriptor accessor function`\n );\n}\n","// @cleverbrush/knex-schema — Schema extension: hasColumnName / hasTableName\n\nimport type {\n AnySchemaBuilder,\n ArraySchemaBuilder,\n BooleanSchemaBuilder,\n DateSchemaBuilder,\n FunctionSchemaBuilder,\n GenericSchemaBuilder,\n NumberSchemaBuilder,\n ObjectSchemaBuilder,\n SchemaBuilder,\n StringSchemaBuilder,\n UnionSchemaBuilder\n} from '@cleverbrush/schema';\nimport {\n arrayExtensions,\n defineExtension,\n numberExtensions,\n stringExtensions,\n withExtensions\n} from '@cleverbrush/schema';\n\n// ---------------------------------------------------------------------------\n// Shared implementations\n// ---------------------------------------------------------------------------\n\n/**\n * Stores the SQL column name for a schema property using the schema extension\n * system. Consumed by {@link getColumnName} and the query builder's column\n * resolution logic.\n */\nfunction hasColumnName(this: SchemaBuilder<any, any, any>, name: string) {\n return this.withExtension('columnName', name);\n}\n\n/**\n * Stores the SQL table name for an `ObjectSchemaBuilder` using the schema\n * extension system. Required for {@link query} to build queries — throws at\n * query creation time if not set.\n */\nfunction hasTableName(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n name: string\n) {\n return this.withExtension('tableName', name);\n}\n\n// ---------------------------------------------------------------------------\n// Extension definition\n// ---------------------------------------------------------------------------\n\n/**\n * Schema extension that adds database-mapping metadata to schema builders.\n *\n * Import the typed factory functions (`string`, `number`, `object`, etc.) from\n * this package instead of from `@cleverbrush/schema` to gain access to the\n * `.hasColumnName()` and `.hasTableName()` methods.\n *\n * @example\n * ```ts\n * import { object, string, number } from '@cleverbrush/knex-schema';\n *\n * const UserSchema = object({\n * id: number(),\n * firstName: string().hasColumnName('first_name'),\n * lastName: string().hasColumnName('last_name'),\n * createdAt: date().hasColumnName('created_at'),\n * }).hasTableName('users');\n * ```\n */\nexport const dbExtension = defineExtension({\n string: {\n /**\n * Override the SQL column name for this property.\n *\n * By default the property key is used as the column name. Call\n * `.hasColumnName('sql_col')` when the database column differs from the\n * schema property name (e.g. camelCase property → snake_case column).\n *\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: StringSchemaBuilder<any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n number: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: NumberSchemaBuilder<any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n boolean: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: BooleanSchemaBuilder<any, any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n date: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: DateSchemaBuilder<any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n any: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: AnySchemaBuilder<any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n func: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: FunctionSchemaBuilder<any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n array: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: ArraySchemaBuilder<any, any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n union: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: UnionSchemaBuilder<any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n generic: {\n /**\n * Override the SQL column name for this property.\n * @param name - The SQL column name.\n */\n hasColumnName(\n this: GenericSchemaBuilder<any, any, any, any, any, any>,\n name: string\n ) {\n return hasColumnName.call(this, name);\n }\n },\n object: {\n /**\n * Set the SQL table name for this object schema.\n *\n * Required before creating a {@link query} builder — throws at\n * query creation time when not set.\n *\n * @param name - The SQL table name (e.g. `'users'`).\n */\n hasTableName(\n this: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n name: string\n ) {\n return hasTableName.call(this, name);\n }\n }\n});\n\n// ---------------------------------------------------------------------------\n// Extended factory functions\n// ---------------------------------------------------------------------------\n\nconst extended = withExtensions(\n stringExtensions,\n numberExtensions,\n arrayExtensions,\n dbExtension\n);\n\nexport const string = extended.string;\nexport const number = extended.number;\nexport const boolean = extended.boolean;\nexport const date = extended.date;\nexport const object = extended.object;\nexport const array = extended.array;\nexport const union = extended.union;\nexport const func = extended.func;\nexport const any = extended.any;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get the SQL column name for a schema property.\n * Returns the `hasColumnName()` value if set, otherwise falls back to `propertyKey`.\n */\nexport function getColumnName(\n schema: SchemaBuilder<any, any, any>,\n propertyKey: string\n): string {\n const col = schema.getExtension('columnName');\n return typeof col === 'string' ? col : propertyKey;\n}\n\n/**\n * Get the SQL table name from an ObjectSchemaBuilder.\n * Throws if `hasTableName()` was never called.\n */\nexport function getTableName(\n schema: ObjectSchemaBuilder<any, any, any, any, any, any, any>\n): string {\n const table = schema.getExtension('tableName');\n if (typeof table !== 'string') {\n throw new Error(\n 'Schema does not have a table name. Use .hasTableName(\"table_name\") to set one.'\n );\n }\n return table;\n}\n","// @cleverbrush/knex-schema — Post-load value transformers (from knex-eager)\n\nimport type { ValidatedSpec } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Built-in named mappers\n// ---------------------------------------------------------------------------\n\n/**\n * Built-in named value mapper functions.\n *\n * Each entry maps a string key to a transformation function. Pass the key as\n * the `mapper` argument to {@link mapValue} instead of a custom function.\n *\n * Currently available:\n * - `date_from_json` — converts a JSON date string (`string | null`) to a\n * JavaScript `Date` object (or passes through falsy values unchanged).\n */\nexport const MAPPERS: Record<string, (value: any) => any> = {\n date_from_json: (value: any) => {\n if (!value) return value;\n return new Date(Date.parse(value));\n }\n};\n\n// ---------------------------------------------------------------------------\n// mapValue — apply a mapper (function or built-in name) to a value\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a single mapper to a value.\n *\n * @param mapper - Either a transformation function `(v: any) => any`, or a\n * string key referencing one of the {@link MAPPERS} built-ins (e.g.\n * `'date_from_json'`).\n * @param value - The raw value to transform.\n * @returns The transformed value.\n *\n * @throws If `mapper` is a string that does not exist in {@link MAPPERS}.\n */\nexport function mapValue(mapper: ((v: any) => any) | string, value: any): any {\n if (typeof mapper === 'string') {\n if (typeof MAPPERS[mapper] !== 'function') {\n throw new Error(`unknown mapper \"${mapper}\"`);\n }\n return MAPPERS[mapper](value);\n }\n if (typeof mapper === 'function') {\n return mapper(value);\n }\n throw new Error(\"couldn't map value\");\n}\n\n// ---------------------------------------------------------------------------\n// mapObject — apply mappers to all matching keys of an object\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a map of per-key transformations to an object, returning a new object\n * with the transformed values.\n *\n * Keys not present in `mappers` are copied through unchanged. Keys present in\n * `mappers` are passed through {@link mapValue}.\n *\n * @param obj - The source object (e.g. a raw database row).\n * @param mappers - A `Record` mapping property keys to mapper functions or\n * {@link MAPPERS} built-in names.\n * @returns A shallow copy of `obj` with the specified values transformed.\n *\n * @throws If `obj` is `null` or a non-object, it is returned as-is.\n */\nexport function mapObject<T extends Record<string, any>>(\n obj: T,\n mappers: Record<string, ((v: any) => any) | string>\n): T {\n if (!mappers) throw new Error('mappers should be an object');\n if (!obj || typeof obj !== 'object' || obj === null) return obj;\n\n const result = {} as Record<string, any>;\n for (const key of Object.keys(obj)) {\n result[key] = mappers[key]\n ? mapValue(mappers[key], obj[key])\n : obj[key];\n }\n return result as T;\n}\n\n// ---------------------------------------------------------------------------\n// clearRow — apply mappers to joined data in a result row\n// ---------------------------------------------------------------------------\n\n/**\n * Apply value mappers to the joined fields of a result row in place.\n *\n * After an eager-loaded query resolves, joined objects and arrays may contain\n * raw database values that need transformation (e.g. date strings → `Date`).\n * This function iterates over the one-to-one and one-to-many specs, applies\n * the `mappers` defined on each spec to the nested data, and returns the\n * mutated row.\n *\n * This is an internal helper used by {@link SchemaQueryBuilder}'s result\n * mapping pipeline. Exported to allow custom post-processing if needed.\n *\n * @param row - The raw result row (mutated in place).\n * @param oneSpecs - Validated one-to-one join specs with optional `mappers`.\n * @param manySpecs - Validated one-to-many join specs with optional `mappers`.\n * @returns The mutated `row` object.\n */\nexport function clearRow(\n row: Record<string, any>,\n oneSpecs: Array<ValidatedSpec & { type: 'one' }>,\n manySpecs: Array<ValidatedSpec & { type: 'many' }>\n): Record<string, any> {\n for (const spec of oneSpecs) {\n const fieldName = spec.as;\n if (\n spec.mappers &&\n typeof row[fieldName] !== 'undefined' &&\n row[fieldName] !== null\n ) {\n row[fieldName] = mapObject(row[fieldName], spec.mappers);\n }\n }\n\n for (const spec of manySpecs) {\n const fieldName = spec.as;\n if (Array.isArray(row[fieldName])) {\n for (let k = 0; k < row[fieldName].length; k++) {\n if (row[fieldName][k]) {\n if (spec.mappers) {\n row[fieldName][k] = mapObject(\n row[fieldName][k],\n spec.mappers\n );\n }\n }\n }\n }\n }\n\n return row;\n}\n","// @cleverbrush/knex-schema — SchemaQueryBuilder\n\nimport type { InferType } from '@cleverbrush/schema';\nimport {\n ObjectSchemaBuilder,\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR\n} from '@cleverbrush/schema';\nimport type { Knex } from 'knex';\nimport { buildColumnMap, resolveColumnRef } from './columns.js';\nimport { getTableName } from './extension.js';\nimport { clearRow } from './mappers.js';\nimport type {\n ColumnRef,\n InsertType,\n JoinManySpec,\n JoinOneSpec,\n ValidatedSpec,\n WithJoinedMany,\n WithJoinedOne\n} from './types.js';\nimport {\n validateJoinMany,\n validateJoinOne,\n validateUniqueFieldNames\n} from './validate.js';\n\n// ---------------------------------------------------------------------------\n// SchemaQueryBuilder\n// ---------------------------------------------------------------------------\n\n/**\n * Type-safe, schema-driven query builder for Knex.\n *\n * `SchemaQueryBuilder` wraps a Knex.QueryBuilder and adds:\n * - **Type-safe column references** — pass a property accessor (`t => t.name`)\n * or a string property name; both are resolved to the correct SQL column\n * through the schema's `hasColumnName()` metadata automatically.\n * - **Eager loading without N+1** — {@link joinOne} and {@link joinMany} use\n * PostgreSQL CTEs and `jsonb_agg` to load related rows in a single query.\n * - **Bidirectional result mapping** — rows returned from Postgres (column\n * names) are converted back to schema property names before being returned.\n * - **Thenable protocol** — the builder itself is `await`-able so you can\n * write `await query(db, Schema)` without calling {@link execute} explicitly.\n *\n * Create instances via the {@link query} factory function rather than\n * calling the constructor directly.\n *\n * @typeParam TLocalSchema - The `ObjectSchemaBuilder` describing the main table.\n * @typeParam TResult - The inferred row type, widened automatically as joins\n * are registered via {@link joinOne} / {@link joinMany}.\n *\n * @example\n * ```ts\n * import knex from 'knex';\n * import { query, object, string, number } from '@cleverbrush/knex-schema';\n *\n * const UserSchema = object({\n * id: number(),\n * name: string(),\n * age: number().optional(),\n * }).hasTableName('users');\n *\n * const db = knex({ client: 'pg', connection: process.env.DB_URL });\n *\n * // Fetch all users older than 18, ordered by name\n * const adults = await query(db, UserSchema)\n * .where(t => t.age, '>', 18)\n * .orderBy(t => t.name);\n * // adults: Array<{ id: number; name: string; age?: number }>\n * ```\n */\nexport class SchemaQueryBuilder<\n TLocalSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n TResult\n> {\n readonly #knex: Knex;\n readonly #baseQuery: Knex.QueryBuilder;\n readonly #localSchema: TLocalSchema;\n readonly #specs: ValidatedSpec[] = [];\n readonly #tableName: string;\n /**\n * Tracks the SQL column names that were explicitly passed to `.select()`.\n * `null` means no explicit select was made (SELECT *).\n */\n #explicitSelects: string[] | null = null;\n\n /**\n * @param knex - A configured Knex instance.\n * @param localSchema - The `ObjectSchemaBuilder` for the primary table.\n * Must have a table name set via `.hasTableName()`.\n * @param baseQuery - Optional pre-configured `Knex.QueryBuilder` to use as\n * the base query instead of the default `knex(tableName)`. Useful when you\n * need custom joins, CTEs, or other Knex features not exposed by this API.\n */\n constructor(\n knex: Knex,\n localSchema: TLocalSchema,\n baseQuery?: Knex.QueryBuilder\n ) {\n this.#knex = knex;\n this.#localSchema = localSchema;\n this.#tableName = getTableName(localSchema);\n this.#baseQuery = baseQuery ?? knex(this.#tableName);\n }\n\n // =======================================================================\n // Private helpers\n // =======================================================================\n\n #resolveColumn(ref: any, label = 'column'): string {\n return resolveColumnRef(\n ref as ColumnRef<any>,\n this.#localSchema,\n label\n );\n }\n\n // =======================================================================\n // Relation methods — eager loading (absorbed from knex-eager)\n // =======================================================================\n\n /**\n * Eager-load a single related row (one-to-one / many-to-one relationship).\n *\n * The related rows are fetched using a single CTE + `jsonb_agg` — no N+1\n * queries. The related object is attached to each result row under the\n * field name specified by `spec.as`.\n *\n * @param spec - Join specification. Key fields:\n * - `foreignSchema` — the `ObjectSchemaBuilder` of the related table.\n * - `localColumn` — the local column that holds the foreign-table reference.\n * - `foreignColumn` — the primary/unique key on the foreign table.\n * - `as` — the property name to attach the related object under.\n * - `required` — if `true` (default), rows without a matching related\n * record are excluded (inner join); if `false`, they are included with\n * `null` (left join).\n * - `foreignQuery` — optional pre-filtered `Knex.QueryBuilder` for the\n * foreign table (e.g. to apply scopes).\n *\n * @returns `this` (with an updated `TResult` type that includes the new field)\n * for chaining.\n *\n * @example\n * ```ts\n * const PostSchema = object({\n * id: number(),\n * title: string(),\n * authorId: number(),\n * }).hasTableName('posts');\n *\n * const AuthorSchema = object({\n * id: number(),\n * name: string(),\n * }).hasTableName('authors');\n *\n * const posts = await query(db, PostSchema)\n * .joinOne({\n * foreignSchema: AuthorSchema,\n * localColumn: t => t.authorId,\n * foreignColumn: t => t.id,\n * as: 'author',\n * });\n * // posts[0].author.name — typed as string ✓\n * ```\n */\n joinOne<\n TForeignSchema extends ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >,\n TFieldName extends string,\n TRequired extends boolean = true\n >(\n spec: JoinOneSpec<TLocalSchema, TForeignSchema, TFieldName, TRequired>\n ): SchemaQueryBuilder<\n TLocalSchema,\n WithJoinedOne<TResult, TFieldName, TForeignSchema, TRequired>\n > {\n const validated = validateJoinOne(spec, this.#localSchema, this.#knex);\n this.#specs.push({ type: 'one' as const, ...validated });\n validateUniqueFieldNames(this.#specs);\n return this as any;\n }\n\n /**\n * Eager-load a collection of related rows (one-to-many relationship).\n *\n * Related rows are fetched via a single CTE + `jsonb_agg` query. The\n * collection is attached to each result row under the field name specified\n * by `spec.as`. Supports `limit`, `offset`, and `orderBy` per-parent\n * using a `row_number()` window function to avoid fetching the full\n * relation before slicing.\n *\n * @param spec - Join specification. Key fields:\n * - `foreignSchema` — the `ObjectSchemaBuilder` of the related table.\n * - `localColumn` — the primary/unique key on the local table.\n * - `foreignColumn` — the column on the foreign table that references `localColumn`.\n * - `as` — the property name to attach the array under.\n * - `limit` / `offset` — optional pagination per parent row.\n * - `orderBy` — optional `{ column, direction }` for the sub-collection.\n * - `foreignQuery` — optional pre-filtered `Knex.QueryBuilder`.\n *\n * @returns `this` (with an updated `TResult` type that includes the new field)\n * for chaining.\n *\n * @example\n * ```ts\n * const UserSchema = object({\n * id: number(),\n * name: string(),\n * }).hasTableName('users');\n *\n * const PostSchema = object({\n * id: number(),\n * title: string(),\n * authorId: number(),\n * }).hasTableName('posts');\n *\n * const users = await query(db, UserSchema)\n * .joinMany({\n * foreignSchema: PostSchema,\n * localColumn: t => t.id,\n * foreignColumn: t => t.authorId,\n * as: 'posts',\n * limit: 5,\n * orderBy: { column: t => t.id, direction: 'desc' },\n * });\n * // users[0].posts — typed as Array<{ id: number; title: string; authorId: number }>\n * ```\n */\n joinMany<\n TForeignSchema extends ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >,\n TFieldName extends string\n >(\n spec: JoinManySpec<TLocalSchema, TForeignSchema, TFieldName>\n ): SchemaQueryBuilder<\n TLocalSchema,\n WithJoinedMany<TResult, TFieldName, TForeignSchema>\n > {\n const validated = validateJoinMany(spec, this.#localSchema, this.#knex);\n this.#specs.push({ type: 'many' as const, ...validated });\n validateUniqueFieldNames(this.#specs);\n return this as any;\n }\n\n // =======================================================================\n // WHERE methods\n // =======================================================================\n\n /**\n * Add a `WHERE` clause to the query.\n *\n * Accepts a column reference, an optional operator, and a value:\n * - `where(t => t.age, '>', 18)` — property accessor + operator + value.\n * - `where('age', 18)` — string key + value (defaults to `=`).\n * - `where({ name: 'Alice' })` — record object; property keys are mapped\n * to column names automatically.\n * - `where(builder => { ... })` — Knex sub-builder callback for grouped\n * conditions.\n * - `where(knex.raw('...'))` — raw SQL expression.\n *\n * Multiple `.where()` calls are combined with `AND`.\n *\n * @returns `this` for chaining.\n */\n where(column: ColumnRef<TLocalSchema>, operator: string, value: any): this;\n where(column: ColumnRef<TLocalSchema>, value: any): this;\n where(raw: Knex.Raw, operator: string, value: any): this;\n where(callback: (builder: Knex.QueryBuilder) => void): this;\n where(record: Record<string, any>): this;\n where(raw: Knex.Raw): this;\n where(\n columnOrRaw:\n | ColumnRef<TLocalSchema>\n | Knex.Raw\n | Record<string, any>\n | ((builder: Knex.QueryBuilder) => void),\n ...args: any[]\n ): this {\n if (\n typeof columnOrRaw === 'function' &&\n !this.#isColumnAccessor(columnOrRaw)\n ) {\n (this.#baseQuery.where as any)(columnOrRaw, ...args);\n } else if (\n typeof columnOrRaw === 'object' &&\n columnOrRaw !== null &&\n !('toSQL' in columnOrRaw)\n ) {\n // Record<string, any> — map property keys to column names\n const mapped = this.#mapRecordToColumns(\n columnOrRaw as Record<string, any>\n );\n (this.#baseQuery.where as any)(mapped, ...args);\n } else {\n const col = this.#resolveColumnArg(columnOrRaw);\n (this.#baseQuery.where as any)(col, ...args);\n }\n return this;\n }\n\n /**\n * Alias for {@link where} — explicitly adds an `AND WHERE` clause.\n * Identical to calling `.where()` when no logical-OR grouping is needed.\n * @returns `this` for chaining.\n */\n andWhere(\n column: ColumnRef<TLocalSchema>,\n operator: string,\n value: any\n ): this;\n andWhere(column: ColumnRef<TLocalSchema>, value: any): this;\n andWhere(record: Record<string, any>): this;\n andWhere(callback: (builder: Knex.QueryBuilder) => void): this;\n andWhere(raw: Knex.Raw): this;\n andWhere(\n columnOrRaw:\n | ColumnRef<TLocalSchema>\n | Knex.Raw\n | Record<string, any>\n | ((builder: Knex.QueryBuilder) => void),\n ...args: any[]\n ): this {\n if (\n typeof columnOrRaw === 'function' &&\n !this.#isColumnAccessor(columnOrRaw)\n ) {\n (this.#baseQuery.andWhere as any)(columnOrRaw, ...args);\n } else if (\n typeof columnOrRaw === 'object' &&\n columnOrRaw !== null &&\n !('toSQL' in columnOrRaw)\n ) {\n const mapped = this.#mapRecordToColumns(\n columnOrRaw as Record<string, any>\n );\n (this.#baseQuery.andWhere as any)(mapped, ...args);\n } else {\n const col = this.#resolveColumnArg(columnOrRaw);\n (this.#baseQuery.andWhere as any)(col, ...args);\n }\n return this;\n }\n\n /**\n * Add an `OR WHERE` clause. Use this to create alternative filter branches.\n * @returns `this` for chaining.\n */\n orWhere(\n column: ColumnRef<TLocalSchema>,\n operator: string,\n value: any\n ): this;\n orWhere(column: ColumnRef<TLocalSchema>, value: any): this;\n orWhere(record: Record<string, any>): this;\n orWhere(callback: (builder: Knex.QueryBuilder) => void): this;\n orWhere(raw: Knex.Raw): this;\n orWhere(\n columnOrRaw:\n | ColumnRef<TLocalSchema>\n | Knex.Raw\n | Record<string, any>\n | ((builder: Knex.QueryBuilder) => void),\n ...args: any[]\n ): this {\n if (\n typeof columnOrRaw === 'function' &&\n !this.#isColumnAccessor(columnOrRaw)\n ) {\n (this.#baseQuery.orWhere as any)(columnOrRaw, ...args);\n } else if (\n typeof columnOrRaw === 'object' &&\n columnOrRaw !== null &&\n !('toSQL' in columnOrRaw)\n ) {\n const mapped = this.#mapRecordToColumns(\n columnOrRaw as Record<string, any>\n );\n (this.#baseQuery.orWhere as any)(mapped, ...args);\n } else {\n const col = this.#resolveColumnArg(columnOrRaw);\n (this.#baseQuery.orWhere as any)(col, ...args);\n }\n return this;\n }\n\n /**\n * Add a `WHERE NOT` clause — negates the condition.\n * @returns `this` for chaining.\n */\n whereNot(\n column: ColumnRef<TLocalSchema>,\n operator: string,\n value: any\n ): this;\n whereNot(column: ColumnRef<TLocalSchema>, value: any): this;\n whereNot(record: Record<string, any>): this;\n whereNot(callback: (builder: Knex.QueryBuilder) => void): this;\n whereNot(raw: Knex.Raw): this;\n whereNot(\n columnOrRaw:\n | ColumnRef<TLocalSchema>\n | Knex.Raw\n | Record<string, any>\n | ((builder: Knex.QueryBuilder) => void),\n ...args: any[]\n ): this {\n if (\n typeof columnOrRaw === 'function' &&\n !this.#isColumnAccessor(columnOrRaw)\n ) {\n (this.#baseQuery.whereNot as any)(columnOrRaw, ...args);\n } else if (\n typeof columnOrRaw === 'object' &&\n columnOrRaw !== null &&\n !('toSQL' in columnOrRaw)\n ) {\n const mapped = this.#mapRecordToColumns(\n columnOrRaw as Record<string, any>\n );\n (this.#baseQuery.whereNot as any)(mapped, ...args);\n } else {\n const col = this.#resolveColumnArg(columnOrRaw);\n (this.#baseQuery.whereNot as any)(col, ...args);\n }\n return this;\n }\n\n /**\n * Add a `WHERE column IN (values)` clause.\n * @param column - Column reference (property accessor or string key).\n * @param values - Array of values or a sub-query.\n * @returns `this` for chaining.\n */\n whereIn(\n column: ColumnRef<TLocalSchema>,\n values: readonly any[] | Knex.QueryBuilder\n ): this {\n this.#baseQuery.whereIn(\n this.#resolveColumn(column, 'whereIn'),\n values as any\n );\n return this;\n }\n\n /**\n * Add a `WHERE column NOT IN (values)` clause.\n * @param column - Column reference.\n * @param values - Array of values or a sub-query.\n * @returns `this` for chaining.\n */\n whereNotIn(\n column: ColumnRef<TLocalSchema>,\n values: readonly any[] | Knex.QueryBuilder\n ): this {\n this.#baseQuery.whereNotIn(\n this.#resolveColumn(column, 'whereNotIn'),\n values as any\n );\n return this;\n }\n\n /**\n * Add an `OR WHERE column IN (values)` clause.\n * @returns `this` for chaining.\n */\n orWhereIn(\n column: ColumnRef<TLocalSchema>,\n values: readonly any[] | Knex.QueryBuilder\n ): this {\n (this.#baseQuery as any).orWhereIn(\n this.#resolveColumn(column, 'orWhereIn'),\n values as any\n );\n return this;\n }\n\n /**\n * Add an `OR WHERE column NOT IN (values)` clause.\n * @returns `this` for chaining.\n */\n orWhereNotIn(\n column: ColumnRef<TLocalSchema>,\n values: readonly any[] | Knex.QueryBuilder\n ): this {\n (this.#baseQuery as any).orWhereNotIn(\n this.#resolveColumn(column, 'orWhereNotIn'),\n values as any\n );\n return this;\n }\n\n /**\n * Add a `WHERE column IS NULL` clause.\n * @returns `this` for chaining.\n */\n whereNull(column: ColumnRef<TLocalSchema>): this {\n this.#baseQuery.whereNull(this.#resolveColumn(column, 'whereNull'));\n return this;\n }\n\n /**\n * Add a `WHERE column IS NOT NULL` clause.\n * @returns `this` for chaining.\n */\n whereNotNull(column: ColumnRef<TLocalSchema>): this {\n this.#baseQuery.whereNotNull(\n this.#resolveColumn(column, 'whereNotNull')\n );\n return this;\n }\n\n /**\n * Add an `OR WHERE column IS NULL` clause.\n * @returns `this` for chaining.\n */\n orWhereNull(column: ColumnRef<TLocalSchema>): this {\n (this.#baseQuery as any).orWhereNull(\n this.#resolveColumn(column, 'orWhereNull')\n );\n return this;\n }\n\n /**\n * Add an `OR WHERE column IS NOT NULL` clause.\n * @returns `this` for chaining.\n */\n orWhereNotNull(column: ColumnRef<TLocalSchema>): this {\n (this.#baseQuery as any).orWhereNotNull(\n this.#resolveColumn(column, 'orWhereNotNull')\n );\n return this;\n }\n\n /**\n * Add a `WHERE column BETWEEN low AND high` clause.\n * @param range - A two-element tuple `[low, high]`.\n * @returns `this` for chaining.\n */\n whereBetween(\n column: ColumnRef<TLocalSchema>,\n range: readonly [any, any]\n ): this {\n this.#baseQuery.whereBetween(\n this.#resolveColumn(column, 'whereBetween'),\n range as [any, any]\n );\n return this;\n }\n\n /**\n * Add a `WHERE column NOT BETWEEN low AND high` clause.\n * @param range - A two-element tuple `[low, high]`.\n * @returns `this` for chaining.\n */\n whereNotBetween(\n column: ColumnRef<TLocalSchema>,\n range: readonly [any, any]\n ): this {\n this.#baseQuery.whereNotBetween(\n this.#resolveColumn(column, 'whereNotBetween'),\n range as [any, any]\n );\n return this;\n }\n\n /**\n * Add a case-sensitive `WHERE column LIKE value` clause.\n * @param value - A SQL LIKE pattern (e.g. `'Alice%'`).\n * @returns `this` for chaining.\n */\n whereLike(column: ColumnRef<TLocalSchema>, value: string): this {\n (this.#baseQuery as any).whereLike(\n this.#resolveColumn(column, 'whereLike'),\n value\n );\n return this;\n }\n\n /**\n * Add a case-insensitive `WHERE column ILIKE value` clause (PostgreSQL).\n * @param value - A SQL LIKE pattern (e.g. `'alice%'`).\n * @returns `this` for chaining.\n */\n whereILike(column: ColumnRef<TLocalSchema>, value: string): this {\n (this.#baseQuery as any).whereILike(\n this.#resolveColumn(column, 'whereILike'),\n value\n );\n return this;\n }\n\n /**\n * Add a raw `WHERE` clause. Useful for database-specific expressions.\n * @param sql - Raw SQL string with optional `:binding:` or `?` placeholders.\n * @param bindings - Values for the placeholders.\n * @returns `this` for chaining.\n */\n whereRaw(sql: string, ...bindings: any[]): this {\n this.#baseQuery.whereRaw(sql, ...bindings);\n return this;\n }\n\n /**\n * Add a `WHERE EXISTS (subquery)` clause.\n * @param callback - A Knex query callback or sub-query builder.\n * @returns `this` for chaining.\n */\n whereExists(callback: Knex.QueryCallback | Knex.QueryBuilder): this {\n this.#baseQuery.whereExists(callback as any);\n return this;\n }\n\n // =======================================================================\n // ORDER BY\n // =======================================================================\n\n /**\n * Order the results by a column.\n * @param column - Column reference or raw expression.\n * @param direction - `'asc'` (default) or `'desc'`.\n * @returns `this` for chaining.\n *\n * @example\n * ```ts\n * query(db, UserSchema).orderBy(t => t.name).orderBy(t => t.createdAt, 'desc');\n * ```\n */\n orderBy(\n column: ColumnRef<TLocalSchema> | Knex.Raw,\n direction?: 'asc' | 'desc'\n ): this {\n const col = this.#resolveColumnArg(column);\n this.#baseQuery.orderBy(col as string, direction);\n return this;\n }\n\n /**\n * Order the results by a raw SQL expression.\n * @param sql - Raw SQL (e.g. `'LOWER(name) ASC'`).\n * @returns `this` for chaining.\n */\n orderByRaw(sql: string, ...bindings: any[]): this {\n this.#baseQuery.orderByRaw(sql, ...bindings);\n return this;\n }\n\n // =======================================================================\n // GROUP BY / HAVING\n // =======================================================================\n\n /**\n * Add a `GROUP BY` clause.\n * @param columns - One or more column references or raw expressions.\n * @returns `this` for chaining.\n */\n groupBy(...columns: (ColumnRef<TLocalSchema> | Knex.Raw)[]): this {\n const resolved = columns.map(c => this.#resolveColumnArg(c));\n this.#baseQuery.groupBy(...(resolved as string[]));\n return this;\n }\n\n /**\n * Add a raw `GROUP BY` expression.\n * @returns `this` for chaining.\n */\n groupByRaw(sql: string, ...bindings: any[]): this {\n this.#baseQuery.groupByRaw(sql, ...bindings);\n return this;\n }\n\n /**\n * Add a `HAVING column operator value` clause (used with `GROUP BY`).\n * @returns `this` for chaining.\n */\n having(\n column: ColumnRef<TLocalSchema> | Knex.Raw,\n operator: string,\n value: any\n ): this {\n const col = this.#resolveColumnArg(column);\n this.#baseQuery.having(col as string, operator, value);\n return this;\n }\n\n /**\n * Add a raw `HAVING` expression.\n * @returns `this` for chaining.\n */\n havingRaw(sql: string, ...bindings: any[]): this {\n this.#baseQuery.havingRaw(sql, ...bindings);\n return this;\n }\n\n // =======================================================================\n // PAGINATION\n // =======================================================================\n\n /**\n * Limit the number of rows returned.\n * @param n - Maximum number of rows.\n * @returns `this` for chaining.\n */\n limit(n: number): this {\n this.#baseQuery.limit(n);\n return this;\n }\n\n /**\n * Skip the first `n` rows in the result set (for cursor/offset pagination).\n * @param n - Number of rows to skip.\n * @returns `this` for chaining.\n */\n offset(n: number): this {\n this.#baseQuery.offset(n);\n return this;\n }\n\n // =======================================================================\n // SELECT / DISTINCT\n // =======================================================================\n\n /**\n * Select specific columns instead of `*`. Each column reference is\n * resolved to its SQL column name through the schema.\n * @param columns - One or more column references or raw expressions.\n * @returns `this` for chaining.\n */\n select(...columns: (ColumnRef<TLocalSchema> | Knex.Raw)[]): this {\n const resolved = columns.map(c => this.#resolveColumnArg(c));\n this.#baseQuery.select(...(resolved as string[]));\n // Track the string-resolved columns (not Knex.Raw) for CTE column management\n this.#explicitSelects ??= [];\n for (const r of resolved) {\n if (typeof r === 'string') {\n this.#explicitSelects.push(r);\n }\n }\n return this;\n }\n\n /**\n * Add `DISTINCT` to the select clause. Duplicate rows are eliminated.\n * @param columns - One or more column references or raw expressions.\n * @returns `this` for chaining.\n */\n distinct(...columns: (ColumnRef<TLocalSchema> | Knex.Raw)[]): this {\n const resolved = columns.map(c => this.#resolveColumnArg(c));\n this.#baseQuery.distinct(...(resolved as string[]));\n return this;\n }\n\n // =======================================================================\n // AGGREGATES\n // =======================================================================\n\n /**\n * Add a `COUNT(*)` or `COUNT(column)` aggregate to the select list.\n * @param column - Optional column to count (defaults to `*`).\n * @returns `this` for chaining.\n */\n count(column?: ColumnRef<TLocalSchema> | Knex.Raw): this {\n if (column) {\n this.#baseQuery.count(this.#resolveColumnArg(column) as string);\n } else {\n this.#baseQuery.count();\n }\n return this;\n }\n\n /**\n * Add a `COUNT(DISTINCT column)` aggregate to the select list.\n * @param column - Optional column (defaults to `*`).\n * @returns `this` for chaining.\n */\n countDistinct(column?: ColumnRef<TLocalSchema> | Knex.Raw): this {\n if (column) {\n this.#baseQuery.countDistinct(\n this.#resolveColumnArg(column) as string\n );\n } else {\n this.#baseQuery.countDistinct();\n }\n return this;\n }\n\n /**\n * Add a `MIN(column)` aggregate.\n * @returns `this` for chaining.\n */\n min(column: ColumnRef<TLocalSchema> | Knex.Raw): this {\n this.#baseQuery.min(this.#resolveColumnArg(column) as string);\n return this;\n }\n\n /**\n * Add a `MAX(column)` aggregate.\n * @returns `this` for chaining.\n */\n max(column: ColumnRef<TLocalSchema> | Knex.Raw): this {\n this.#baseQuery.max(this.#resolveColumnArg(column) as string);\n return this;\n }\n\n /**\n * Add a `SUM(column)` aggregate.\n * @returns `this` for chaining.\n */\n sum(column: ColumnRef<TLocalSchema> | Knex.Raw): this {\n this.#baseQuery.sum(this.#resolveColumnArg(column) as string);\n return this;\n }\n\n /**\n * Add an `AVG(column)` aggregate.\n * @returns `this` for chaining.\n */\n avg(column: ColumnRef<TLocalSchema> | Knex.Raw): this {\n this.#baseQuery.avg(this.#resolveColumnArg(column) as string);\n return this;\n }\n\n // =======================================================================\n // WRITE OPERATIONS\n // =======================================================================\n\n /**\n * Insert a single row into the table and return the inserted record.\n *\n * Property keys are mapped to SQL column names via the schema's\n * `hasColumnName()` metadata before the `INSERT` is executed. The\n * returned row is mapped back to property names.\n *\n * @param data - The object to insert. Keys must be valid schema property names.\n * @returns The full inserted row (including database-generated fields).\n *\n * @example\n * ```ts\n * const user = await query(db, UserSchema).insert({ name: 'Alice', age: 30 });\n * // user.id is populated by the database DEFAULT / SERIAL\n * ```\n */\n async insert(data: InsertType<TLocalSchema>): Promise<TResult> {\n const mapped = this.#mapObjectToColumns(data as Record<string, any>);\n const [row] = await this.#knex(this.#tableName)\n .insert(mapped)\n .returning('*');\n return this.#mapRow(row) as TResult;\n }\n\n /**\n * Insert multiple rows in a single `INSERT` statement and return all\n * inserted records.\n *\n * @param data - Array of objects to insert.\n * @returns The full inserted rows in insertion order.\n */\n async insertMany(data: InsertType<TLocalSchema>[]): Promise<TResult[]> {\n const mapped = data.map(d =>\n this.#mapObjectToColumns(d as Record<string, any>)\n );\n const rows = await this.#knex(this.#tableName)\n .insert(mapped)\n .returning('*');\n return rows.map((row: any) => this.#mapRow(row) as TResult);\n }\n\n /**\n * Update all rows that match the current `WHERE` clause and return the\n * updated records.\n *\n * Only the keys present in `data` are updated (partial update). Property\n * keys are resolved to column names automatically.\n *\n * @param data - Partial schema object with fields to update.\n * @returns All rows that were updated.\n *\n * @example\n * ```ts\n * const updated = await query(db, UserSchema)\n * .where(t => t.id, userId)\n * .update({ name: 'Bob' });\n * ```\n */\n async update(data: Partial<InferType<TLocalSchema>>): Promise<TResult[]> {\n const mapped = this.#mapObjectToColumns(data as Record<string, any>);\n const rows = await this.#baseQuery.update(mapped).returning('*');\n return rows.map((row: any) => this.#mapRow(row) as TResult);\n }\n\n /**\n * Delete all rows that match the current `WHERE` clause.\n * @returns The number of rows deleted.\n *\n * @example\n * ```ts\n * const count = await query(db, UserSchema).where(t => t.id, id).delete();\n * ```\n */\n async delete(): Promise<number> {\n return this.#baseQuery.delete();\n }\n\n // =======================================================================\n // ESCAPE HATCH\n // =======================================================================\n\n /**\n * Escape hatch: apply any Knex method to the underlying base query.\n *\n * Use this when you need a Knex feature not exposed by this API (e.g.\n * `forUpdate()`, CTEs, `join()`, `union()`).\n *\n * @param fn - A callback that receives the raw `Knex.QueryBuilder` and\n * may mutate it in place.\n * @returns `this` for chaining.\n *\n * @example\n * ```ts\n * query(db, UserSchema).apply(qb => qb.forUpdate().noWait());\n * ```\n */\n apply(fn: (builder: Knex.QueryBuilder) => void): this {\n fn(this.#baseQuery);\n return this;\n }\n\n // =======================================================================\n // Transaction support\n // =======================================================================\n\n /**\n * Bind this query builder to a Knex transaction.\n *\n * Returns a **new** builder that runs all operations — SELECT, INSERT,\n * UPDATE, DELETE, and eager-loaded sub-queries — within the given\n * transaction. The original builder is left unchanged.\n *\n * Use this when you already have a transaction obtained from\n * `knex.transaction()` and want all operations performed by the returned\n * builder to participate in that transaction.\n *\n * @param trx - The Knex transaction obtained from `knex.transaction()`.\n * @returns A new {@link SchemaQueryBuilder} bound to the transaction.\n *\n * @example\n * ```ts\n * async function createUser(\n * data: InsertType<typeof UserSchema>,\n * trx: Knex.Transaction\n * ) {\n * return query(db, UserSchema).transacting(trx).insert(data);\n * }\n *\n * await db.transaction(async trx => {\n * const user = await createUser({ name: 'Alice' }, trx);\n * await query(db, PostSchema).transacting(trx).insert({ authorId: user.id, title: 'Hello' });\n * });\n * ```\n */\n transacting(\n trx: Knex.Transaction\n ): SchemaQueryBuilder<TLocalSchema, TResult> {\n const builder = new SchemaQueryBuilder<TLocalSchema, TResult>(\n trx as unknown as Knex,\n this.#localSchema,\n this.#baseQuery.clone().transacting(trx)\n );\n for (const spec of this.#specs) {\n builder.#specs.push({\n ...spec,\n foreignQuery: spec.foreignQuery.clone().transacting(trx)\n });\n }\n builder.#explicitSelects = this.#explicitSelects\n ? [...this.#explicitSelects]\n : null;\n return builder;\n }\n\n // =======================================================================\n // CTE-based eager loading query building (from knex-eager)\n // =======================================================================\n\n #buildQuery(): Knex.QueryBuilder {\n if (this.#specs.length === 0) {\n return this.#baseQuery;\n }\n\n const knex = this.#knex;\n const specs = this.#specs;\n\n // Collect all localColumns needed for CTE joins\n const requiredLocalColumns = [\n ...new Set(specs.map(s => s.localColumn))\n ];\n\n // If the caller used .select(...), some localColumns may have been\n // omitted. Clone the base query and ensure those columns are always\n // included in the CTE so the join conditions work at runtime.\n // Track which columns we added so they can be excluded from the\n // final SELECT (preserving the original column set the caller asked for).\n let cteQuery = this.#baseQuery;\n let extraColumns: string[] = [];\n\n if (this.#explicitSelects !== null) {\n const selectedSet = new Set(this.#explicitSelects);\n extraColumns = requiredLocalColumns.filter(\n col => !selectedSet.has(col)\n );\n if (extraColumns.length > 0) {\n cteQuery = this.#baseQuery.clone();\n for (const col of extraColumns) {\n cteQuery.column(col);\n }\n }\n }\n\n // Build the outer query that wraps the CTE.\n // When we added extra columns, select only the original columns + joined\n // aliases (instead of originalQuery.*) so the caller's column set is\n // preserved in the final result.\n const resultQuery = knex.queryBuilder().with('originalQuery', cteQuery);\n\n if (extraColumns.length > 0 && this.#explicitSelects !== null) {\n // Explicit column list: original user selections only\n for (const col of this.#explicitSelects) {\n resultQuery.select(\n knex.raw(':originalQuery:.:col: as :col:', {\n originalQuery: 'originalQuery',\n col\n })\n );\n }\n } else {\n resultQuery.select('originalQuery.*');\n }\n\n resultQuery.from(\n knex.raw(':originalQuery:', {\n originalQuery: 'originalQuery'\n })\n );\n\n for (let i = 0; i < specs.length; i++) {\n const spec = specs[i];\n const relationAlias = `eagerRelation${i}`;\n\n if (spec.type === 'one') {\n this.#buildJoinOne(resultQuery, spec, relationAlias);\n } else {\n this.#buildJoinMany(resultQuery, spec, relationAlias, i);\n }\n }\n\n return resultQuery;\n }\n\n #buildJoinOne(\n resultQuery: Knex.QueryBuilder,\n spec: ValidatedSpec & { type: 'one' },\n relationAlias: string\n ): void {\n const knex = this.#knex;\n const foreignTable = spec.foreignQuery;\n const foreignTableName = (foreignTable as any)._single?.table;\n\n if (!foreignTableName) {\n throw new Error(\n `Could not determine table name from foreignQuery for \"${spec.as}\". ` +\n 'Make sure foreignQuery is created via knex(\"tableName\").'\n );\n }\n\n resultQuery.select(\n knex.raw(':relationAlias:.:as:->0 as :as:', {\n relationAlias,\n as: spec.as\n })\n );\n\n const subquery = knex\n .from(foreignTable.as(foreignTableName))\n .select(\n knex.raw(':foreignTable:.:foreignColumn:', {\n foreignTable: foreignTableName,\n foreignColumn: spec.foreignColumn\n })\n )\n .select(\n knex.raw('jsonb_agg(:foreignTable:) as :as:', {\n foreignTable: foreignTableName,\n as: spec.as\n })\n )\n .groupByRaw(':foreignTable:.:foreignColumn:', {\n foreignTable: foreignTableName,\n foreignColumn: spec.foreignColumn\n })\n .as(relationAlias);\n\n const joinMethod = spec.required ? 'join' : 'leftJoin';\n resultQuery[joinMethod](subquery, function () {\n this.on(\n knex.raw(\n ':relationAlias:.:foreignColumn: = :originalQuery:.:localColumn:',\n {\n originalQuery: 'originalQuery',\n relationAlias,\n foreignColumn: spec.foreignColumn,\n localColumn: spec.localColumn\n }\n )\n );\n });\n }\n\n #buildJoinMany(\n resultQuery: Knex.QueryBuilder,\n spec: ValidatedSpec & { type: 'many' },\n relationAlias: string,\n i: number\n ): void {\n const knex = this.#knex;\n const filterName = `withFilter${i}`;\n\n const hasLimitOffset =\n (spec.limit !== null && spec.limit > 0) ||\n (spec.offset !== null && spec.offset > 0);\n\n const orderByColumn = spec.orderBy\n ? spec.orderBy.column\n : spec.foreignColumn;\n const orderByDirection = spec.orderBy ? spec.orderBy.direction : 'asc';\n\n if (hasLimitOffset) {\n resultQuery.with(\n filterName,\n knex\n .from(\n spec.foreignQuery\n .clone()\n .whereIn(\n spec.foreignColumn,\n knex\n .from(\n knex.raw(':originalQuery:', {\n originalQuery: 'originalQuery'\n })\n )\n .distinct(spec.localColumn)\n )\n .as(`__wf_inner_${i}`)\n )\n .select(`__wf_inner_${i}.*`)\n .select(\n knex.raw(\n `row_number() over(partition by :foreignColumn: order by :orderByColumn: ${orderByDirection}) as \"__rn__\"`,\n {\n foreignColumn: spec.foreignColumn,\n orderByColumn\n }\n )\n )\n );\n } else {\n resultQuery.with(\n filterName,\n spec.foreignQuery.clone().whereIn(\n spec.foreignColumn,\n knex\n .from(\n knex.raw(':originalQuery:', {\n originalQuery: 'originalQuery'\n })\n )\n .distinct(spec.localColumn)\n )\n );\n }\n\n const aggSubquery = knex.from(filterName);\n\n if (hasLimitOffset) {\n const hasLimit = spec.limit !== null && spec.limit > 0;\n const hasOffset = spec.offset !== null && spec.offset > 0;\n const effectiveOffset = spec.offset ?? 0;\n const effectiveLimit = effectiveOffset + (spec.limit ?? 0);\n\n const condition =\n hasLimit && hasOffset\n ? '\"__rn__\" > :offset and \"__rn__\" <= :limit'\n : hasLimit\n ? '\"__rn__\" <= :limit'\n : '\"__rn__\" > :offset';\n\n aggSubquery.whereRaw(condition, {\n limit: effectiveLimit,\n offset: effectiveOffset\n });\n\n aggSubquery.select(\n knex.raw(':foreignColumn:', {\n foreignColumn: spec.foreignColumn\n })\n );\n aggSubquery.select(\n knex.raw(\n \"coalesce(jsonb_agg(to_jsonb(:filterName:) - '__rn__' order by \\\"__rn__\\\"), '[]'::jsonb) as :as:\",\n { filterName, as: spec.as }\n )\n );\n } else {\n aggSubquery.select(\n knex.raw(':foreignColumn:', {\n foreignColumn: spec.foreignColumn\n })\n );\n\n const orderClause = spec.orderBy\n ? `jsonb_agg(:filterName: order by :filterName:.:orderByColumn: ${orderByDirection})`\n : 'jsonb_agg(:filterName:)';\n\n aggSubquery.select(\n knex.raw(\n `coalesce(${orderClause}, '[]'::jsonb) as :as:`,\n spec.orderBy\n ? { filterName, orderByColumn, as: spec.as }\n : { filterName, as: spec.as }\n )\n );\n }\n\n aggSubquery.groupByRaw(':foreignColumn:', {\n foreignColumn: spec.foreignColumn\n });\n\n const subquery = aggSubquery.as(relationAlias);\n\n resultQuery.select(\n knex.raw(\"coalesce(:relationAlias:.:as:, '[]'::jsonb) as :as:\", {\n relationAlias,\n as: spec.as\n })\n );\n\n resultQuery.leftJoin(subquery, function () {\n this.on(\n knex.raw(\n ':relationAlias:.:foreignColumn: = :originalQuery:.:localColumn:',\n {\n relationAlias,\n foreignColumn: spec.foreignColumn,\n originalQuery: 'originalQuery',\n localColumn: spec.localColumn\n }\n )\n );\n });\n }\n\n // =======================================================================\n // Result mapping\n // =======================================================================\n\n /**\n * Map a SQL result row (column names) back to schema property names.\n * Also handles joined fields (which are already named by `as`).\n */\n #mapRow(row: Record<string, any>): Record<string, any> {\n if (!row) return row;\n\n const { colToProp } = buildColumnMap(this.#localSchema);\n const result: Record<string, any> = {};\n\n // Map known columns back to property names\n for (const [colName, value] of Object.entries(row)) {\n const propName = colToProp.get(colName);\n if (propName) {\n result[propName] = value;\n } else {\n // Unknown column (e.g., joined field, raw expression) — pass through\n result[colName] = value;\n }\n }\n\n return result;\n }\n\n /**\n * Clean a row that has eager-loaded relations (apply mappers, then map columns).\n */\n #cleanAndMapRow(row: Record<string, any>): Record<string, any> {\n const oneSpecs = this.#specs.filter(\n (s): s is ValidatedSpec & { type: 'one' } => s.type === 'one'\n );\n const manySpecs = this.#specs.filter(\n (s): s is ValidatedSpec & { type: 'many' } => s.type === 'many'\n );\n const cleaned = clearRow(row, oneSpecs, manySpecs);\n return this.#mapRow(cleaned);\n }\n\n // =======================================================================\n // Column mapping helpers\n // =======================================================================\n\n /**\n * Map a schema-shaped object (property keys) to a SQL object (column names).\n * Used for INSERT / UPDATE.\n */\n #mapObjectToColumns(obj: Record<string, any>): Record<string, any> {\n const { propToCol } = buildColumnMap(this.#localSchema);\n const result: Record<string, any> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const colName = propToCol.get(key);\n if (colName) {\n result[colName] = value;\n } else {\n // Unknown key — pass through (could be a raw column)\n result[key] = value;\n }\n }\n\n return result;\n }\n\n /**\n * Map a Record<propertyKey, value> to Record<columnName, value>.\n * Used for `.where({ name: 'John' })` style calls.\n */\n #mapRecordToColumns(record: Record<string, any>): Record<string, any> {\n return this.#mapObjectToColumns(record);\n }\n\n /**\n * Resolve a column argument that could be a ColumnRef, Knex.Raw, or callback.\n * Returns the resolved string or passes through Knex.Raw.\n */\n #resolveColumnArg(col: any): string | Knex.Raw {\n if (typeof col === 'string') {\n return this.#resolveColumn(col, 'column');\n }\n if (typeof col === 'function') {\n // Property descriptor accessor\n return this.#resolveColumn(col, 'column');\n }\n // Knex.Raw — pass through\n return col;\n }\n\n /**\n * Detect if a function is a property descriptor accessor (takes tree, returns descriptor)\n * vs a knex sub-builder callback (takes builder, returns void).\n *\n * Heuristic: property descriptor accessors are arrow functions that access\n * tree properties. We cannot distinguish at runtime, so we try the accessor\n * and fallback to callback if it fails.\n *\n * For safety, we check if the function parameter count can help:\n * - Knex callbacks typically have 1 parameter named `builder` or `qb`\n * - Property accessors typically have 1 parameter named `t` or similar\n *\n * Since both are `(arg) => result`, we use a try/catch approach:\n * attempt to resolve as column accessor first.\n */\n #isColumnAccessor(fn: Function): boolean {\n // Try to invoke the accessor with the property descriptor tree\n try {\n const tree = ObjectSchemaBuilder.getPropertiesFor(\n this.#localSchema as any\n );\n const result = fn(tree);\n // If it returns a valid property descriptor, it's an accessor\n if (\n result &&\n typeof result === 'object' &&\n SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR in result\n ) {\n return true;\n }\n } catch {\n // Not an accessor\n }\n return false;\n }\n\n // =======================================================================\n // Execution\n // =======================================================================\n\n /**\n * Return the raw SQL string that would be executed, for debugging.\n * Does not execute the query against the database.\n */\n toQuery(): string {\n return this.#buildQuery().toQuery();\n }\n\n /**\n * Returns the underlying Knex query builder. Useful when passing this\n * query as a `foreignQuery` in `.joinOne()` / `.joinMany()`, or any context\n * that expects a raw `Knex.QueryBuilder`.\n */\n toKnexQuery(): Knex.QueryBuilder {\n return this.#buildQuery();\n }\n\n /**\n * Alias for {@link toQuery} — returns the raw SQL string.\n */\n toString(): string {\n return this.#buildQuery().toString();\n }\n\n /**\n * Execute the query and return all matching rows, mapped back to schema\n * property names.\n *\n * @returns A promise that resolves to an array of result objects typed as\n * `TResult[]`.\n *\n * @example\n * ```ts\n * const users = await query(db, UserSchema).execute();\n * ```\n */\n async execute(): Promise<TResult[]> {\n const query = this.#buildQuery();\n const rows = await query;\n\n if (!rows) return [];\n if (!Array.isArray(rows))\n return [this.#cleanAndMapRow(rows)] as TResult[];\n\n return rows.map((row: any) => this.#cleanAndMapRow(row)) as TResult[];\n }\n\n /**\n * Execute the query and return only the first row, or `undefined` if no\n * rows match.\n *\n * @example\n * ```ts\n * const user = await query(db, UserSchema).where(t => t.id, id).first();\n * if (user) { /* ... *\\/ }\n * ```\n */\n async first(): Promise<TResult | undefined> {\n const query = this.#buildQuery().first();\n const row = await query;\n\n if (!row) return undefined;\n return this.#cleanAndMapRow(row) as TResult;\n }\n\n /**\n * Thenable implementation — allows the builder to be awaited directly\n * without calling {@link execute} explicitly.\n *\n * @example\n * ```ts\n * const users = await query(db, UserSchema).where(t => t.name, 'Alice');\n * // Equivalent to: await query(db, UserSchema).where(...).execute()\n * ```\n */\n // biome-ignore lint/suspicious/noThenProperty: intentional thenable for `await builder` support\n then<TReturn1 = TResult[], TReturn2 = never>(\n onfulfilled?:\n | ((value: TResult[]) => TReturn1 | PromiseLike<TReturn1>)\n | null,\n onrejected?: ((reason: any) => TReturn2 | PromiseLike<TReturn2>) | null\n ): Promise<TReturn1 | TReturn2> {\n return this.execute().then(onfulfilled, onrejected);\n }\n}\n\n// ---------------------------------------------------------------------------\n// query() — main entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Create a typed {@link SchemaQueryBuilder} for the table described by `schema`.\n *\n * The schema must have a table name configured via `.hasTableName()`.\n * Column name mappings set via `.hasColumnName()` are applied automatically\n * to all query methods. The returned builder is thenable — you can `await` it\n * directly to execute the query and get `TResult[]`.\n *\n * @param knex - A configured Knex instance.\n * @param schema - The `ObjectSchemaBuilder` describing the table.\n * @returns A new {@link SchemaQueryBuilder} ready for chaining.\n *\n * @example\n * ```ts\n * import knex from 'knex';\n * import { query, object, string, number } from '@cleverbrush/knex-schema';\n *\n * const UserSchema = object({ id: number(), name: string() }).hasTableName('users');\n * const db = knex({ client: 'pg', connection: process.env.DB_URL });\n *\n * const users = await query(db, UserSchema).where(t => t.name, 'like', 'A%');\n * ```\n */\nexport function query<\n TLocalSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>\n>(\n knex: Knex,\n schema: TLocalSchema\n): SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>>;\n\n/**\n * Create a typed {@link SchemaQueryBuilder} from an existing Knex query builder.\n *\n * Use this overload when you need to supply a pre-configured base query —\n * for example one that already has a sub-query, CTE, or a schema scope applied.\n *\n * @param knex - A configured Knex instance.\n * @param schema - The `ObjectSchemaBuilder` describing the table.\n * @param baseQuery - An existing `Knex.QueryBuilder` to use as the base.\n * @returns A new {@link SchemaQueryBuilder} wrapping `baseQuery`.\n *\n * @example\n * ```ts\n * // Use a scoped base query (e.g. soft-delete filter applied globally)\n * const base = db('users').where('deleted_at', null);\n * const activeUsers = await query(db, UserSchema, base).where(t => t.age, '>', 18);\n * ```\n */\nexport function query<\n TLocalSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>\n>(\n knex: Knex,\n schema: TLocalSchema,\n baseQuery: Knex.QueryBuilder\n): SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>>;\n\nexport function query<\n TLocalSchema extends ObjectSchemaBuilder<any, any, any, any, any, any, any>\n>(\n knex: Knex,\n schema: TLocalSchema,\n baseQuery?: Knex.QueryBuilder\n): SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>> {\n return new SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>>(\n knex,\n schema,\n baseQuery\n );\n}\n\n// ---------------------------------------------------------------------------\n// createQuery() — knex-bound factory\n// ---------------------------------------------------------------------------\n\n/** Bound query function returned by {@link createQuery}. */\nexport interface BoundQuery {\n <\n TLocalSchema extends ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >\n >(\n schema: TLocalSchema\n ): SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>>;\n <\n TLocalSchema extends ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >\n >(\n schema: TLocalSchema,\n baseQuery: Knex.QueryBuilder\n ): SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>>;\n /**\n * Return a version of this bound factory whose queries all run within the\n * given Knex transaction. Equivalent to calling `.transacting(trx)` on\n * each individual builder, but more convenient when every query in a block\n * must share the same transaction.\n *\n * @example\n * ```ts\n * const db = createQuery(knex);\n *\n * await knex.transaction(async trx => {\n * const dbTrx = db.withTransaction(trx);\n * const user = await dbTrx(UserSchema).insert({ name: 'Alice' });\n * await dbTrx(PostSchema).insert({ authorId: user.id, title: 'Hello' });\n * });\n * ```\n */\n withTransaction(trx: Knex.Transaction): BoundQuery;\n /**\n * Start a Knex transaction and run `callback` inside it, passing a\n * transaction-bound `BoundQuery` factory as the argument. The transaction\n * is committed when the callback resolves and rolled back if it rejects.\n *\n * This is the callback-style counterpart to {@link withTransaction} — you\n * don't need to obtain a `Knex.Transaction` object yourself.\n *\n * @param callback - An async function that receives a transaction-bound\n * `BoundQuery` and returns a value. The returned value is forwarded as\n * the resolved value of the outer `Promise`.\n * @returns A `Promise` that resolves with the value returned by `callback`.\n *\n * @example\n * ```ts\n * const db = createQuery(knex);\n *\n * const user = await db.transaction(async dbTrx => {\n * const newUser = await dbTrx(UserSchema).insert({ name: 'Alice' });\n * await dbTrx(PostSchema).insert({ authorId: newUser.id, title: 'Hello' });\n * return newUser;\n * });\n * ```\n */\n transaction<T>(callback: (db: BoundQuery) => Promise<T>): Promise<T>;\n}\n\n/**\n * Bind a Knex instance once and get back a `query(schema)` function that\n * doesn't require repeating the knex argument on every call.\n *\n * @param knex - A configured Knex instance.\n * @returns A bound query factory: `(schema, baseQuery?) => SchemaQueryBuilder`.\n *\n * @example\n * ```ts\n * import Knex from 'knex';\n * import { createQuery } from '@cleverbrush/knex-schema';\n *\n * const knex = Knex({ client: 'pg', connection: process.env.DB_URL });\n * const query = createQuery(knex);\n *\n * // No knex argument needed from here on\n * const users = await query(UserSchema).where(t => t.role, '=', 'admin');\n * const post = await query(PostSchema).where(t => t.id, '=', 42).first();\n *\n * // Optional base query (e.g. soft-delete scope applied globally)\n * const active = query(UserSchema, knex('users').where('deleted_at', null));\n * ```\n */\nexport function createQuery(knexInstance: Knex): BoundQuery {\n function boundQuery<\n TLocalSchema extends ObjectSchemaBuilder<\n any,\n any,\n any,\n any,\n any,\n any,\n any\n >\n >(\n schema: TLocalSchema,\n baseQuery?: Knex.QueryBuilder\n ): SchemaQueryBuilder<TLocalSchema, InferType<TLocalSchema>> {\n return baseQuery\n ? query(knexInstance, schema, baseQuery)\n : query(knexInstance, schema);\n }\n\n (boundQuery as BoundQuery).withTransaction = (\n trx: Knex.Transaction\n ): BoundQuery => createQuery(trx as unknown as Knex);\n\n (boundQuery as BoundQuery).transaction = <T>(\n callback: (db: BoundQuery) => Promise<T>\n ): Promise<T> =>\n knexInstance.transaction(trx =>\n callback(createQuery(trx as unknown as Knex))\n );\n\n return boundQuery as BoundQuery;\n}\n","// @cleverbrush/knex-schema — Validation of join specs\n\nimport type { ObjectSchemaBuilder } from '@cleverbrush/schema';\nimport type { Knex } from 'knex';\nimport { resolveColumnRef } from './columns.js';\nimport { getTableName } from './extension.js';\nimport { MAPPERS } from './mappers.js';\nimport type {\n JoinManySpec,\n JoinOneSpec,\n ValidatedJoinManySpec,\n ValidatedJoinOneSpec\n} from './types.js';\n\n/**\n * Resolve foreignQuery: use the provided one, or auto-derive from\n * the foreign schema's tableName extension.\n * Also normalizes SchemaQueryBuilder instances to raw Knex.QueryBuilder\n * by calling `.toKnexQuery()` if available.\n */\nfunction resolveForeignQuery(\n spec: {\n foreignQuery?: Knex.QueryBuilder | { toKnexQuery(): Knex.QueryBuilder };\n foreignSchema: ObjectSchemaBuilder<any, any, any, any, any, any, any>;\n },\n knex: Knex\n): Knex.QueryBuilder {\n if (spec.foreignQuery) {\n if ('toKnexQuery' in spec.foreignQuery) {\n return spec.foreignQuery.toKnexQuery();\n }\n return spec.foreignQuery as Knex.QueryBuilder;\n }\n\n const tableName = getTableName(spec.foreignSchema);\n return knex(tableName);\n}\n\nexport function validateJoinOne(\n spec: JoinOneSpec<any, any, any, any>,\n localSchema: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n knex: Knex\n): ValidatedJoinOneSpec {\n const localColumn = resolveColumnRef(\n spec.localColumn,\n localSchema,\n 'localColumn'\n );\n const foreignColumn = resolveColumnRef(\n spec.foreignColumn,\n spec.foreignSchema,\n 'foreignColumn'\n );\n if (typeof spec.as !== 'string' || !spec.as) {\n throw new Error('as must be a non-empty string');\n }\n\n const foreignQuery = resolveForeignQuery(spec, knex);\n const required = spec.required !== false;\n\n if (spec.mappers !== undefined) {\n validateMappers(spec.mappers);\n }\n\n return {\n localColumn,\n foreignColumn,\n as: spec.as,\n required,\n foreignQuery,\n mappers: spec.mappers as\n | Record<string, ((value: any) => any) | string>\n | undefined\n };\n}\n\nexport function validateJoinMany(\n spec: JoinManySpec<any, any, any>,\n localSchema: ObjectSchemaBuilder<any, any, any, any, any, any, any>,\n knex: Knex\n): ValidatedJoinManySpec {\n const localColumn = resolveColumnRef(\n spec.localColumn,\n localSchema,\n 'localColumn'\n );\n const foreignColumn = resolveColumnRef(\n spec.foreignColumn,\n spec.foreignSchema,\n 'foreignColumn'\n );\n if (typeof spec.as !== 'string' || !spec.as) {\n throw new Error('as must be a non-empty string');\n }\n\n const foreignQuery = resolveForeignQuery(spec, knex);\n\n if (spec.mappers !== undefined) {\n validateMappers(spec.mappers);\n }\n\n const limit =\n Number.isFinite(spec.limit) && (spec.limit as number) > 0\n ? (spec.limit as number)\n : null;\n const offset =\n Number.isFinite(spec.offset) && (spec.offset as number) > 0\n ? (spec.offset as number)\n : null;\n const orderBy = spec.orderBy\n ? {\n column: resolveColumnRef(\n spec.orderBy.column,\n spec.foreignSchema,\n 'orderBy.column'\n ),\n direction: spec.orderBy.direction ?? ('asc' as const)\n }\n : null;\n\n return {\n localColumn,\n foreignColumn,\n as: spec.as,\n foreignQuery,\n limit,\n offset,\n orderBy,\n mappers: spec.mappers as\n | Record<string, ((value: any) => any) | string>\n | undefined\n };\n}\n\nfunction validateMappers(mappers: Record<string, unknown>): void {\n if (typeof mappers !== 'object' || mappers === null) {\n throw new Error('mappers must be an object');\n }\n for (const key of Object.keys(mappers)) {\n const m = mappers[key];\n if (typeof m === 'string') {\n if (typeof MAPPERS[m] !== 'function') {\n throw new Error(\n `mapper for \"${key}\": unknown built-in mapper name \"${m}\"`\n );\n }\n } else if (typeof m !== 'function') {\n throw new Error(\n `mapper for \"${key}\" must be a function or a built-in mapper name`\n );\n }\n }\n}\n\nexport function validateUniqueFieldNames(specs: Array<{ as: string }>): void {\n const seen = new Set<string>();\n for (const spec of specs) {\n if (seen.has(spec.as)) {\n throw new Error(`duplicate field name: ${spec.as}`);\n }\n seen.add(spec.as);\n }\n}\n"],"mappings":"AAGA,OACI,uBAAAA,EACA,qCAAAC,MACG,sBCSP,OACI,mBAAAC,EACA,mBAAAC,EACA,oBAAAC,EACA,oBAAAC,EACA,kBAAAC,MACG,sBAWP,SAASC,EAAkDC,EAAc,CACrE,OAAO,KAAK,cAAc,aAAcA,CAAI,CAChD,CAOA,SAASC,EAELD,EACF,CACE,OAAO,KAAK,cAAc,YAAaA,CAAI,CAC/C,CAyBO,IAAME,EAAcP,EAAgB,CACvC,OAAQ,CAUJ,cAEIK,EACF,CACE,OAAOD,EAAc,KAAK,KAAMC,CAAI,CACxC,CACJ,EACA,OAAQ,CAKJ,cAEIA,EACF,CACE,OAAOD,EAAc,KAAK,KAAMC,CAAI,CACxC,CACJ,EACA,QAAS,CAKL,cAEIA,EACF,CACE,OAAOD,EAAc,KAAK,KAAMC,CAAI,CACxC,CACJ,EACA,KAAM,CAKF,cAEIA,EACF,CACE,OAAOD,EAAc,KAAK,KAAMC,CAAI,CACxC,CACJ,EACA,IAAK,CAKD,cAEIA,EACF,CACE,OAAOD,EAAc,KAAK,KAAMC,CAAI,CACxC,CACJ,EACA,KAAM,CAKF,cAEIA,EACF,CACE,OAAOD,EAAc,KAAK,KAAMC,CAAI,CACxC,CACJ,EACA,MAAO,CAKH,cAEIA,EACF,CACE,OAAOD,EAAc,KAAK,KAAMC,CAAI,CACxC,CACJ,EACA,MAAO,CAKH,cAEIA,EACF,CACE,OAAOD,EAAc,KAAK,KAAMC,CAAI,CACxC,CACJ,EACA,QAAS,CAKL,cAEIA,EACF,CACE,OAAOD,EAAc,KAAK,KAAMC,CAAI,CACxC,CACJ,EACA,OAAQ,CASJ,aAEIA,EACF,CACE,OAAOC,EAAa,KAAK,KAAMD,CAAI,CACvC,CACJ,CACJ,CAAC,EAMKG,EAAWL,EACbD,EACAD,EACAF,EACAQ,CACJ,EAEaE,EAASD,EAAS,OAClBE,EAASF,EAAS,OAClBG,EAAUH,EAAS,QACnBI,EAAOJ,EAAS,KAChBK,EAASL,EAAS,OAClBM,EAAQN,EAAS,MACjBO,EAAQP,EAAS,MACjBQ,EAAOR,EAAS,KAChBS,EAAMT,EAAS,IAUrB,SAASU,EACZC,EACAC,EACM,CACN,IAAMC,EAAMF,EAAO,aAAa,YAAY,EAC5C,OAAO,OAAOE,GAAQ,SAAWA,EAAMD,CAC3C,CAMO,SAASE,EACZH,EACM,CACN,IAAMI,EAAQJ,EAAO,aAAa,WAAW,EAC7C,GAAI,OAAOI,GAAU,SACjB,MAAM,IAAI,MACN,gFACJ,EAEJ,OAAOA,CACX,CD3OA,IAAMC,EAAiB,IAAI,QAOpB,SAASC,EACZC,EACe,CACf,IAAMC,EAASH,EAAe,IAAIE,CAAM,EACxC,GAAIC,EAAQ,OAAOA,EAGnB,IAAMC,EADeF,EAAO,WAAW,EAItB,YAAc,CAAC,EAE1BG,EAAY,IAAI,IAChBC,EAAY,IAAI,IAEtB,QAAWC,KAAW,OAAO,KAAKH,CAAU,EAAG,CAC3C,IAAMI,EAAaJ,EAAWG,CAAO,EAC/BE,EAAUC,EAAcF,EAAYD,CAAO,EAEjDF,EAAU,IAAIE,EAASE,CAAO,EAC9BH,EAAU,IAAIG,EAASF,CAAO,CAClC,CAEA,IAAMI,EAA0B,CAAE,UAAAN,EAAW,UAAAC,CAAU,EACvD,OAAAN,EAAe,IAAIE,EAAQS,CAAM,EAC1BA,CACX,CAcO,SAASC,EACZC,EACAX,EACAY,EACM,CACN,GAAI,OAAOD,GAAQ,SAAU,CACzB,GAAI,CAACA,EAAK,MAAM,IAAI,MAAM,GAAGC,CAAK,6BAA6B,EAC/D,GAAM,CAAE,UAAAT,CAAU,EAAIJ,EAAeC,CAAM,EAI3C,OAHYG,EAAU,IAAIQ,CAAG,GAGfA,CAClB,CAEA,GAAI,OAAOA,GAAQ,WAAY,CAC3B,IAAME,EAAOC,EAAoB,iBAAiBd,CAAa,EACzDe,EAAaJ,EAAIE,CAAW,EAElC,GACI,CAACE,GACD,OAAOA,GAAe,UACtB,EAAEC,KAAqCD,GAEvC,MAAM,IAAI,MACN,GAAGH,CAAK,mDACZ,EAGJ,IAAMK,EAASF,EAAmBC,CAAiC,EAE7Dd,EADeF,EAAO,WAAW,EACP,YAAc,CAAC,EAE/C,QAAWkB,KAAY,OAAO,KAAKhB,CAAU,EAAG,CAC5C,IAAMiB,EAAkBN,EAAaK,CAAQ,EAC7C,GACIC,GACCA,EAAuBH,CAAiC,IACrDC,EACN,CAEE,GAAM,CAAE,UAAAd,CAAU,EAAIJ,EAAeC,CAAM,EAC3C,OAAOG,EAAU,IAAIe,CAAQ,GAAKA,CACtC,CACJ,CAEA,MAAM,IAAI,MACN,GAAGN,CAAK,oDACZ,CACJ,CAEA,MAAM,IAAI,MACN,GAAGA,CAAK,8DACZ,CACJ,CAMO,SAASQ,GACZT,EACAX,EACAY,EACM,CACN,GAAI,OAAOD,GAAQ,SAAU,CACzB,GAAI,CAACA,EAAK,MAAM,IAAI,MAAM,GAAGC,CAAK,6BAA6B,EAC/D,OAAOD,CACX,CAEA,GAAI,OAAOA,GAAQ,WAAY,CAC3B,IAAME,EAAOC,EAAoB,iBAAiBd,CAAa,EACzDe,EAAaJ,EAAIE,CAAW,EAElC,GACI,CAACE,GACD,OAAOA,GAAe,UACtB,EAAEC,KAAqCD,GAEvC,MAAM,IAAI,MACN,GAAGH,CAAK,mDACZ,EAGJ,IAAMK,EAASF,EAAmBC,CAAiC,EAE7Dd,EADeF,EAAO,WAAW,EACP,YAAc,CAAC,EAE/C,QAAWkB,KAAY,OAAO,KAAKhB,CAAU,EAAG,CAC5C,IAAMiB,EAAkBN,EAAaK,CAAQ,EAC7C,GACIC,GACCA,EAAuBH,CAAiC,IACrDC,EAEJ,OAAOC,CAEf,CAEA,MAAM,IAAI,MACN,GAAGN,CAAK,oDACZ,CACJ,CAEA,MAAM,IAAI,MACN,GAAGA,CAAK,8DACZ,CACJ,CE1JO,IAAMS,EAA+C,CACxD,eAAiBC,GACRA,GACE,IAAI,KAAK,KAAK,MAAMA,CAAK,CAAC,CAEzC,EAiBO,SAASC,EAASC,EAAoCF,EAAiB,CAC1E,GAAI,OAAOE,GAAW,SAAU,CAC5B,GAAI,OAAOH,EAAQG,CAAM,GAAM,WAC3B,MAAM,IAAI,MAAM,mBAAmBA,CAAM,GAAG,EAEhD,OAAOH,EAAQG,CAAM,EAAEF,CAAK,CAChC,CACA,GAAI,OAAOE,GAAW,WAClB,OAAOA,EAAOF,CAAK,EAEvB,MAAM,IAAI,MAAM,oBAAoB,CACxC,CAoBO,SAASG,EACZC,EACAC,EACC,CACD,GAAI,CAACA,EAAS,MAAM,IAAI,MAAM,6BAA6B,EAC3D,GAAI,CAACD,GAAO,OAAOA,GAAQ,UAAYA,IAAQ,KAAM,OAAOA,EAE5D,IAAME,EAAS,CAAC,EAChB,QAAWC,KAAO,OAAO,KAAKH,CAAG,EAC7BE,EAAOC,CAAG,EAAIF,EAAQE,CAAG,EACnBN,EAASI,EAAQE,CAAG,EAAGH,EAAIG,CAAG,CAAC,EAC/BH,EAAIG,CAAG,EAEjB,OAAOD,CACX,CAuBO,SAASE,EACZC,EACAC,EACAC,EACmB,CACnB,QAAWC,KAAQF,EAAU,CACzB,IAAMG,EAAYD,EAAK,GAEnBA,EAAK,SACL,OAAOH,EAAII,CAAS,EAAM,KAC1BJ,EAAII,CAAS,IAAM,OAEnBJ,EAAII,CAAS,EAAIV,EAAUM,EAAII,CAAS,EAAGD,EAAK,OAAO,EAE/D,CAEA,QAAWA,KAAQD,EAAW,CAC1B,IAAME,EAAYD,EAAK,GACvB,GAAI,MAAM,QAAQH,EAAII,CAAS,CAAC,EAC5B,QAASC,EAAI,EAAGA,EAAIL,EAAII,CAAS,EAAE,OAAQC,IACnCL,EAAII,CAAS,EAAEC,CAAC,GACZF,EAAK,UACLH,EAAII,CAAS,EAAEC,CAAC,EAAIX,EAChBM,EAAII,CAAS,EAAEC,CAAC,EAChBF,EAAK,OACT,EAKpB,CAEA,OAAOH,CACX,CC1IA,OACI,uBAAAM,GACA,qCAAAC,OACG,sBCcP,SAASC,EACLC,EAIAC,EACiB,CACjB,GAAID,EAAK,aACL,MAAI,gBAAiBA,EAAK,aACfA,EAAK,aAAa,YAAY,EAElCA,EAAK,aAGhB,IAAME,EAAYC,EAAaH,EAAK,aAAa,EACjD,OAAOC,EAAKC,CAAS,CACzB,CAEO,SAASE,EACZJ,EACAK,EACAJ,EACoB,CACpB,IAAMK,EAAcC,EAChBP,EAAK,YACLK,EACA,aACJ,EACMG,EAAgBD,EAClBP,EAAK,cACLA,EAAK,cACL,eACJ,EACA,GAAI,OAAOA,EAAK,IAAO,UAAY,CAACA,EAAK,GACrC,MAAM,IAAI,MAAM,+BAA+B,EAGnD,IAAMS,EAAeV,EAAoBC,EAAMC,CAAI,EAC7CS,EAAWV,EAAK,WAAa,GAEnC,OAAIA,EAAK,UAAY,QACjBW,EAAgBX,EAAK,OAAO,EAGzB,CACH,YAAAM,EACA,cAAAE,EACA,GAAIR,EAAK,GACT,SAAAU,EACA,aAAAD,EACA,QAAST,EAAK,OAGlB,CACJ,CAEO,SAASY,EACZZ,EACAK,EACAJ,EACqB,CACrB,IAAMK,EAAcC,EAChBP,EAAK,YACLK,EACA,aACJ,EACMG,EAAgBD,EAClBP,EAAK,cACLA,EAAK,cACL,eACJ,EACA,GAAI,OAAOA,EAAK,IAAO,UAAY,CAACA,EAAK,GACrC,MAAM,IAAI,MAAM,+BAA+B,EAGnD,IAAMS,EAAeV,EAAoBC,EAAMC,CAAI,EAE/CD,EAAK,UAAY,QACjBW,EAAgBX,EAAK,OAAO,EAGhC,IAAMa,EACF,OAAO,SAASb,EAAK,KAAK,GAAMA,EAAK,MAAmB,EACjDA,EAAK,MACN,KACJc,EACF,OAAO,SAASd,EAAK,MAAM,GAAMA,EAAK,OAAoB,EACnDA,EAAK,OACN,KACJe,EAAUf,EAAK,QACf,CACI,OAAQO,EACJP,EAAK,QAAQ,OACbA,EAAK,cACL,gBACJ,EACA,UAAWA,EAAK,QAAQ,WAAc,KAC1C,EACA,KAEN,MAAO,CACH,YAAAM,EACA,cAAAE,EACA,GAAIR,EAAK,GACT,aAAAS,EACA,MAAAI,EACA,OAAAC,EACA,QAAAC,EACA,QAASf,EAAK,OAGlB,CACJ,CAEA,SAASW,EAAgBK,EAAwC,CAC7D,GAAI,OAAOA,GAAY,UAAYA,IAAY,KAC3C,MAAM,IAAI,MAAM,2BAA2B,EAE/C,QAAWC,KAAO,OAAO,KAAKD,CAAO,EAAG,CACpC,IAAME,EAAIF,EAAQC,CAAG,EACrB,GAAI,OAAOC,GAAM,UACb,GAAI,OAAOC,EAAQD,CAAC,GAAM,WACtB,MAAM,IAAI,MACN,eAAeD,CAAG,oCAAoCC,CAAC,GAC3D,UAEG,OAAOA,GAAM,WACpB,MAAM,IAAI,MACN,eAAeD,CAAG,gDACtB,CAER,CACJ,CAEO,SAASG,EAAyBC,EAAoC,CACzE,IAAMC,EAAO,IAAI,IACjB,QAAWtB,KAAQqB,EAAO,CACtB,GAAIC,EAAK,IAAItB,EAAK,EAAE,EAChB,MAAM,IAAI,MAAM,yBAAyBA,EAAK,EAAE,EAAE,EAEtDsB,EAAK,IAAItB,EAAK,EAAE,CACpB,CACJ,CD3FO,IAAMuB,EAAN,MAAMC,CAGX,CACWC,GACAC,GACAC,GACAC,GAA0B,CAAC,EAC3BC,GAKTC,GAAoC,KAUpC,YACIC,EACAC,EACAC,EACF,CACE,KAAKR,GAAQM,EACb,KAAKJ,GAAeK,EACpB,KAAKH,GAAaK,EAAaF,CAAW,EAC1C,KAAKN,GAAaO,GAAaF,EAAK,KAAKF,EAAU,CACvD,CAMAM,GAAeC,EAAUC,EAAQ,SAAkB,CAC/C,OAAOC,EACHF,EACA,KAAKT,GACLU,CACJ,CACJ,CAkDA,QAaIE,EAIF,CACE,IAAMC,EAAYC,EAAgBF,EAAM,KAAKZ,GAAc,KAAKF,EAAK,EACrE,YAAKG,GAAO,KAAK,CAAE,KAAM,MAAgB,GAAGY,CAAU,CAAC,EACvDE,EAAyB,KAAKd,EAAM,EAC7B,IACX,CAgDA,SAYIW,EAIF,CACE,IAAMC,EAAYG,EAAiBJ,EAAM,KAAKZ,GAAc,KAAKF,EAAK,EACtE,YAAKG,GAAO,KAAK,CAAE,KAAM,OAAiB,GAAGY,CAAU,CAAC,EACxDE,EAAyB,KAAKd,EAAM,EAC7B,IACX,CA4BA,MACIgB,KAKGC,EACC,CACJ,GACI,OAAOD,GAAgB,YACvB,CAAC,KAAKE,GAAkBF,CAAW,EAElC,KAAKlB,GAAW,MAAckB,EAAa,GAAGC,CAAI,UAEnD,OAAOD,GAAgB,UACvBA,IAAgB,MAChB,EAAE,UAAWA,GACf,CAEE,IAAMG,EAAS,KAAKC,GAChBJ,CACJ,EACC,KAAKlB,GAAW,MAAcqB,EAAQ,GAAGF,CAAI,CAClD,KAAO,CACH,IAAMI,EAAM,KAAKC,GAAkBN,CAAW,EAC7C,KAAKlB,GAAW,MAAcuB,EAAK,GAAGJ,CAAI,CAC/C,CACA,OAAO,IACX,CAgBA,SACID,KAKGC,EACC,CACJ,GACI,OAAOD,GAAgB,YACvB,CAAC,KAAKE,GAAkBF,CAAW,EAElC,KAAKlB,GAAW,SAAiBkB,EAAa,GAAGC,CAAI,UAEtD,OAAOD,GAAgB,UACvBA,IAAgB,MAChB,EAAE,UAAWA,GACf,CACE,IAAMG,EAAS,KAAKC,GAChBJ,CACJ,EACC,KAAKlB,GAAW,SAAiBqB,EAAQ,GAAGF,CAAI,CACrD,KAAO,CACH,IAAMI,EAAM,KAAKC,GAAkBN,CAAW,EAC7C,KAAKlB,GAAW,SAAiBuB,EAAK,GAAGJ,CAAI,CAClD,CACA,OAAO,IACX,CAeA,QACID,KAKGC,EACC,CACJ,GACI,OAAOD,GAAgB,YACvB,CAAC,KAAKE,GAAkBF,CAAW,EAElC,KAAKlB,GAAW,QAAgBkB,EAAa,GAAGC,CAAI,UAErD,OAAOD,GAAgB,UACvBA,IAAgB,MAChB,EAAE,UAAWA,GACf,CACE,IAAMG,EAAS,KAAKC,GAChBJ,CACJ,EACC,KAAKlB,GAAW,QAAgBqB,EAAQ,GAAGF,CAAI,CACpD,KAAO,CACH,IAAMI,EAAM,KAAKC,GAAkBN,CAAW,EAC7C,KAAKlB,GAAW,QAAgBuB,EAAK,GAAGJ,CAAI,CACjD,CACA,OAAO,IACX,CAeA,SACID,KAKGC,EACC,CACJ,GACI,OAAOD,GAAgB,YACvB,CAAC,KAAKE,GAAkBF,CAAW,EAElC,KAAKlB,GAAW,SAAiBkB,EAAa,GAAGC,CAAI,UAEtD,OAAOD,GAAgB,UACvBA,IAAgB,MAChB,EAAE,UAAWA,GACf,CACE,IAAMG,EAAS,KAAKC,GAChBJ,CACJ,EACC,KAAKlB,GAAW,SAAiBqB,EAAQ,GAAGF,CAAI,CACrD,KAAO,CACH,IAAMI,EAAM,KAAKC,GAAkBN,CAAW,EAC7C,KAAKlB,GAAW,SAAiBuB,EAAK,GAAGJ,CAAI,CAClD,CACA,OAAO,IACX,CAQA,QACIM,EACAC,EACI,CACJ,YAAK1B,GAAW,QACZ,KAAKS,GAAegB,EAAQ,SAAS,EACrCC,CACJ,EACO,IACX,CAQA,WACID,EACAC,EACI,CACJ,YAAK1B,GAAW,WACZ,KAAKS,GAAegB,EAAQ,YAAY,EACxCC,CACJ,EACO,IACX,CAMA,UACID,EACAC,EACI,CACJ,OAAC,KAAK1B,GAAmB,UACrB,KAAKS,GAAegB,EAAQ,WAAW,EACvCC,CACJ,EACO,IACX,CAMA,aACID,EACAC,EACI,CACJ,OAAC,KAAK1B,GAAmB,aACrB,KAAKS,GAAegB,EAAQ,cAAc,EAC1CC,CACJ,EACO,IACX,CAMA,UAAUD,EAAuC,CAC7C,YAAKzB,GAAW,UAAU,KAAKS,GAAegB,EAAQ,WAAW,CAAC,EAC3D,IACX,CAMA,aAAaA,EAAuC,CAChD,YAAKzB,GAAW,aACZ,KAAKS,GAAegB,EAAQ,cAAc,CAC9C,EACO,IACX,CAMA,YAAYA,EAAuC,CAC/C,OAAC,KAAKzB,GAAmB,YACrB,KAAKS,GAAegB,EAAQ,aAAa,CAC7C,EACO,IACX,CAMA,eAAeA,EAAuC,CAClD,OAAC,KAAKzB,GAAmB,eACrB,KAAKS,GAAegB,EAAQ,gBAAgB,CAChD,EACO,IACX,CAOA,aACIA,EACAE,EACI,CACJ,YAAK3B,GAAW,aACZ,KAAKS,GAAegB,EAAQ,cAAc,EAC1CE,CACJ,EACO,IACX,CAOA,gBACIF,EACAE,EACI,CACJ,YAAK3B,GAAW,gBACZ,KAAKS,GAAegB,EAAQ,iBAAiB,EAC7CE,CACJ,EACO,IACX,CAOA,UAAUF,EAAiCG,EAAqB,CAC5D,OAAC,KAAK5B,GAAmB,UACrB,KAAKS,GAAegB,EAAQ,WAAW,EACvCG,CACJ,EACO,IACX,CAOA,WAAWH,EAAiCG,EAAqB,CAC7D,OAAC,KAAK5B,GAAmB,WACrB,KAAKS,GAAegB,EAAQ,YAAY,EACxCG,CACJ,EACO,IACX,CAQA,SAASC,KAAgBC,EAAuB,CAC5C,YAAK9B,GAAW,SAAS6B,EAAK,GAAGC,CAAQ,EAClC,IACX,CAOA,YAAYC,EAAwD,CAChE,YAAK/B,GAAW,YAAY+B,CAAe,EACpC,IACX,CAiBA,QACIN,EACAO,EACI,CACJ,IAAMT,EAAM,KAAKC,GAAkBC,CAAM,EACzC,YAAKzB,GAAW,QAAQuB,EAAeS,CAAS,EACzC,IACX,CAOA,WAAWH,KAAgBC,EAAuB,CAC9C,YAAK9B,GAAW,WAAW6B,EAAK,GAAGC,CAAQ,EACpC,IACX,CAWA,WAAWG,EAAuD,CAC9D,IAAMC,EAAWD,EAAQ,IAAIE,GAAK,KAAKX,GAAkBW,CAAC,CAAC,EAC3D,YAAKnC,GAAW,QAAQ,GAAIkC,CAAqB,EAC1C,IACX,CAMA,WAAWL,KAAgBC,EAAuB,CAC9C,YAAK9B,GAAW,WAAW6B,EAAK,GAAGC,CAAQ,EACpC,IACX,CAMA,OACIL,EACAW,EACAR,EACI,CACJ,IAAML,EAAM,KAAKC,GAAkBC,CAAM,EACzC,YAAKzB,GAAW,OAAOuB,EAAea,EAAUR,CAAK,EAC9C,IACX,CAMA,UAAUC,KAAgBC,EAAuB,CAC7C,YAAK9B,GAAW,UAAU6B,EAAK,GAAGC,CAAQ,EACnC,IACX,CAWA,MAAMO,EAAiB,CACnB,YAAKrC,GAAW,MAAMqC,CAAC,EAChB,IACX,CAOA,OAAOA,EAAiB,CACpB,YAAKrC,GAAW,OAAOqC,CAAC,EACjB,IACX,CAYA,UAAUJ,EAAuD,CAC7D,IAAMC,EAAWD,EAAQ,IAAIE,GAAK,KAAKX,GAAkBW,CAAC,CAAC,EAC3D,KAAKnC,GAAW,OAAO,GAAIkC,CAAqB,EAEhD,KAAK9B,KAAqB,CAAC,EAC3B,QAAWkC,KAAKJ,EACR,OAAOI,GAAM,UACb,KAAKlC,GAAiB,KAAKkC,CAAC,EAGpC,OAAO,IACX,CAOA,YAAYL,EAAuD,CAC/D,IAAMC,EAAWD,EAAQ,IAAIE,GAAK,KAAKX,GAAkBW,CAAC,CAAC,EAC3D,YAAKnC,GAAW,SAAS,GAAIkC,CAAqB,EAC3C,IACX,CAWA,MAAMT,EAAmD,CACrD,OAAIA,EACA,KAAKzB,GAAW,MAAM,KAAKwB,GAAkBC,CAAM,CAAW,EAE9D,KAAKzB,GAAW,MAAM,EAEnB,IACX,CAOA,cAAcyB,EAAmD,CAC7D,OAAIA,EACA,KAAKzB,GAAW,cACZ,KAAKwB,GAAkBC,CAAM,CACjC,EAEA,KAAKzB,GAAW,cAAc,EAE3B,IACX,CAMA,IAAIyB,EAAkD,CAClD,YAAKzB,GAAW,IAAI,KAAKwB,GAAkBC,CAAM,CAAW,EACrD,IACX,CAMA,IAAIA,EAAkD,CAClD,YAAKzB,GAAW,IAAI,KAAKwB,GAAkBC,CAAM,CAAW,EACrD,IACX,CAMA,IAAIA,EAAkD,CAClD,YAAKzB,GAAW,IAAI,KAAKwB,GAAkBC,CAAM,CAAW,EACrD,IACX,CAMA,IAAIA,EAAkD,CAClD,YAAKzB,GAAW,IAAI,KAAKwB,GAAkBC,CAAM,CAAW,EACrD,IACX,CAsBA,MAAM,OAAOc,EAAkD,CAC3D,IAAMlB,EAAS,KAAKmB,GAAoBD,CAA2B,EAC7D,CAACE,CAAG,EAAI,MAAM,KAAK1C,GAAM,KAAKI,EAAU,EACzC,OAAOkB,CAAM,EACb,UAAU,GAAG,EAClB,OAAO,KAAKqB,GAAQD,CAAG,CAC3B,CASA,MAAM,WAAWF,EAAsD,CACnE,IAAMlB,EAASkB,EAAK,IAAII,GACpB,KAAKH,GAAoBG,CAAwB,CACrD,EAIA,OAHa,MAAM,KAAK5C,GAAM,KAAKI,EAAU,EACxC,OAAOkB,CAAM,EACb,UAAU,GAAG,GACN,IAAKoB,GAAa,KAAKC,GAAQD,CAAG,CAAY,CAC9D,CAmBA,MAAM,OAAOF,EAA4D,CACrE,IAAMlB,EAAS,KAAKmB,GAAoBD,CAA2B,EAEnE,OADa,MAAM,KAAKvC,GAAW,OAAOqB,CAAM,EAAE,UAAU,GAAG,GACnD,IAAKoB,GAAa,KAAKC,GAAQD,CAAG,CAAY,CAC9D,CAWA,MAAM,QAA0B,CAC5B,OAAO,KAAKzC,GAAW,OAAO,CAClC,CAqBA,MAAM4C,EAAgD,CAClD,OAAAA,EAAG,KAAK5C,EAAU,EACX,IACX,CAmCA,YACI6C,EACyC,CACzC,IAAMC,EAAU,IAAIhD,EAChB+C,EACA,KAAK5C,GACL,KAAKD,GAAW,MAAM,EAAE,YAAY6C,CAAG,CAC3C,EACA,QAAWhC,KAAQ,KAAKX,GACpB4C,EAAQ5C,GAAO,KAAK,CAChB,GAAGW,EACH,aAAcA,EAAK,aAAa,MAAM,EAAE,YAAYgC,CAAG,CAC3D,CAAC,EAEL,OAAAC,EAAQ1C,GAAmB,KAAKA,GAC1B,CAAC,GAAG,KAAKA,EAAgB,EACzB,KACC0C,CACX,CAMAC,IAAiC,CAC7B,GAAI,KAAK7C,GAAO,SAAW,EACvB,OAAO,KAAKF,GAGhB,IAAMK,EAAO,KAAKN,GACZiD,EAAQ,KAAK9C,GAGb+C,EAAuB,CACzB,GAAG,IAAI,IAAID,EAAM,IAAI,GAAK,EAAE,WAAW,CAAC,CAC5C,EAOIE,EAAW,KAAKlD,GAChBmD,EAAyB,CAAC,EAE9B,GAAI,KAAK/C,KAAqB,KAAM,CAChC,IAAMgD,EAAc,IAAI,IAAI,KAAKhD,EAAgB,EAIjD,GAHA+C,EAAeF,EAAqB,OAChC1B,GAAO,CAAC6B,EAAY,IAAI7B,CAAG,CAC/B,EACI4B,EAAa,OAAS,EAAG,CACzBD,EAAW,KAAKlD,GAAW,MAAM,EACjC,QAAWuB,KAAO4B,EACdD,EAAS,OAAO3B,CAAG,CAE3B,CACJ,CAMA,IAAM8B,EAAchD,EAAK,aAAa,EAAE,KAAK,gBAAiB6C,CAAQ,EAEtE,GAAIC,EAAa,OAAS,GAAK,KAAK/C,KAAqB,KAErD,QAAWmB,KAAO,KAAKnB,GACnBiD,EAAY,OACRhD,EAAK,IAAI,iCAAkC,CACvC,cAAe,gBACf,IAAAkB,CACJ,CAAC,CACL,OAGJ8B,EAAY,OAAO,iBAAiB,EAGxCA,EAAY,KACRhD,EAAK,IAAI,kBAAmB,CACxB,cAAe,eACnB,CAAC,CACL,EAEA,QAASiD,EAAI,EAAGA,EAAIN,EAAM,OAAQM,IAAK,CACnC,IAAMzC,EAAOmC,EAAMM,CAAC,EACdC,EAAgB,gBAAgBD,CAAC,GAEnCzC,EAAK,OAAS,MACd,KAAK2C,GAAcH,EAAaxC,EAAM0C,CAAa,EAEnD,KAAKE,GAAeJ,EAAaxC,EAAM0C,EAAeD,CAAC,CAE/D,CAEA,OAAOD,CACX,CAEAG,GACIH,EACAxC,EACA0C,EACI,CACJ,IAAMlD,EAAO,KAAKN,GACZ2D,EAAe7C,EAAK,aACpB8C,EAAoBD,EAAqB,SAAS,MAExD,GAAI,CAACC,EACD,MAAM,IAAI,MACN,yDAAyD9C,EAAK,EAAE,6DAEpE,EAGJwC,EAAY,OACRhD,EAAK,IAAI,kCAAmC,CACxC,cAAAkD,EACA,GAAI1C,EAAK,EACb,CAAC,CACL,EAEA,IAAM+C,EAAWvD,EACZ,KAAKqD,EAAa,GAAGC,CAAgB,CAAC,EACtC,OACGtD,EAAK,IAAI,iCAAkC,CACvC,aAAcsD,EACd,cAAe9C,EAAK,aACxB,CAAC,CACL,EACC,OACGR,EAAK,IAAI,oCAAqC,CAC1C,aAAcsD,EACd,GAAI9C,EAAK,EACb,CAAC,CACL,EACC,WAAW,iCAAkC,CAC1C,aAAc8C,EACd,cAAe9C,EAAK,aACxB,CAAC,EACA,GAAG0C,CAAa,EAEfM,EAAahD,EAAK,SAAW,OAAS,WAC5CwC,EAAYQ,CAAU,EAAED,EAAU,UAAY,CAC1C,KAAK,GACDvD,EAAK,IACD,kEACA,CACI,cAAe,gBACf,cAAAkD,EACA,cAAe1C,EAAK,cACpB,YAAaA,EAAK,WACtB,CACJ,CACJ,CACJ,CAAC,CACL,CAEA4C,GACIJ,EACAxC,EACA0C,EACAD,EACI,CACJ,IAAMjD,EAAO,KAAKN,GACZ+D,EAAa,aAAaR,CAAC,GAE3BS,EACDlD,EAAK,QAAU,MAAQA,EAAK,MAAQ,GACpCA,EAAK,SAAW,MAAQA,EAAK,OAAS,EAErCmD,EAAgBnD,EAAK,QACrBA,EAAK,QAAQ,OACbA,EAAK,cACLoD,EAAmBpD,EAAK,QAAUA,EAAK,QAAQ,UAAY,MAE7DkD,EACAV,EAAY,KACRS,EACAzD,EACK,KACGQ,EAAK,aACA,MAAM,EACN,QACGA,EAAK,cACLR,EACK,KACGA,EAAK,IAAI,kBAAmB,CACxB,cAAe,eACnB,CAAC,CACL,EACC,SAASQ,EAAK,WAAW,CAClC,EACC,GAAG,cAAcyC,CAAC,EAAE,CAC7B,EACC,OAAO,cAAcA,CAAC,IAAI,EAC1B,OACGjD,EAAK,IACD,2EAA2E4D,CAAgB,gBAC3F,CACI,cAAepD,EAAK,cACpB,cAAAmD,CACJ,CACJ,CACJ,CACR,EAEAX,EAAY,KACRS,EACAjD,EAAK,aAAa,MAAM,EAAE,QACtBA,EAAK,cACLR,EACK,KACGA,EAAK,IAAI,kBAAmB,CACxB,cAAe,eACnB,CAAC,CACL,EACC,SAASQ,EAAK,WAAW,CAClC,CACJ,EAGJ,IAAMqD,EAAc7D,EAAK,KAAKyD,CAAU,EAExC,GAAIC,EAAgB,CAChB,IAAMI,EAAWtD,EAAK,QAAU,MAAQA,EAAK,MAAQ,EAC/CuD,EAAYvD,EAAK,SAAW,MAAQA,EAAK,OAAS,EAClDwD,EAAkBxD,EAAK,QAAU,EACjCyD,EAAiBD,GAAmBxD,EAAK,OAAS,GAElD0D,EACFJ,GAAYC,EACN,4CACAD,EACE,qBACA,qBAEZD,EAAY,SAASK,EAAW,CAC5B,MAAOD,EACP,OAAQD,CACZ,CAAC,EAEDH,EAAY,OACR7D,EAAK,IAAI,kBAAmB,CACxB,cAAeQ,EAAK,aACxB,CAAC,CACL,EACAqD,EAAY,OACR7D,EAAK,IACD,gGACA,CAAE,WAAAyD,EAAY,GAAIjD,EAAK,EAAG,CAC9B,CACJ,CACJ,KAAO,CACHqD,EAAY,OACR7D,EAAK,IAAI,kBAAmB,CACxB,cAAeQ,EAAK,aACxB,CAAC,CACL,EAEA,IAAM2D,EAAc3D,EAAK,QACnB,gEAAgEoD,CAAgB,IAChF,0BAENC,EAAY,OACR7D,EAAK,IACD,YAAYmE,CAAW,yBACvB3D,EAAK,QACC,CAAE,WAAAiD,EAAY,cAAAE,EAAe,GAAInD,EAAK,EAAG,EACzC,CAAE,WAAAiD,EAAY,GAAIjD,EAAK,EAAG,CACpC,CACJ,CACJ,CAEAqD,EAAY,WAAW,kBAAmB,CACtC,cAAerD,EAAK,aACxB,CAAC,EAED,IAAM+C,EAAWM,EAAY,GAAGX,CAAa,EAE7CF,EAAY,OACRhD,EAAK,IAAI,sDAAuD,CAC5D,cAAAkD,EACA,GAAI1C,EAAK,EACb,CAAC,CACL,EAEAwC,EAAY,SAASO,EAAU,UAAY,CACvC,KAAK,GACDvD,EAAK,IACD,kEACA,CACI,cAAAkD,EACA,cAAe1C,EAAK,cACpB,cAAe,gBACf,YAAaA,EAAK,WACtB,CACJ,CACJ,CACJ,CAAC,CACL,CAUA6B,GAAQD,EAA+C,CACnD,GAAI,CAACA,EAAK,OAAOA,EAEjB,GAAM,CAAE,UAAAgC,CAAU,EAAIC,EAAe,KAAKzE,EAAY,EAChD0E,EAA8B,CAAC,EAGrC,OAAW,CAACC,EAAShD,CAAK,IAAK,OAAO,QAAQa,CAAG,EAAG,CAChD,IAAMoC,EAAWJ,EAAU,IAAIG,CAAO,EAClCC,EACAF,EAAOE,CAAQ,EAAIjD,EAGnB+C,EAAOC,CAAO,EAAIhD,CAE1B,CAEA,OAAO+C,CACX,CAKAG,GAAgBrC,EAA+C,CAC3D,IAAMsC,EAAW,KAAK7E,GAAO,OACxB8E,GAA4CA,EAAE,OAAS,KAC5D,EACMC,EAAY,KAAK/E,GAAO,OACzB8E,GAA6CA,EAAE,OAAS,MAC7D,EACME,EAAUC,EAAS1C,EAAKsC,EAAUE,CAAS,EACjD,OAAO,KAAKvC,GAAQwC,CAAO,CAC/B,CAUA1C,GAAoB4C,EAA+C,CAC/D,GAAM,CAAE,UAAAC,CAAU,EAAIX,EAAe,KAAKzE,EAAY,EAChD0E,EAA8B,CAAC,EAErC,OAAW,CAACW,EAAK1D,CAAK,IAAK,OAAO,QAAQwD,CAAG,EAAG,CAC5C,IAAMR,EAAUS,EAAU,IAAIC,CAAG,EAC7BV,EACAD,EAAOC,CAAO,EAAIhD,EAGlB+C,EAAOW,CAAG,EAAI1D,CAEtB,CAEA,OAAO+C,CACX,CAMArD,GAAoBiE,EAAkD,CAClE,OAAO,KAAK/C,GAAoB+C,CAAM,CAC1C,CAMA/D,GAAkBD,EAA6B,CAC3C,OAAI,OAAOA,GAAQ,SACR,KAAKd,GAAec,EAAK,QAAQ,EAExC,OAAOA,GAAQ,WAER,KAAKd,GAAec,EAAK,QAAQ,EAGrCA,CACX,CAiBAH,GAAkBwB,EAAuB,CAErC,GAAI,CACA,IAAM4C,EAAOC,GAAoB,iBAC7B,KAAKxF,EACT,EACM0E,EAAS/B,EAAG4C,CAAI,EAEtB,GACIb,GACA,OAAOA,GAAW,UAClBe,MAAqCf,EAErC,MAAO,EAEf,MAAQ,CAER,CACA,MAAO,EACX,CAUA,SAAkB,CACd,OAAO,KAAK5B,GAAY,EAAE,QAAQ,CACtC,CAOA,aAAiC,CAC7B,OAAO,KAAKA,GAAY,CAC5B,CAKA,UAAmB,CACf,OAAO,KAAKA,GAAY,EAAE,SAAS,CACvC,CAcA,MAAM,SAA8B,CAEhC,IAAM4C,EAAO,MADC,KAAK5C,GAAY,EAG/B,OAAK4C,EACA,MAAM,QAAQA,CAAI,EAGhBA,EAAK,IAAKlD,GAAa,KAAKqC,GAAgBrC,CAAG,CAAC,EAF5C,CAAC,KAAKqC,GAAgBa,CAAI,CAAC,EAFpB,CAAC,CAKvB,CAYA,MAAM,OAAsC,CAExC,IAAMlD,EAAM,MADE,KAAKM,GAAY,EAAE,MAAM,EAGvC,GAAKN,EACL,OAAO,KAAKqC,GAAgBrC,CAAG,CACnC,CAaA,KACImD,EAGAC,EAC4B,CAC5B,OAAO,KAAK,QAAQ,EAAE,KAAKD,EAAaC,CAAU,CACtD,CACJ,EA8DO,SAASC,EAGZzF,EACA0F,EACAxF,EACyD,CACzD,OAAO,IAAIV,EACPQ,EACA0F,EACAxF,CACJ,CACJ,CAuGO,SAASyF,EAAYC,EAAgC,CACxD,SAASC,EAWLH,EACAxF,EACyD,CACzD,OAAOA,EACDuF,EAAMG,EAAcF,EAAQxF,CAAS,EACrCuF,EAAMG,EAAcF,CAAM,CACpC,CAEA,OAACG,EAA0B,gBACvBrD,GACamD,EAAYnD,CAAsB,EAElDqD,EAA0B,YACvBnE,GAEAkE,EAAa,YAAYpD,GACrBd,EAASiE,EAAYnD,CAAsB,CAAC,CAChD,EAEGqD,CACX","names":["ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","arrayExtensions","defineExtension","numberExtensions","stringExtensions","withExtensions","hasColumnName","name","hasTableName","dbExtension","extended","string","number","boolean","date","object","array","union","func","any","getColumnName","schema","propertyKey","col","getTableName","table","columnMapCache","buildColumnMap","schema","cached","properties","propToCol","colToProp","propKey","propSchema","colName","getColumnName","result","resolveColumnRef","ref","label","tree","ObjectSchemaBuilder","descriptor","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","inner","propName","propDescriptor","resolvePropertyKey","MAPPERS","value","mapValue","mapper","mapObject","obj","mappers","result","key","clearRow","row","oneSpecs","manySpecs","spec","fieldName","k","ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","resolveForeignQuery","spec","knex","tableName","getTableName","validateJoinOne","localSchema","localColumn","resolveColumnRef","foreignColumn","foreignQuery","required","validateMappers","validateJoinMany","limit","offset","orderBy","mappers","key","m","MAPPERS","validateUniqueFieldNames","specs","seen","SchemaQueryBuilder","_SchemaQueryBuilder","#knex","#baseQuery","#localSchema","#specs","#tableName","#explicitSelects","knex","localSchema","baseQuery","getTableName","#resolveColumn","ref","label","resolveColumnRef","spec","validated","validateJoinOne","validateUniqueFieldNames","validateJoinMany","columnOrRaw","args","#isColumnAccessor","mapped","#mapRecordToColumns","col","#resolveColumnArg","column","values","range","value","sql","bindings","callback","direction","columns","resolved","c","operator","n","r","data","#mapObjectToColumns","row","#mapRow","d","fn","trx","builder","#buildQuery","specs","requiredLocalColumns","cteQuery","extraColumns","selectedSet","resultQuery","i","relationAlias","#buildJoinOne","#buildJoinMany","foreignTable","foreignTableName","subquery","joinMethod","filterName","hasLimitOffset","orderByColumn","orderByDirection","aggSubquery","hasLimit","hasOffset","effectiveOffset","effectiveLimit","condition","orderClause","colToProp","buildColumnMap","result","colName","propName","#cleanAndMapRow","oneSpecs","s","manySpecs","cleaned","clearRow","obj","propToCol","key","record","tree","ObjectSchemaBuilder","SYMBOL_SCHEMA_PROPERTY_DESCRIPTOR","rows","onfulfilled","onrejected","query","schema","createQuery","knexInstance","boundQuery"]}
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "email": "andrew_zol@cleverbrush.com"
6
6
  },
7
7
  "dependencies": {
8
- "@cleverbrush/schema": "^3.0.0"
8
+ "@cleverbrush/schema": "^3.1.0"
9
9
  },
10
10
  "peerDependencies": {
11
11
  "knex": ">=3.1.0"
@@ -48,5 +48,5 @@
48
48
  },
49
49
  "type": "module",
50
50
  "types": "./dist/index.d.ts",
51
- "version": "3.0.0"
51
+ "version": "3.1.0"
52
52
  }