uql-orm 0.28.2 → 0.30.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.
Files changed (48) hide show
  1. package/dist/browser/querier/httpQuerier.d.ts +5 -5
  2. package/dist/browser/uql-browser.min.js +2 -2
  3. package/dist/browser/uql-browser.min.js.map +3 -3
  4. package/dist/dialect/abstractSqlDialect.d.ts +6 -4
  5. package/dist/dialect/abstractSqlDialect.js +29 -29
  6. package/dist/dialect/mysqlLikeSqlDialect.d.ts +3 -1
  7. package/dist/dialect/mysqlLikeSqlDialect.js +9 -0
  8. package/dist/dialect/vectorSqlDialect.js +8 -1
  9. package/dist/mongo/mongoDialect.d.ts +3 -3
  10. package/dist/mongo/mongoDialect.js +17 -9
  11. package/dist/mongo/mongodbQuerier.d.ts +9 -4
  12. package/dist/mongo/mongodbQuerier.js +15 -7
  13. package/dist/mysql/mysql2Querier.d.ts +1 -1
  14. package/dist/mysql/mysql2Querier.js +7 -1
  15. package/dist/nestjs/uqlContextInterceptor.d.ts +2 -2
  16. package/dist/nestjs/uqlContextInterceptor.js +2 -2
  17. package/dist/nestjs/uqlModule.d.ts +3 -3
  18. package/dist/nestjs/uqlModule.js +5 -6
  19. package/dist/postgres/abstractPgQuerier.d.ts +3 -2
  20. package/dist/postgres/abstractPgQuerier.js +2 -2
  21. package/dist/querier/abstractPoolQuerier.d.ts +2 -2
  22. package/dist/querier/abstractPoolQuerier.js +7 -7
  23. package/dist/querier/abstractQuerier.d.ts +45 -16
  24. package/dist/querier/abstractQuerier.js +99 -40
  25. package/dist/querier/abstractQuerierPool.d.ts +7 -7
  26. package/dist/querier/abstractSqlQuerier.d.ts +17 -6
  27. package/dist/querier/abstractSqlQuerier.js +61 -34
  28. package/dist/querier/index.d.ts +0 -2
  29. package/dist/querier/index.js +0 -2
  30. package/dist/sqlite/abstractSqliteQuerier.d.ts +1 -2
  31. package/dist/sqlite/abstractSqliteQuerier.js +2 -8
  32. package/dist/sqlite/hranaQuerier.d.ts +5 -0
  33. package/dist/sqlite/hranaQuerier.js +10 -6
  34. package/dist/type/entity.d.ts +34 -11
  35. package/dist/type/querier.d.ts +26 -10
  36. package/dist/type/query.d.ts +22 -9
  37. package/dist/type/queryAggregate.d.ts +67 -24
  38. package/dist/type/queryWhere.d.ts +4 -1
  39. package/dist/type/universalQuerier.d.ts +11 -11
  40. package/dist/util/dialect.util.d.ts +26 -4
  41. package/dist/util/dialect.util.js +42 -0
  42. package/package.json +4 -5
  43. package/dist/querier/querierContext.browser.d.ts +0 -12
  44. package/dist/querier/querierContext.browser.js +0 -18
  45. package/dist/querier/querierContext.d.ts +0 -22
  46. package/dist/querier/querierContext.js +0 -42
  47. package/dist/querier/transactional.d.ts +0 -26
  48. package/dist/querier/transactional.js +0 -43
@@ -1,5 +1,5 @@
1
1
  import { type HttpMethod, type RequestCountedSuccessResponse, type RequestSuccessResponse } from '../../http/contract.js';
2
- import type { IdValue, Query, QueryOne, QueryOptions, QuerySearch, Type, UpdatePayload } from '../../type/index.js';
2
+ import type { EntityData, IdValue, Query, QueryOne, QueryOptions, QuerySearch, Type, UpdatePayload } from '../../type/index.js';
3
3
  import type { ClientQuerier, RequestFindOptions, RequestOptions } from '../type/index.js';
4
4
  export type HttpQuerierDefaults = {
5
5
  /**
@@ -23,12 +23,12 @@ export declare class HttpQuerier implements ClientQuerier {
23
23
  findMany<E extends object>(entity: Type<E>, q: Query<E>, opts?: RequestFindOptions): Promise<RequestSuccessResponse<E[]>>;
24
24
  findManyAndCount<E extends object>(entity: Type<E>, q: Query<E>, opts?: RequestFindOptions): Promise<RequestCountedSuccessResponse<E[]>>;
25
25
  count<E extends object>(entity: Type<E>, q?: QuerySearch<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<number>>;
26
- insertOne<E extends object>(entity: Type<E>, payload: E, opts?: RequestOptions): Promise<RequestSuccessResponse<IdValue<E> | undefined>>;
27
- insertMany<E extends object>(entity: Type<E>, payload: E[], opts?: RequestOptions): Promise<RequestSuccessResponse<IdValue<E>[]>>;
26
+ insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<IdValue<E> | undefined>>;
27
+ insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions): Promise<RequestSuccessResponse<IdValue<E>[]>>;
28
28
  updateOneById<E extends object>(entity: Type<E>, id: IdValue<E>, payload: UpdatePayload<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<number>>;
29
29
  updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<number>>;
30
- saveOne<E extends object>(entity: Type<E>, payload: E, opts?: RequestOptions): Promise<RequestSuccessResponse<IdValue<E>>>;
31
- saveMany<E extends object>(entity: Type<E>, payload: E[], opts?: RequestOptions): Promise<RequestSuccessResponse<IdValue<E>[]>>;
30
+ saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<IdValue<E>>>;
31
+ saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions): Promise<RequestSuccessResponse<IdValue<E>[]>>;
32
32
  deleteOneById<E extends object>(entity: Type<E>, id: IdValue<E>, opts?: QueryOptions & RequestOptions): Promise<RequestSuccessResponse<number>>;
33
33
  deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions & RequestOptions): Promise<RequestSuccessResponse<number>>;
34
34
  getBasePath<E>(entity: Type<E>): string;
@@ -1,4 +1,4 @@
1
- var W=[];function F(B){for(let V of W)V(B)}function _(B){W.push(B);let V=W.length-1;return()=>{W.splice(V,1)}}class f extends Error{status;constructor(B,V){super(B);this.status=V;this.name="RequestError"}}function N(B,V){return G(B,{method:"get"},V)}function Q(B,V,I){let X=JSON.stringify(V);return G(B,{method:"post",body:X},I)}function J(B,V,I){let X=JSON.stringify(V);return G(B,{method:"patch",body:X},I)}function L(B,V,I){let X=JSON.stringify(V);return G(B,{method:"put",body:X},I)}function x(B,V){return G(B,{method:"delete"},V)}function T(B,V,I){let X=JSON.stringify(V);return G(B,{method:"QUERY",body:X},I)}function G(B,V,I){if(F({phase:"start",opts:I}),V.headers={accept:"application/json","content-type":"application/json",...I?.headers},I?.signal)V.signal=I.signal;return fetch(B,V).then((X)=>X.json().then((Z)=>{if(X.status>=200&&X.status<300)return F({phase:"success",opts:I}),Z;let Y=Z,M={message:Y?.error?.message??X.statusText,code:Y?.error?.code??X.status};throw F({phase:"error",error:M,opts:I}),new f(M.message,M.code)})).finally(()=>{F({phase:"complete",opts:I})})}function z(B){return B?Object.keys(B):[]}function K(B){let V=B.charAt(0).toLowerCase();for(let I=1;I<B.length;++I)V+=B[I]===B[I].toUpperCase()?"-"+B[I].toLowerCase():B[I];return V}var $={findMany:{method:"GET",path:""},findOne:{method:"GET",path:"/one"},count:{method:"GET",path:"/count"},findOneById:{method:"GET",path:"/:id"},insertOne:{method:"POST",path:""},insertMany:{method:"POST",path:"/many"},saveOne:{method:"PUT",path:""},saveMany:{method:"PUT",path:"/many"},updateMany:{method:"PATCH",path:""},updateOneById:{method:"PATCH",path:"/:id"},deleteOneById:{method:"DELETE",path:"/:id"},deleteMany:{method:"DELETE",path:""}},k=z($),b=new Map(k.filter((B)=>$[B].method==="GET"&&$[B].path!=="/:id").map((B)=>[$[B].path,B]));function D(B){return K(B.name)}var c=["$select","$populate","$exclude","$where","$sort"],v=new Set([...c,"$skip","$limit","hardDelete","count"]);function A(B){if(!B)return"";let V=new URLSearchParams;for(let X of z(B)){let Z=B[X];if(Z===void 0)continue;V.append(X,typeof Z==="object"&&Z!==null?JSON.stringify(Z):String(Z))}let I=V.toString();return I?`?${I}`:""}class C{basePath;defaults;constructor(B,V={}){this.basePath=B;this.defaults=V}findOneById(B,V,I,X){let Z=this.getBasePath(B),H=A(I);return N(`${Z}/${V}${H}`,this.buildOptions(X))}findOne(B,V,I){return this.read(`${this.getBasePath(B)}${$.findOne.path}`,V,I)}findMany(B,V,I){let X={...V};if(I?.count)X.count=!0;return this.read(this.getBasePath(B),X,I)}async findManyAndCount(B,V,I){let X=await this.findMany(B,V,{...I,count:!0});if(typeof X.count!=="number")throw TypeError("findManyAndCount response has an invalid count");return{...X,count:X.count}}count(B,V,I){return this.read(`${this.getBasePath(B)}${$.count.path}`,V,I)}insertOne(B,V,I){let X=this.getBasePath(B);return Q(X,V,this.buildOptions(I))}insertMany(B,V,I){let X=this.getBasePath(B);return Q(`${X}${$.insertMany.path}`,V,this.buildOptions(I))}updateOneById(B,V,I,X){let Z=this.getBasePath(B);return J(`${Z}/${V}`,I,this.buildOptions(X))}updateMany(B,V,I,X){let Z=this.getBasePath(B),H=A(V);return J(`${Z}${H}`,I,this.buildOptions(X))}saveOne(B,V,I){let X=this.getBasePath(B);return L(X,V,this.buildOptions(I))}saveMany(B,V,I){let X=this.getBasePath(B);return L(`${X}${$.saveMany.path}`,V,this.buildOptions(I))}deleteOneById(B,V,I={}){let X=this.getBasePath(B),Z=I.hardDelete?A({hardDelete:I.hardDelete}):"";return x(`${X}/${V}${Z}`,this.buildOptions(I))}deleteMany(B,V,I={}){let X=this.getBasePath(B),Z=A(I.hardDelete?{...V,hardDelete:I.hardDelete}:V);return x(`${X}${Z}`,this.buildOptions(I))}getBasePath(B){return`${this.basePath}/${D(B)}`}read(B,V,I){if(this.defaults.readMethod==="QUERY")return T(B,V??{},this.buildOptions(I));return N(`${B}${A(V)}`,this.buildOptions(I))}buildOptions(B){if(!this.defaults.headers&&!B?.headers)return B;return{...B,headers:{...this.defaults.headers,...B?.headers}}}}var P={getQuerier:()=>new C("/api")};function y(B){P=B}function g(){return P}function p(){return g().getQuerier()}export{y as setQuerierPool,x as remove,T as query,L as put,Q as post,J as patch,_ as on,F as notify,g as getQuerierPool,p as getQuerier,N as get,f as RequestError,C as HttpQuerier};
1
+ var d=[];function a(e){for(let n of d)n(e)}function k(e){d.push(e);let n=d.length-1;return()=>{d.splice(n,1)}}class m extends Error{status;constructor(e,n){super(e);this.status=n;this.name="RequestError"}}function y(e,n){return i(e,{method:"get"},n)}function f(e,n,t){let r=JSON.stringify(n);return i(e,{method:"post",body:r},t)}function l(e,n,t){let r=JSON.stringify(n);return i(e,{method:"patch",body:r},t)}function E(e,n,t){let r=JSON.stringify(n);return i(e,{method:"put",body:r},t)}function g(e,n){return i(e,{method:"delete"},n)}function T(e,n,t){let r=JSON.stringify(n);return i(e,{method:"QUERY",body:r},t)}function i(e,n,t){if(a({phase:"start",opts:t}),n.headers={accept:"application/json","content-type":"application/json",...t?.headers},t?.signal)n.signal=t.signal;return fetch(e,n).then((r)=>r.json().then((s)=>{if(r.status>=200&&r.status<300)return a({phase:"success",opts:t}),s;let O=s,h={message:O?.error?.message??r.statusText,code:O?.error?.code??r.status};throw a({phase:"error",error:h,opts:t}),new m(h.message,h.code)})).finally(()=>{a({phase:"complete",opts:t})})}function c(e){return e?Object.keys(e):[]}function b(e){let n=e.charAt(0).toLowerCase();for(let t=1;t<e.length;++t)n+=e[t]===e[t].toUpperCase()?"-"+e[t].toLowerCase():e[t];return n}var o={findMany:{method:"GET",path:""},findOne:{method:"GET",path:"/one"},count:{method:"GET",path:"/count"},findOneById:{method:"GET",path:"/:id"},insertOne:{method:"POST",path:""},insertMany:{method:"POST",path:"/many"},saveOne:{method:"PUT",path:""},saveMany:{method:"PUT",path:"/many"},updateMany:{method:"PATCH",path:""},updateOneById:{method:"PATCH",path:"/:id"},deleteOneById:{method:"DELETE",path:"/:id"},deleteMany:{method:"DELETE",path:""}},Q=c(o),A=new Map(Q.filter((e)=>o[e].method==="GET"&&o[e].path!=="/:id").map((e)=>[o[e].path,e]));function x(e){return b(e.name)}var q=["$select","$populate","$exclude","$where","$sort"],K=new Set([...q,"$skip","$limit","hardDelete","count"]);function u(e){if(!e)return"";let n=new URLSearchParams;for(let r of c(e)){let s=e[r];if(s===void 0)continue;n.append(r,typeof s==="object"&&s!==null?JSON.stringify(s):String(s))}let t=n.toString();return t?`?${t}`:""}class R{basePath;defaults;constructor(e,n={}){this.basePath=e;this.defaults=n}findOneById(e,n,t,r){let s=this.getBasePath(e),p=u(t);return y(`${s}/${n}${p}`,this.buildOptions(r))}findOne(e,n,t){return this.read(`${this.getBasePath(e)}${o.findOne.path}`,n,t)}findMany(e,n,t){let r={...n};if(t?.count)r.count=!0;return this.read(this.getBasePath(e),r,t)}async findManyAndCount(e,n,t){let r=await this.findMany(e,n,{...t,count:!0});if(typeof r.count!=="number")throw TypeError("findManyAndCount response has an invalid count");return{...r,count:r.count}}count(e,n,t){return this.read(`${this.getBasePath(e)}${o.count.path}`,n,t)}insertOne(e,n,t){let r=this.getBasePath(e);return f(r,n,this.buildOptions(t))}insertMany(e,n,t){let r=this.getBasePath(e);return f(`${r}${o.insertMany.path}`,n,this.buildOptions(t))}updateOneById(e,n,t,r){let s=this.getBasePath(e);return l(`${s}/${n}`,t,this.buildOptions(r))}updateMany(e,n,t,r){let s=this.getBasePath(e),p=u(n);return l(`${s}${p}`,t,this.buildOptions(r))}saveOne(e,n,t){let r=this.getBasePath(e);return E(r,n,this.buildOptions(t))}saveMany(e,n,t){let r=this.getBasePath(e);return E(`${r}${o.saveMany.path}`,n,this.buildOptions(t))}deleteOneById(e,n,t={}){let r=this.getBasePath(e),s=t.hardDelete?u({hardDelete:t.hardDelete}):"";return g(`${r}/${n}${s}`,this.buildOptions(t))}deleteMany(e,n,t={}){let r=this.getBasePath(e),s=u(t.hardDelete?{...n,hardDelete:t.hardDelete}:n);return g(`${r}${s}`,this.buildOptions(t))}getBasePath(e){return`${this.basePath}/${x(e)}`}read(e,n,t){if(this.defaults.readMethod==="QUERY")return T(e,n??{},this.buildOptions(t));return y(`${e}${u(n)}`,this.buildOptions(t))}buildOptions(e){if(!this.defaults.headers&&!e?.headers)return e;return{...e,headers:{...this.defaults.headers,...e?.headers}}}}var P={getQuerier:()=>new R("/api")};function J(e){P=e}function C(){return P}function V(){return C().getQuerier()}export{R as HttpQuerier,m as RequestError,y as get,V as getQuerier,C as getQuerierPool,a as notify,k as on,l as patch,f as post,E as put,T as query,g as remove,J as setQuerierPool};
2
2
 
3
- //# debugId=63F5785EAD0E521C64756E2164756E21
3
+ //# debugId=E55D2B69C2EDA86C64756E2164756E21
4
4
  //# sourceMappingURL=uql-browser.min.js.map
@@ -8,10 +8,10 @@
8
8
  "export function kebabCase(val: string): string {\n let resp = val.charAt(0).toLowerCase();\n for (let i = 1; i < val.length; ++i) {\n resp += val[i] === val[i].toUpperCase() ? '-' + val[i].toLowerCase() : val[i];\n }\n return resp;\n}\n\nexport function upperFirst(text: string): string {\n if (!text) return text;\n return text[0].toUpperCase() + text.slice(1);\n}\n\nexport function lowerFirst(text: string): string {\n if (!text) return text;\n return text[0].toLowerCase() + text.slice(1);\n}\n\nexport function snakeCase(val: string): string {\n if (val === null || val === undefined) return val as string;\n if (!val) return '';\n let resp = val.charAt(0).toLowerCase();\n for (let i = 1; i < val.length; ++i) {\n const char = val[i];\n const charLower = char.toLowerCase();\n if (char !== charLower && char === char.toUpperCase()) {\n resp += '_' + charLower;\n } else {\n resp += char;\n }\n }\n return resp;\n}\n\n/**\n * Convert a string to PascalCase (UpperCamelCase).\n * @example 'user_profile' -> 'UserProfile'\n * @example 'some-text' -> 'SomeText'\n */\nexport function pascalCase(str: string): string {\n if (!str) return '';\n return str\n .split(/[_\\s-]+/)\n .map((word) => {\n // Lower-casing the rest is only right for a word that carries no case of its own: it turns\n // `USER_ID` into `UserId`, but it also turns `tenantId` into `Tenantid`.\n const rest = word === word.toUpperCase() ? word.slice(1).toLowerCase() : word.slice(1);\n return word.charAt(0).toUpperCase() + rest;\n })\n .join('');\n}\n\n/**\n * Convert a string to camelCase.\n * @example 'user_profile' -> 'userProfile'\n * @example 'SomeText' -> 'someText'\n */\nexport function camelCase(str: string): string {\n const pascal = pascalCase(str);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\n/**\n * Simple singularize function for English words.\n * @example 'users' -> 'user'\n * @example 'categories' -> 'category'\n */\nexport function singularize(name: string): string {\n if (!name) return '';\n if (name.endsWith('ies')) {\n return name.slice(0, -3) + 'y';\n }\n if (name.endsWith('ses') || name.endsWith('xes') || name.endsWith('zes')) {\n return name.slice(0, -2);\n }\n if (name.endsWith('s') && !name.endsWith('ss')) {\n return name.slice(0, -1);\n }\n return name;\n}\n\n/**\n * Simple pluralize function for English words.\n * @example 'user' -> 'users'\n * @example 'category' -> 'categories'\n */\nexport function pluralize(name: string): string {\n if (!name) return '';\n if (name.endsWith('y') && name.length > 1 && !/[aeiou]/.test(name[name.length - 2])) {\n return name.slice(0, -1) + 'ies';\n }\n if (name.endsWith('s') || name.endsWith('x') || name.endsWith('z') || name.endsWith('ch') || name.endsWith('sh')) {\n return name + 'es';\n }\n return name + 's';\n}\n",
9
9
  "import type { Type, UniversalQuerier } from '../type/index.js';\n// the specific util modules, not the barrel, so the browser bundle does not pull in entity metadata\nimport { getKeys } from '../util/object.util.js';\nimport { kebabCase } from '../util/string.util.js';\n\ntype RouteShape = {\n readonly method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n readonly path: '' | `/${string}`;\n};\n\n/**\n * Single source of truth for the CRUD-over-HTTP surface, shared by server adapters and the browser client.\n * Keys are constrained to {@link UniversalQuerier} method names, so renaming a querier method\n * (or routing a non-existent one) is a compile error.\n */\nexport const CRUD_ROUTES = {\n findMany: { method: 'GET', path: '' },\n findOne: { method: 'GET', path: '/one' },\n count: { method: 'GET', path: '/count' },\n findOneById: { method: 'GET', path: '/:id' },\n insertOne: { method: 'POST', path: '' },\n insertMany: { method: 'POST', path: '/many' },\n saveOne: { method: 'PUT', path: '' },\n saveMany: { method: 'PUT', path: '/many' },\n updateMany: { method: 'PATCH', path: '' },\n updateOneById: { method: 'PATCH', path: '/:id' },\n deleteOneById: { method: 'DELETE', path: '/:id' },\n deleteMany: { method: 'DELETE', path: '' },\n} as const satisfies Partial<Record<keyof UniversalQuerier, RouteShape>>;\n\nexport type CrudOperation = keyof typeof CRUD_ROUTES;\n\nexport type CrudRoute = (typeof CRUD_ROUTES)[CrudOperation];\n\n/**\n * `QUERY` (RFC 10008) is an alternate transport for the read operations: same semantics as the\n * GET routes, but the JSON query travels in the request body instead of the query string,\n * avoiding URL-length limits for large queries.\n */\nexport type HttpMethod = CrudRoute['method'] | 'QUERY';\n\nconst CRUD_OPS = getKeys(CRUD_ROUTES);\n\n// derived from CRUD_ROUTES (the literal-path GET routes) so the sub-paths live in exactly one place\nconst QUERY_READ_OPS: ReadonlyMap<string, CrudOperation> = new Map(\n CRUD_OPS.filter((op) => CRUD_ROUTES[op].method === 'GET' && CRUD_ROUTES[op].path !== '/:id').map((op) => [\n CRUD_ROUTES[op].path,\n op,\n ]),\n);\n\n/**\n * URL segment for an entity, e.g. `entityPath(UserProfile) === 'user-profile'`.\n */\nexport function entityPath<E>(entity: Type<E>): string {\n return kebabCase(entity.name);\n}\n\nexport type RouteMatch = {\n readonly op: CrudOperation;\n /**\n * the resolved transport method - differs from the op's canonical route method for QUERY.\n */\n readonly method: HttpMethod;\n readonly id?: string;\n};\n\n/**\n * Resolve a (method, sub-path) pair to a CRUD operation. Literal sub-paths win over `:id`.\n */\nexport function matchRoute(method: string, subPath: string | undefined): RouteMatch | undefined {\n const raw = method.toUpperCase();\n const literal = subPath === undefined ? '' : `/${subPath}`;\n if (raw === 'QUERY') {\n const op = QUERY_READ_OPS.get(literal);\n return op ? { op, method: 'QUERY' } : undefined;\n }\n // HEAD reads like GET per HTTP semantics; the server runtime omits the response body\n const verb = raw === 'HEAD' ? 'GET' : raw;\n let idOp: CrudOperation | undefined;\n for (const op of CRUD_OPS) {\n const route = CRUD_ROUTES[op];\n if (route.method !== verb) {\n continue;\n }\n if (route.path === literal) {\n return { op, method: route.method };\n }\n if (route.path === '/:id') {\n idOp = op;\n }\n }\n return idOp && subPath !== undefined ? { op: idOp, method: CRUD_ROUTES[idOp].method, id: subPath } : undefined;\n}\n\nexport type RequestSuccessResponse<E> = {\n data: E;\n count?: number;\n};\n\nexport type RequestCountedSuccessResponse<E> = RequestSuccessResponse<E> & {\n count: number;\n};\n\nexport type RequestErrorResponse = {\n readonly error: {\n readonly message: string;\n readonly code: number;\n };\n};\n\n/**\n * Map a thrown error to the wire error envelope. Honors a numeric `status` on the error\n * (e.g. hooks throwing 403), defaults to 500; `code` mirrors the HTTP status.\n */\nexport function toErrorResponse(err: unknown): { status: number; body: RequestErrorResponse } {\n const status = err instanceof Error && 'status' in err && typeof err.status === 'number' ? err.status : 500;\n const message = err instanceof Error ? err.message : 'Internal Server Error';\n return { status, body: { error: { message, code: status } } };\n}\n",
10
10
  "import type { Query, QueryOptions } from '../type/index.js';\n// the specific util module, not the barrel, so the browser bundle does not pull in entity metadata\nimport { getKeys } from '../util/object.util.js';\n\nconst JSON_QUERY_KEYS = [\n '$select',\n '$populate',\n '$exclude',\n '$where',\n '$sort',\n] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Keys accepted from the wire - query structure ({@link Query}) plus the `hardDelete`/`count` scalar\n * flags. Anything else (e.g. `filters`, `context`, `$entity`) is dropped so a remote client can't\n * bypass a security filter or inject ambient context - those are server-only. The `satisfies` ties\n * every entry to a real query/option key, so a typo or a renamed option fails to compile.\n */\nconst ALLOWED_QUERY_KEYS = new Set<string>([...JSON_QUERY_KEYS, '$skip', '$limit', 'hardDelete', 'count'] satisfies (\n | keyof Query<unknown>\n | keyof Pick<QueryOptions, 'hardDelete'>\n | 'count'\n)[]);\n\n/**\n * Keys that mean something locally but that this transport can never honor, so they are rejected\n * rather than dropped like the rest. Each request runs on its own auto-committing connection, so a\n * row lock taken here is released before the response is written: honoring `$lock` is impossible,\n * and ignoring it would hand the caller a read they believe is serialized and is not.\n */\nconst REJECTED_QUERY_KEYS = new Set<string>(['$lock'] satisfies (keyof Query<unknown>)[]);\n\n/**\n * Parse raw query-string entries (with JSON-stringified values) into a UQL query object.\n * Symmetric counterpart of {@link stringifyQuery}. Only {@link ALLOWED_QUERY_KEYS} are honored.\n */\nexport function parseQueryParams(params: Record<string, unknown> = {}): Query<unknown> {\n const query: Record<string, unknown> = {};\n for (const key of getKeys(params)) {\n if (REJECTED_QUERY_KEYS.has(key)) {\n throw Object.assign(new TypeError(`'${key}' is not supported over HTTP`), { status: 400 });\n }\n if (ALLOWED_QUERY_KEYS.has(key)) {\n query[key] = params[key];\n }\n }\n\n for (const key of JSON_QUERY_KEYS) {\n const value = query[key];\n if (typeof value === 'string') {\n try {\n query[key] = JSON.parse(value);\n } catch {\n throw Object.assign(new SyntaxError(`invalid JSON in '${key}'`), { status: 400 });\n }\n }\n }\n\n query['$where'] ??= {};\n\n if (query['$skip']) {\n query['$skip'] = Number(query['$skip']);\n }\n if (query['$limit']) {\n query['$limit'] = Number(query['$limit']);\n }\n\n return query as Query<unknown>;\n}\n\n/**\n * Serialize a UQL query object into a percent-encoded query string where object values\n * are JSON-stringified. Symmetric counterpart of {@link parseQueryParams}.\n */\nexport function stringifyQuery(query?: Record<string, unknown>): string {\n if (!query) {\n return '';\n }\n const params = new URLSearchParams();\n for (const key of getKeys(query)) {\n const value = query[key];\n if (value === undefined) {\n continue;\n }\n params.append(key, typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value));\n }\n const qs = params.toString();\n return qs ? `?${qs}` : '';\n}\n",
11
- "import {\n CRUD_ROUTES,\n entityPath,\n type HttpMethod,\n type RequestCountedSuccessResponse,\n type RequestSuccessResponse,\n} from '../../http/contract.js';\nimport { stringifyQuery } from '../../http/query.js';\nimport type { IdValue, Query, QueryOne, QueryOptions, QuerySearch, Type, UpdatePayload } from '../../type/index.js';\nimport { get, query as httpQuery, patch, post, put, remove } from '../http/index.js';\nimport type { ClientQuerier, RequestFindOptions, RequestOptions } from '../type/index.js';\n\nexport type HttpQuerierDefaults = {\n /**\n * headers sent with every request from this instance, merged under per-call headers.\n * Create one instance per request (e.g. during SSR) to scope auth headers safely.\n */\n readonly headers?: Record<string, string>;\n /**\n * transport for read queries (findOne, findMany, count). 'QUERY' (RFC 10008) sends the\n * JSON query in the request body, avoiding URL-length limits for large queries; requires\n * infrastructure (proxies, CDNs) that forwards the QUERY method. Defaults to 'GET'.\n */\n readonly readMethod?: Extract<HttpMethod, 'GET' | 'QUERY'>;\n};\n\nexport class HttpQuerier implements ClientQuerier {\n constructor(\n readonly basePath: string,\n readonly defaults: HttpQuerierDefaults = {},\n ) {}\n\n findOneById<E extends object>(\n entity: Type<E>,\n id: IdValue<E>,\n q?: QueryOne<E>,\n opts?: RequestOptions,\n ): Promise<RequestSuccessResponse<E | undefined>> {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(q);\n return get<E | undefined>(`${basePath}/${id}${qs}`, this.buildOptions(opts));\n }\n\n findOne<E extends object>(\n entity: Type<E>,\n q: QueryOne<E>,\n opts?: RequestOptions,\n ): Promise<RequestSuccessResponse<E | undefined>> {\n return this.read<E | undefined>(`${this.getBasePath(entity)}${CRUD_ROUTES.findOne.path}`, q, opts);\n }\n\n findMany<E extends object>(\n entity: Type<E>,\n q: Query<E>,\n opts?: RequestFindOptions,\n ): Promise<RequestSuccessResponse<E[]>> {\n const data: Query<E> & { count?: boolean } = { ...q };\n if (opts?.count) {\n data.count = true;\n }\n return this.read<E[]>(this.getBasePath(entity), data, opts);\n }\n\n async findManyAndCount<E extends object>(\n entity: Type<E>,\n q: Query<E>,\n opts?: RequestFindOptions,\n ): Promise<RequestCountedSuccessResponse<E[]>> {\n const response = await this.findMany(entity, q, { ...opts, count: true });\n if (typeof response.count !== 'number') {\n throw new TypeError('findManyAndCount response has an invalid count');\n }\n return { ...response, count: response.count };\n }\n\n count<E extends object>(entity: Type<E>, q?: QuerySearch<E>, opts?: RequestOptions) {\n return this.read<number>(`${this.getBasePath(entity)}${CRUD_ROUTES.count.path}`, q, opts);\n }\n\n insertOne<E extends object>(entity: Type<E>, payload: E, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return post<IdValue<E> | undefined>(basePath, payload, this.buildOptions(opts));\n }\n\n insertMany<E extends object>(entity: Type<E>, payload: E[], opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return post<IdValue<E>[]>(`${basePath}${CRUD_ROUTES.insertMany.path}`, payload, this.buildOptions(opts));\n }\n\n updateOneById<E extends object>(entity: Type<E>, id: IdValue<E>, payload: UpdatePayload<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return patch<number>(`${basePath}/${id}`, payload, this.buildOptions(opts));\n }\n\n updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(q);\n return patch<number>(`${basePath}${qs}`, payload, this.buildOptions(opts));\n }\n\n saveOne<E extends object>(entity: Type<E>, payload: E, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return put<IdValue<E>>(basePath, payload, this.buildOptions(opts));\n }\n\n saveMany<E extends object>(entity: Type<E>, payload: E[], opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return put<IdValue<E>[]>(`${basePath}${CRUD_ROUTES.saveMany.path}`, payload, this.buildOptions(opts));\n }\n\n deleteOneById<E extends object>(entity: Type<E>, id: IdValue<E>, opts: QueryOptions & RequestOptions = {}) {\n const basePath = this.getBasePath(entity);\n const qs = opts.hardDelete ? stringifyQuery({ hardDelete: opts.hardDelete }) : '';\n return remove<number>(`${basePath}/${id}${qs}`, this.buildOptions(opts));\n }\n\n deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts: QueryOptions & RequestOptions = {}) {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(opts.hardDelete ? { ...q, hardDelete: opts.hardDelete } : q);\n return remove<number>(`${basePath}${qs}`, this.buildOptions(opts));\n }\n\n getBasePath<E>(entity: Type<E>) {\n return `${this.basePath}/${entityPath(entity)}`;\n }\n\n protected read<T>(path: string, q: Record<string, unknown> | undefined, opts?: RequestOptions) {\n if (this.defaults.readMethod === 'QUERY') {\n return httpQuery<T>(path, q ?? {}, this.buildOptions(opts));\n }\n return get<T>(`${path}${stringifyQuery(q)}`, this.buildOptions(opts));\n }\n\n protected buildOptions(opts?: RequestOptions): RequestOptions | undefined {\n if (!this.defaults.headers && !opts?.headers) {\n return opts;\n }\n return { ...opts, headers: { ...this.defaults.headers, ...opts?.headers } };\n }\n}\n",
11
+ "import {\n CRUD_ROUTES,\n entityPath,\n type HttpMethod,\n type RequestCountedSuccessResponse,\n type RequestSuccessResponse,\n} from '../../http/contract.js';\nimport { stringifyQuery } from '../../http/query.js';\nimport type {\n EntityData,\n IdValue,\n Query,\n QueryOne,\n QueryOptions,\n QuerySearch,\n Type,\n UpdatePayload,\n} from '../../type/index.js';\nimport { get, query as httpQuery, patch, post, put, remove } from '../http/index.js';\nimport type { ClientQuerier, RequestFindOptions, RequestOptions } from '../type/index.js';\n\nexport type HttpQuerierDefaults = {\n /**\n * headers sent with every request from this instance, merged under per-call headers.\n * Create one instance per request (e.g. during SSR) to scope auth headers safely.\n */\n readonly headers?: Record<string, string>;\n /**\n * transport for read queries (findOne, findMany, count). 'QUERY' (RFC 10008) sends the\n * JSON query in the request body, avoiding URL-length limits for large queries; requires\n * infrastructure (proxies, CDNs) that forwards the QUERY method. Defaults to 'GET'.\n */\n readonly readMethod?: Extract<HttpMethod, 'GET' | 'QUERY'>;\n};\n\nexport class HttpQuerier implements ClientQuerier {\n constructor(\n readonly basePath: string,\n readonly defaults: HttpQuerierDefaults = {},\n ) {}\n\n findOneById<E extends object>(\n entity: Type<E>,\n id: IdValue<E>,\n q?: QueryOne<E>,\n opts?: RequestOptions,\n ): Promise<RequestSuccessResponse<E | undefined>> {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(q);\n return get<E | undefined>(`${basePath}/${id}${qs}`, this.buildOptions(opts));\n }\n\n findOne<E extends object>(\n entity: Type<E>,\n q: QueryOne<E>,\n opts?: RequestOptions,\n ): Promise<RequestSuccessResponse<E | undefined>> {\n return this.read<E | undefined>(`${this.getBasePath(entity)}${CRUD_ROUTES.findOne.path}`, q, opts);\n }\n\n findMany<E extends object>(\n entity: Type<E>,\n q: Query<E>,\n opts?: RequestFindOptions,\n ): Promise<RequestSuccessResponse<E[]>> {\n const data: Query<E> & { count?: boolean } = { ...q };\n if (opts?.count) {\n data.count = true;\n }\n return this.read<E[]>(this.getBasePath(entity), data, opts);\n }\n\n async findManyAndCount<E extends object>(\n entity: Type<E>,\n q: Query<E>,\n opts?: RequestFindOptions,\n ): Promise<RequestCountedSuccessResponse<E[]>> {\n const response = await this.findMany(entity, q, { ...opts, count: true });\n if (typeof response.count !== 'number') {\n throw new TypeError('findManyAndCount response has an invalid count');\n }\n return { ...response, count: response.count };\n }\n\n count<E extends object>(entity: Type<E>, q?: QuerySearch<E>, opts?: RequestOptions) {\n return this.read<number>(`${this.getBasePath(entity)}${CRUD_ROUTES.count.path}`, q, opts);\n }\n\n insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return post<IdValue<E> | undefined>(basePath, payload, this.buildOptions(opts));\n }\n\n insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return post<IdValue<E>[]>(`${basePath}${CRUD_ROUTES.insertMany.path}`, payload, this.buildOptions(opts));\n }\n\n updateOneById<E extends object>(entity: Type<E>, id: IdValue<E>, payload: UpdatePayload<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return patch<number>(`${basePath}/${id}`, payload, this.buildOptions(opts));\n }\n\n updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(q);\n return patch<number>(`${basePath}${qs}`, payload, this.buildOptions(opts));\n }\n\n saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return put<IdValue<E>>(basePath, payload, this.buildOptions(opts));\n }\n\n saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return put<IdValue<E>[]>(`${basePath}${CRUD_ROUTES.saveMany.path}`, payload, this.buildOptions(opts));\n }\n\n deleteOneById<E extends object>(entity: Type<E>, id: IdValue<E>, opts: QueryOptions & RequestOptions = {}) {\n const basePath = this.getBasePath(entity);\n const qs = opts.hardDelete ? stringifyQuery({ hardDelete: opts.hardDelete }) : '';\n return remove<number>(`${basePath}/${id}${qs}`, this.buildOptions(opts));\n }\n\n deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts: QueryOptions & RequestOptions = {}) {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(opts.hardDelete ? { ...q, hardDelete: opts.hardDelete } : q);\n return remove<number>(`${basePath}${qs}`, this.buildOptions(opts));\n }\n\n getBasePath<E>(entity: Type<E>) {\n return `${this.basePath}/${entityPath(entity)}`;\n }\n\n protected read<T>(path: string, q: Record<string, unknown> | undefined, opts?: RequestOptions) {\n if (this.defaults.readMethod === 'QUERY') {\n return httpQuery<T>(path, q ?? {}, this.buildOptions(opts));\n }\n return get<T>(`${path}${stringifyQuery(q)}`, this.buildOptions(opts));\n }\n\n protected buildOptions(opts?: RequestOptions): RequestOptions | undefined {\n if (!this.defaults.headers && !opts?.headers) {\n return opts;\n }\n return { ...opts, headers: { ...this.defaults.headers, ...opts?.headers } };\n }\n}\n",
12
12
  "import { HttpQuerier } from './querier/httpQuerier.js';\nimport type { ClientQuerier, ClientQuerierPool } from './type/index.js';\n\nlet defaultPool: ClientQuerierPool = {\n getQuerier: () => new HttpQuerier('/api'),\n};\n\nexport function setQuerierPool<T extends ClientQuerierPool>(pool: T) {\n defaultPool = pool;\n}\n\nexport function getQuerierPool(): ClientQuerierPool {\n return defaultPool;\n}\n\nexport function getQuerier(): ClientQuerier {\n return getQuerierPool().getQuerier();\n}\n"
13
13
  ],
14
- "mappings": "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",
15
- "debugId": "63F5785EAD0E521C64756E2164756E21",
14
+ "mappings": "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",
15
+ "debugId": "E55D2B69C2EDA86C64756E2164756E21",
16
16
  "names": []
17
17
  }
@@ -1,4 +1,4 @@
1
- import { type EntityMeta, type FieldKey, type FieldOptions, type IsolationLevel, type JsonColumnType, type JsonUpdateOp, type Query, type QueryAggMap, type QueryAggregate, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryDialect, type QueryExclude, type QueryGroupMap, type QueryHavingMap, type QueryOptions, type QueryPager, QueryRaw, type QueryRawFnOptions, type QuerySearch, type QuerySelectOptions, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOperatorMap, type QueryWhereMap, type QueryWhereOptions, type RelationMeta, type SqlDialectName, type SqlQueryDialect, type Type, type UpdatePayload } from '../type/index.js';
1
+ import { type EntityMeta, type FieldKey, type FieldOptions, type IsolationLevel, type JsonColumnType, type JsonUpdateOp, type Query, type QueryAggMap, type QueryAggregate, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryDialect, type QueryExclude, type QueryFilter, type QueryGroupMap, type QueryHavingMap, type QueryOptions, type QueryPager, QueryRaw, type QueryRawFnOptions, type QuerySearch, type QuerySelectOptions, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOperatorMap, type QueryWhereMap, type QueryWhereOptions, type RelationMeta, type SqlDialectName, type SqlQueryDialect, type Type, type UpdatePayload } from '../type/index.js';
2
2
  import type { HydrateKind } from './hydrateColumn.js';
3
3
  import { IndexSqlDialect } from './indexSqlDialect.js';
4
4
  import { type QueryJoins, type QuerySortOptions } from './queryJoins.js';
@@ -221,7 +221,7 @@ export declare abstract class AbstractSqlDialect extends IndexSqlDialect impleme
221
221
  * and the other engines quietly widen the lock to the joined rows.
222
222
  */
223
223
  protected appendLock<E>(ctx: QueryContext, entity: Type<E>, q: Query<E>, joins?: QueryJoins): void;
224
- count<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): void;
224
+ count<E>(ctx: QueryContext, entity: Type<E>, q: QueryFilter<E>, opts?: QueryOptions): void;
225
225
  /** `$group` aggregate operator → SQL function name. An allowlist, not a formatter: the op key
226
226
  * comes from query data, so anything outside this map must be rejected rather than passed
227
227
  * through as a raw SQL function name. */
@@ -233,7 +233,9 @@ export declare abstract class AbstractSqlDialect extends IndexSqlDialect impleme
233
233
  * nothing defines.
234
234
  */
235
235
  private aggregateSort;
236
- protected having(ctx: QueryContext, having: QueryHavingMap, aggregateExpressions: Record<string, string>): void;
236
+ /** The SQL referencing one of an aggregate's emitted columns, rejecting any other name. */
237
+ private aggregateRef;
238
+ protected having(ctx: QueryContext, having: QueryHavingMap, emittedColumns: Record<string, string>): void;
237
239
  private static readonly SORT_DIRECTION_MAP;
238
240
  private resolveSortDirection;
239
241
  /** Scalar comparison operators shared by `HAVING` conditions and `$size` comparisons. */
@@ -403,7 +405,7 @@ export declare abstract class AbstractSqlDialect extends IndexSqlDialect impleme
403
405
  protected getJsonPathJsonbExpr(escapedColumn: string, jsonPath: string): string;
404
406
  /**
405
407
  * Normalizes a raw WHERE value into an operator map.
406
- * Arrays become `$in`, scalars/null become `$eq`, objects pass through.
408
+ * Arrays become `$in`, operator maps pass through, everything else becomes `$eq`.
407
409
  */
408
410
  private normalizeWhereValue;
409
411
  /**
@@ -1,6 +1,6 @@
1
1
  import { getMeta } from '../entity/index.js';
2
2
  import { parseQueryLock, QueryRaw, RAW_ALIAS, RAW_VALUE, } from '../type/index.js';
3
- import { asSelectMap, buildQueryWhereAsMap, escapeSqlId, fillOnFields, filterFieldKeys, getInsertFieldKeys, getKeys, getSoftDeleteValue, hasKeys, isBooleanType, isJsonType, isJsonUpdateOp, isNumericType, isOperatorObject, isOperatorOnlyObject, isVectorSearch, normalizeScalarFieldSelection, parseGroupMap, parseRelationSize, populatesRelations, raw, someValue, withoutSoftDeleteFilter, } from '../util/index.js';
3
+ import { asSelectMap, assertNonNegativeInteger, buildQueryWhereAsMap, escapeSqlId, fillOnFields, filterFieldKeys, getInsertFieldKeys, getKeys, getSoftDeleteValue, hasKeys, isBooleanType, isJsonType, isJsonUpdateOp, isNumericType, isOperatorMap, isOperatorObject, isOperatorOnlyObject, isVectorSearch, normalizeScalarFieldSelection, parseGroupMap, parseRelationSize, populatesRelations, raw, someValue, throwUnknownAggregateColumn, withoutSoftDeleteFilter, } from '../util/index.js';
4
4
  import { escapeAnsiSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
5
5
  import { IndexSqlDialect } from './indexSqlDialect.js';
6
6
  import { buildElemMatchConditions } from './jsonArrayElemMatchUtils.js';
@@ -698,11 +698,12 @@ export class AbstractSqlDialect extends IndexSqlDialect {
698
698
  return this.resolveJsonDotPath(meta, key, prefix)?.accessor() ?? this.escapeId(key);
699
699
  }
700
700
  pager(ctx, opts) {
701
- if (opts.$limit) {
702
- ctx.append(` LIMIT ${Number(opts.$limit)}`);
701
+ // `!== undefined`, not truthiness: `$limit: 0` asks for no rows, where "unset" means every row.
702
+ if (opts.$limit !== undefined) {
703
+ ctx.append(` LIMIT ${assertNonNegativeInteger(opts.$limit, '$limit')}`);
703
704
  }
704
705
  if (opts.$skip !== undefined) {
705
- ctx.append(` OFFSET ${Number(opts.$skip)}`);
706
+ ctx.append(` OFFSET ${assertNonNegativeInteger(opts.$skip, '$skip')}`);
706
707
  }
707
708
  }
708
709
  /** Whether this engine has row locks at all. The SQLite family locks the database instead. */
@@ -738,12 +739,11 @@ export class AbstractSqlDialect extends IndexSqlDialect {
738
739
  const suffix = wait === 'skip' ? ' SKIP LOCKED' : wait === 'nowait' ? ' NOWAIT' : '';
739
740
  ctx.append(` FOR UPDATE${target}${suffix}`);
740
741
  }
742
+ // `QueryFilter`, not `QuerySearch`: a count orders nothing and pages nothing, and an `OFFSET`
743
+ // would push its single row out of the result set. The parameter type is what keeps them away.
741
744
  count(ctx, entity, q, opts) {
742
- const search = { ...q };
743
- // A count joins nothing and orders nothing: how many rows match is the same either way.
744
- delete search.$sort;
745
745
  this.select(ctx, entity, { $select: [raw('COUNT(*)', 'count')] });
746
- this.search(ctx, entity, search, opts);
746
+ this.search(ctx, entity, q, opts);
747
747
  }
748
748
  /** `$group` aggregate operator → SQL function name. An allowlist, not a formatter: the op key
749
749
  * comes from query data, so anything outside this map must be rejected rather than passed
@@ -760,13 +760,17 @@ export class AbstractSqlDialect extends IndexSqlDialect {
760
760
  const tableName = this.resolveTableName(entity, meta);
761
761
  const groupKeys = [];
762
762
  const selectParts = [];
763
- const aggregateExpressions = {};
763
+ // Every column the statement emits, mapped to the SQL that references it. `$having` and `$sort`
764
+ // may name these and nothing else, so one map answers both "is this legal?" and "what do I
765
+ // emit for it?".
766
+ const emittedColumns = {};
764
767
  for (const entry of parseGroupMap(q.$group, q.$agg)) {
765
768
  if (entry.kind === 'key') {
766
769
  const field = meta.fields[entry.alias];
767
770
  const columnName = this.resolveColumnName(entry.alias, field);
768
771
  const escaped = this.escapeId(columnName);
769
772
  groupKeys.push(escaped);
773
+ emittedColumns[entry.alias] = escaped;
770
774
  selectParts.push(columnName !== entry.alias ? `${escaped} ${this.escapeId(entry.alias)}` : escaped);
771
775
  }
772
776
  else {
@@ -776,7 +780,7 @@ export class AbstractSqlDialect extends IndexSqlDialect {
776
780
  }
777
781
  const sqlArg = entry.fieldRef === '*' ? '*' : this.escapeId(this.columnOf(meta, entry.fieldRef));
778
782
  const expr = `${sqlFn}(${entry.distinct ? 'DISTINCT ' : ''}${sqlArg})`;
779
- aggregateExpressions[entry.alias] = expr;
783
+ emittedColumns[entry.alias] = expr;
780
784
  selectParts.push(`${expr} ${this.escapeId(entry.alias)}`);
781
785
  }
782
786
  }
@@ -789,9 +793,9 @@ export class AbstractSqlDialect extends IndexSqlDialect {
789
793
  ctx.append(` GROUP BY ${groupKeys.join(', ')}`);
790
794
  }
791
795
  if (q.$having) {
792
- this.having(ctx, q.$having, aggregateExpressions);
796
+ this.having(ctx, q.$having, emittedColumns);
793
797
  }
794
- this.aggregateSort(ctx, meta, q.$sort, aggregateExpressions);
798
+ this.aggregateSort(ctx, q.$sort, emittedColumns);
795
799
  this.pager(ctx, q);
796
800
  }
797
801
  /**
@@ -799,22 +803,21 @@ export class AbstractSqlDialect extends IndexSqlDialect {
799
803
  * statement has no joins to address, so a relation key is rejected rather than emitted as an alias
800
804
  * nothing defines.
801
805
  */
802
- aggregateSort(ctx, meta, sort, aggregateExpressions) {
806
+ aggregateSort(ctx, sort, emittedColumns) {
803
807
  if (!hasKeys(sort))
804
808
  return;
805
809
  ctx.append(' ORDER BY ');
806
810
  Object.entries(sort).forEach(([key, dir], index) => {
807
811
  if (index > 0)
808
812
  ctx.append(', ');
809
- if (meta.relations[key]) {
810
- throw new TypeError(`cannot $sort by relation '${key}' in an aggregate query: it groups rows, it joins none`);
811
- }
812
- const direction = this.resolveSortDirection(dir);
813
- const ref = aggregateExpressions[key] ?? this.escapeId(key);
814
- ctx.append(ref + direction);
813
+ ctx.append(this.aggregateRef(emittedColumns, key, '$sort') + this.resolveSortDirection(dir));
815
814
  });
816
815
  }
817
- having(ctx, having, aggregateExpressions) {
816
+ /** The SQL referencing one of an aggregate's emitted columns, rejecting any other name. */
817
+ aggregateRef(emittedColumns, key, clause) {
818
+ return emittedColumns[key] ?? throwUnknownAggregateColumn(key, clause);
819
+ }
820
+ having(ctx, having, emittedColumns) {
818
821
  const entries = Object.entries(having).filter(([, v]) => v !== undefined);
819
822
  if (!entries.length)
820
823
  return;
@@ -822,8 +825,9 @@ export class AbstractSqlDialect extends IndexSqlDialect {
822
825
  entries.forEach(([alias, condition], index) => {
823
826
  if (index > 0)
824
827
  ctx.append(' AND ');
825
- const expr = aggregateExpressions[alias] ?? this.escapeId(alias);
826
- this.havingCondition(ctx, expr, condition);
828
+ // A HAVING may only name a column the statement emits. Falling back to the bare key produced
829
+ // `HAVING "status" = ?` over an ungrouped column, which every engine rejects.
830
+ this.havingCondition(ctx, this.aggregateRef(emittedColumns, alias, '$having'), condition);
827
831
  });
828
832
  }
829
833
  static SORT_DIRECTION_MAP = new Map([
@@ -841,11 +845,7 @@ export class AbstractSqlDialect extends IndexSqlDialect {
841
845
  }
842
846
  /** Scalar comparison operators shared by `HAVING` conditions and `$size` comparisons. */
843
847
  havingCondition(ctx, expr, condition) {
844
- if (typeof condition !== 'object' || condition === null) {
845
- this.appendOperatorCondition(ctx, expr, '$eq', condition);
846
- return;
847
- }
848
- const ops = condition;
848
+ const ops = this.normalizeWhereValue(condition);
849
849
  getKeys(ops).forEach((op, i) => {
850
850
  if (i > 0)
851
851
  ctx.append(' AND ');
@@ -1308,12 +1308,12 @@ export class AbstractSqlDialect extends IndexSqlDialect {
1308
1308
  }
1309
1309
  /**
1310
1310
  * Normalizes a raw WHERE value into an operator map.
1311
- * Arrays become `$in`, scalars/null become `$eq`, objects pass through.
1311
+ * Arrays become `$in`, operator maps pass through, everything else becomes `$eq`.
1312
1312
  */
1313
1313
  normalizeWhereValue(val) {
1314
1314
  if (Array.isArray(val))
1315
1315
  return { $in: val };
1316
- if (typeof val === 'object' && val !== null)
1316
+ if (isOperatorMap(val))
1317
1317
  return val;
1318
1318
  return { $eq: val };
1319
1319
  }
@@ -1,4 +1,4 @@
1
- import type { DialectFeatures, EntityMeta, FieldOptions, IndexFeature, IndexSchema, InsertIdSource, QueryConflictPaths, QueryContext, QuerySizeComparisonOps, QueryTextSearchOptions, Type } from '../type/index.js';
1
+ import type { DialectFeatures, EntityMeta, FieldOptions, IndexFeature, IndexSchema, InsertIdSource, QueryConflictPaths, QueryContext, QueryPager, QuerySizeComparisonOps, QueryTextSearchOptions, Type } from '../type/index.js';
2
2
  import { AbstractSqlDialect } from './abstractSqlDialect.js';
3
3
  /**
4
4
  * Shared JSON-array / JSON-object operator implementation between MySQL and MariaDB.
@@ -13,6 +13,8 @@ import { AbstractSqlDialect } from './abstractSqlDialect.js';
13
13
  export declare abstract class MysqlLikeSqlDialect extends AbstractSqlDialect {
14
14
  /** Default {@link DialectFeatures} for MySQL-compatible SQL dialects. */
15
15
  protected readonly featureDefaults: DialectFeatures;
16
+ /** `OFFSET` is only legal after a `LIMIT` here, so a bare `$skip` needs one. */
17
+ pager(ctx: QueryContext, opts: QueryPager): void;
16
18
  readonly serialPrimaryKey = "BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY";
17
19
  readonly escapeIdChar = "`";
18
20
  readonly tableOptions = "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
@@ -3,6 +3,8 @@ import { getFieldKeys } from '../util/index.js';
3
3
  import { escapeMysqlSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
4
4
  import { AbstractSqlDialect } from './abstractSqlDialect.js';
5
5
  import { JSON_PULL_ALIAS, jsonAssignCall, jsonPath, jsonRemoveCall, jsonSetTarget } from './jsonSql.js';
6
+ /** The row count MySQL's manual gives for "all rows from the offset on": the largest `BIGINT UNSIGNED`. */
7
+ const MAX_LIMIT = BigInt.asUintN(64, -1n);
6
8
  /**
7
9
  * Shared JSON-array / JSON-object operator implementation between MySQL and MariaDB.
8
10
  *
@@ -30,6 +32,13 @@ export class MysqlLikeSqlDialect extends AbstractSqlDialect {
30
32
  supportsTimestamptz: false,
31
33
  defaultStringAsText: false,
32
34
  };
35
+ /** `OFFSET` is only legal after a `LIMIT` here, so a bare `$skip` needs one. */
36
+ pager(ctx, opts) {
37
+ if (opts.$limit === undefined && opts.$skip !== undefined) {
38
+ ctx.append(` LIMIT ${MAX_LIMIT}`);
39
+ }
40
+ super.pager(ctx, opts);
41
+ }
33
42
  serialPrimaryKey = 'BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY';
34
43
  escapeIdChar = '`';
35
44
  tableOptions = 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4';
@@ -63,8 +63,15 @@ export class VectorSqlDialect extends AbstractDialect {
63
63
  * Delegates to `appendVectorSort` so each dialect's distance syntax is written once.
64
64
  */
65
65
  appendVectorProjection(ctx, meta, key, search) {
66
+ const alias = search.$project;
67
+ // `$project` names a new column, so it cannot be one the entity already has: both come back
68
+ // under that name and the driver keeps whichever it read last. Checked here rather than in the
69
+ // type because TypeScript cannot say "any string except these".
70
+ if (meta.fields[alias]) {
71
+ throw new TypeError(`$project '${alias}' collides with a field of '${meta.name ?? String(meta.entity.name)}'`);
72
+ }
66
73
  this.appendVectorSort(ctx, meta, key, search);
67
- ctx.append(` AS ${this.escapeId(search.$project)}`);
74
+ ctx.append(` AS ${this.escapeId(alias)}`);
68
75
  }
69
76
  /**
70
77
  * Append a vector similarity expression for `ORDER BY`.
@@ -1,6 +1,6 @@
1
1
  import { type Document, type Filter, ObjectId, type Sort, type UpdateFilter } from 'mongodb';
2
2
  import { AbstractDialect } from '../dialect/abstractDialect.js';
3
- import type { DialectFeatures, EntityMeta, FieldValue, Query, QueryAggMap, QueryAggregate, QueryExclude, QueryGroupMap, QueryOptions, QueryPopulate, QuerySelectValue, QuerySortMap, QueryVectorSearch, QueryWhere, Type } from '../type/index.js';
3
+ import type { DialectFeatures, EntityData, EntityMeta, FieldValue, Query, QueryAggMap, QueryAggregate, QueryExclude, QueryGroupMap, QueryOptions, QueryPopulate, QuerySelectValue, QuerySortMap, QueryVectorSearch, QueryWhere, Type } from '../type/index.js';
4
4
  import { type CallbackKey } from '../util/index.js';
5
5
  /** What a read pipeline contributes to {@link MongoDialect.readStages} beyond the query itself. */
6
6
  type MongoReadStages = {
@@ -163,7 +163,7 @@ export declare class MongoDialect extends AbstractDialect {
163
163
  normalizeIds<E extends Document>(meta: EntityMeta<E>, docs: E[] | undefined): E[] | undefined;
164
164
  normalizeId<E extends Document>(meta: EntityMeta<E>, doc: E | undefined): E | undefined;
165
165
  getIdValue<T extends IdValue>(value: T): T;
166
- getPersistable<E extends Document>(meta: EntityMeta<E>, payload: E, callbackKey: CallbackKey): Partial<E>;
166
+ getPersistable<E extends Document>(meta: EntityMeta<E>, payload: EntityData<E>, callbackKey: CallbackKey): Partial<E>;
167
167
  /** One MongoDB update document's operators, grouped by kind and keyed by dotted path. */
168
168
  private groupUpdateOperators;
169
169
  /**
@@ -184,7 +184,7 @@ export declare class MongoDialect extends AbstractDialect {
184
184
  * `$literal` so a string starting with `$` stays data rather than becoming a field reference.
185
185
  */
186
186
  private getUpdatePipeline;
187
- getPersistables<E extends Document>(meta: EntityMeta<E>, payload: E | E[], callbackKey: CallbackKey): Partial<E>[];
187
+ getPersistables<E extends Document>(meta: EntityMeta<E>, payload: EntityData<E> | EntityData<E>[], callbackKey: CallbackKey): Partial<E>[];
188
188
  /**
189
189
  * Build MongoDB aggregation pipeline stages from a QueryAggregate.
190
190
  */
@@ -3,7 +3,7 @@ import { AbstractDialect } from '../dialect/abstractDialect.js';
3
3
  import { resolveQueryJoins, resolveSortableJoin } from '../dialect/queryJoins.js';
4
4
  import { getMeta } from '../entity/index.js';
5
5
  import { QueryRaw } from '../type/queryRaw.js';
6
- import { asSelectMap, buildQueryWhereAsMap, fillOnFields, filterFieldKeys, getKeys, getRelationRequestSummary, hasKeys, isJsonUpdateOp, isOperatorObject, isVectorSearch, normalizeScalarFieldSelection, parseGroupMap, parseRelationSize, } from '../util/index.js';
6
+ import { asSelectMap, assertAggregateColumns, assertNonNegativeInteger, buildQueryWhereAsMap, fillOnFields, filterFieldKeys, getKeys, getRelationRequestSummary, hasKeys, isJsonUpdateOp, isOperatorMap, isOperatorObject, isVectorSearch, normalizeScalarFieldSelection, parseGroupMap, parseRelationSize, } from '../util/index.js';
7
7
  /** Default {@link DialectFeatures} for MongoDB; shared by {@link MongoDialect} and its schema generator. */
8
8
  export const mongoDialectFeatures = {
9
9
  explicitJsonCast: false,
@@ -430,8 +430,8 @@ export class MongoDialect extends AbstractDialect {
430
430
  ...this.readStages(entity, q, opts, {
431
431
  sort: this.sort(entity, q.$sort, q.$populate),
432
432
  pager: [
433
- ...(q.$skip === undefined ? [] : [{ $skip: q.$skip }]),
434
- ...(q.$limit === undefined ? [] : [{ $limit: q.$limit }]),
433
+ ...(q.$skip === undefined ? [] : [{ $skip: assertNonNegativeInteger(q.$skip, '$skip') }]),
434
+ ...(q.$limit === undefined ? [] : [{ $limit: assertNonNegativeInteger(q.$limit, '$limit') }]),
435
435
  ],
436
436
  }),
437
437
  ];
@@ -722,8 +722,12 @@ export class MongoDialect extends AbstractDialect {
722
722
  }
723
723
  pipeline.push({ $project: project });
724
724
  }
725
+ // Everything the pipeline emits, which is all `$having` and `$sort` may name. The `$project`
726
+ // above has already dropped the rest, so an unchecked key matched nothing or ordered by nothing.
727
+ const emitted = new Set([...Object.keys(groupId), ...Object.keys(groupAccumulators)]);
725
728
  // $match stage for HAVING (post-group filtering)
726
729
  if (q.$having) {
730
+ assertAggregateColumns(q.$having, emitted, '$having');
727
731
  const havingFilter = this.buildHavingFilter(q.$having);
728
732
  if (hasKeys(havingFilter)) {
729
733
  pipeline.push({ $match: havingFilter });
@@ -731,6 +735,7 @@ export class MongoDialect extends AbstractDialect {
731
735
  }
732
736
  // $sort stage - by alias, since $group/$project already renamed everything
733
737
  if (q.$sort) {
738
+ assertAggregateColumns(q.$sort, emitted, '$sort');
734
739
  const sort = this.aliasSort(q.$sort);
735
740
  if (hasKeys(sort)) {
736
741
  pipeline.push({ $sort: sort });
@@ -738,10 +743,10 @@ export class MongoDialect extends AbstractDialect {
738
743
  }
739
744
  // $skip and $limit stages
740
745
  if (q.$skip !== undefined) {
741
- pipeline.push({ $skip: q.$skip });
746
+ pipeline.push({ $skip: assertNonNegativeInteger(q.$skip, '$skip') });
742
747
  }
743
748
  if (q.$limit !== undefined) {
744
- pipeline.push({ $limit: q.$limit });
749
+ pipeline.push({ $limit: assertNonNegativeInteger(q.$limit, '$limit') });
745
750
  }
746
751
  return pipeline;
747
752
  }
@@ -788,12 +793,15 @@ export class MongoDialect extends AbstractDialect {
788
793
  for (const [alias, condition] of Object.entries(having)) {
789
794
  if (condition === undefined)
790
795
  continue;
791
- if (typeof condition === 'number') {
792
- filter[alias] = condition;
793
- }
794
- else if (typeof condition === 'object' && condition !== null) {
796
+ // Classified exactly as the SQL side classifies a `$where`/`$having` value, so the two agree
797
+ // on identical input. Keeping only numbers and objects dropped a string or boolean without a
798
+ // word, handing back every group instead of the filtered ones.
799
+ if (isOperatorMap(condition)) {
795
800
  filter[alias] = this.transformOperators(condition);
796
801
  }
802
+ else {
803
+ filter[alias] = Array.isArray(condition) ? { $in: condition } : condition;
804
+ }
797
805
  }
798
806
  return filter;
799
807
  }
@@ -1,6 +1,6 @@
1
1
  import type { Document, MongoClient } from 'mongodb';
2
2
  import { AbstractQuerier } from '../querier/index.js';
3
- import type { ExtraOptions, IdValue, PrimaryKey, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryGroupMap, QueryOptions, QuerySearch, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
3
+ import type { EntityData, ExtraOptions, IdValue, PrimaryKey, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryGroupMap, QueryOptions, QuerySearch, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
4
4
  import type { MongoDialect } from './mongoDialect.js';
5
5
  export declare class MongodbQuerier extends AbstractQuerier {
6
6
  readonly dialect: MongoDialect;
@@ -28,15 +28,15 @@ export declare class MongodbQuerier extends AbstractQuerier {
28
28
  * MongoDB `updateMany`/`deleteMany` filter cannot host a `$lookup` - reads never pay this cost.
29
29
  */
30
30
  private matchedIds;
31
- internalInsertMany<E extends Document>(entity: Type<E>, payloads: E[]): Promise<IdValue<E>[]>;
31
+ internalInsertMany<E extends Document>(entity: Type<E>, payloads: EntityData<E>[]): Promise<IdValue<E>[]>;
32
32
  internalUpdateMany<E extends Document>(entity: Type<E>, qm: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
33
33
  private buildConflictFilter;
34
- upsertOne<E extends Document>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E): Promise<{
34
+ upsertOne<E extends Document>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<{
35
35
  firstId: string;
36
36
  changes: number;
37
37
  created: boolean;
38
38
  }>;
39
- upsertMany<E extends Document>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E[]): Promise<{
39
+ upsertMany<E extends Document>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<{
40
40
  changes: number;
41
41
  ids?: undefined;
42
42
  firstId?: undefined;
@@ -47,9 +47,14 @@ export declare class MongodbQuerier extends AbstractQuerier {
47
47
  }>;
48
48
  protected internalDeleteMany<E extends Document>(entity: Type<E>, qm: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
49
49
  get hasOpenTransaction(): boolean;
50
+ /** Every read and write goes through here, which makes it where a released querier is caught. */
50
51
  collection<E extends Document>(entity: Type<E>): import("mongodb").Collection<E>;
51
52
  get db(): import("mongodb").Db;
52
53
  beginTransaction(_opts?: TransactionOptions): Promise<void>;
54
+ /**
55
+ * The driver owns the transaction state here and settles it in its own `finally`, so a failed commit
56
+ * or abort still leaves `inTransaction()` false and the querier releasable.
57
+ */
53
58
  commitTransaction(): Promise<void>;
54
59
  rollbackTransaction(): Promise<void>;
55
60
  internalRelease(): Promise<void>;
@@ -278,7 +278,11 @@ export class MongodbQuerier extends AbstractQuerier {
278
278
  get hasOpenTransaction() {
279
279
  return !!this.session?.inTransaction();
280
280
  }
281
+ /** Every read and write goes through here, which makes it where a released querier is caught. */
281
282
  collection(entity) {
283
+ if (this.released) {
284
+ throw new TypeError('querier already released');
285
+ }
282
286
  const { name } = getMeta(entity);
283
287
  return this.db.collection(name);
284
288
  }
@@ -296,29 +300,33 @@ export class MongodbQuerier extends AbstractQuerier {
296
300
  this.session.startTransaction();
297
301
  });
298
302
  }
303
+ /**
304
+ * The driver owns the transaction state here and settles it in its own `finally`, so a failed commit
305
+ * or abort still leaves `inTransaction()` false and the querier releasable.
306
+ */
299
307
  async commitTransaction() {
300
308
  return this.serialize(async () => {
301
309
  if (!this.hasOpenTransaction) {
302
310
  throwNoPendingTransaction();
303
311
  }
304
312
  this.logger.logInfo('commitTransaction');
305
- await this.session.commitTransaction();
313
+ await this.session?.commitTransaction();
306
314
  });
307
315
  }
308
316
  async rollbackTransaction() {
309
317
  return this.serialize(async () => {
310
318
  if (!this.hasOpenTransaction) {
311
- throwNoPendingTransaction();
319
+ return;
312
320
  }
313
321
  this.logger.logInfo('rollbackTransaction');
314
- await this.session.abortTransaction();
322
+ await this.session?.abortTransaction();
315
323
  });
316
324
  }
317
325
  async internalRelease() {
318
- if (this.hasOpenTransaction) {
319
- throwPendingTransaction();
320
- }
321
- await this.session?.endSession();
326
+ const session = this.session;
327
+ // Cleared first, so a failing `endSession` cannot leave the querier holding a session it already
328
+ // tried to end.
322
329
  this.session = undefined;
330
+ await session?.endSession();
323
331
  }
324
332
  }
@@ -7,5 +7,5 @@ export declare class MySql2Querier extends AbstractPoolQuerier<PoolConnection> {
7
7
  internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
8
8
  internalRun(query: string, values?: unknown[]): Promise<import("../type/query.js").QueryUpdateResult>;
9
9
  internalStream<T>(query: string, values?: unknown[]): AsyncGenerator<Awaited<T>, void, unknown>;
10
- protected releaseConn(conn: PoolConnection): Promise<void>;
10
+ protected releaseConn(conn: PoolConnection, discard: boolean): Promise<void>;
11
11
  }