uql-orm 0.72.2 → 0.73.1

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.
@@ -1,4 +1,4 @@
1
- var p=[];function y(e){for(let r of p)r(e)}function _(e){p.push(e);let r=p.length-1;return()=>{p.splice(r,1)}}var P=["$select","$populate","$exclude","$where","$sort"],E=["$count"],O=["$skip","$limit"],f=["$candidates"],T=["$distinct"],A=["$lock",...E,...f];var b=Symbol("rawValue"),W=Symbol("rawAlias");function c(e){return e?Object.keys(e):[]}function C(e){if(typeof e!=="object"||e===null)return!0;if(Array.isArray(e))return!1;let r=Object.getPrototypeOf(e);return r!==Object.prototype&&r!==null}var Y=new Set([...P,...E,...O,...f,...T,"hardDelete","count"]);function i(e){if(!e)return"";let r=new URLSearchParams;for(let n of c(e)){let o=e[n];if(o===void 0)continue;r.append(n,typeof o==="object"&&o!==null?u(o):String(o))}let t=r.toString();return t?`?${t}`:""}function u(e){return JSON.stringify(e,(r,t)=>{if(typeof t!=="object"||t===null)return t;if(b in t)throw TypeError("raw SQL cannot travel over HTTP: what leaves the browser is JSON");if(t instanceof ArrayBuffer||ArrayBuffer.isView(t))throw TypeError("binary cannot travel over HTTP: what leaves the browser is JSON");return t})}class k extends Error{status;constructor(e,r){super(e);this.status=r;this.name="RequestError"}}function R(e,r){return a(e,{method:"get"},r)}function h(e,r,t){return a(e,{method:"post",body:u(r)},t)}function x(e,r,t){return a(e,{method:"patch",body:u(r)},t)}function Q(e,r,t){return a(e,{method:"put",body:u(r)},t)}function g(e,r){return a(e,{method:"delete"},r)}function K(e,r,t){return a(e,{method:"QUERY",body:u(r)},t)}function a(e,r,t){if(y({phase:"start",opts:t}),r.headers={accept:"application/json","content-type":"application/json",...t?.headers},t?.signal)r.signal=t.signal;return fetch(e,r).then((n)=>n.json().then((o)=>{if(n.status>=200&&n.status<300)return y({phase:"success",opts:t}),o;let S=o,l={message:S?.error?.message??n.statusText,code:S?.error?.code??n.status};throw y({phase:"error",error:l,opts:t}),new k(l.message,l.code)})).finally(()=>{y({phase:"complete",opts:t})})}function U(e){let r=e.charAt(0).toLowerCase();for(let t=1;t<e.length;++t)r+=e[t]===e[t].toUpperCase()?"-"+e[t].toLowerCase():e[t];return r}var s={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:""}},V=c(s),re=new Map(V.filter((e)=>s[e].method==="GET"&&s[e].path!=="/:id").map((e)=>[s[e].path,e]));function j(e){return U(e.name)}function w(e,r){if(!C(r))throw TypeError(`'${e.name}' was addressed by an id object, which the HTTP route cannot carry.`);return String(r)}class m{basePath;defaults;constructor(e,r={}){this.basePath=e;this.defaults=r}async findOneById(e,r,t,n){let o=this.getBasePath(e),d=i(t);return R(`${o}/${w(e,r)}${d}`,this.buildOptions(n))}findOne(e,r,t){return this.read(`${this.getBasePath(e)}${s.findOne.path}`,r,t)}findMany(e,r,t){let n={...r};if(t?.count)n.count=!0;return this.read(this.getBasePath(e),n,t)}async findManyAndCount(e,r,t){let n=await this.findMany(e,r,{...t,count:!0});if(typeof n.count!=="number")throw TypeError("findManyAndCount response has an invalid count");return{...n,count:n.count}}count(e,r,t){return this.read(`${this.getBasePath(e)}${s.count.path}`,r,t)}async exists(e,r,t){let n=await this.count(e,{...r,$limit:1},t);return{...n,data:n.data>0}}insertOne(e,r,t){let n=this.getBasePath(e);return h(n,r,this.buildOptions(t))}insertMany(e,r,t){let n=this.getBasePath(e);return h(`${n}${s.insertMany.path}`,r,this.buildOptions(t))}async updateOneById(e,r,t,n){let o=this.getBasePath(e);return x(`${o}/${w(e,r)}`,t,this.buildOptions(n))}updateMany(e,r,t,n){let o=this.getBasePath(e),d=i(r);return x(`${o}${d}`,t,this.buildOptions(n))}saveOne(e,r,t){let n=this.getBasePath(e);return Q(n,r,this.buildOptions(t))}saveMany(e,r,t){let n=this.getBasePath(e);return Q(`${n}${s.saveMany.path}`,r,this.buildOptions(t))}async deleteOneById(e,r,t={}){let n=this.getBasePath(e),o=t.hardDelete?i({hardDelete:t.hardDelete}):"";return g(`${n}/${w(e,r)}${o}`,this.buildOptions(t))}deleteMany(e,r,t={}){let n=this.getBasePath(e),o=i(t.hardDelete?{...r,hardDelete:t.hardDelete}:r);return g(`${n}${o}`,this.buildOptions(t))}getBasePath(e){return`${this.basePath}/${(this.defaults.entityPath??j)(e)}`}read(e,r,t){if(this.defaults.readMethod==="QUERY")return K(e,r??{},this.buildOptions(t));return R(`${e}${i(r)}`,this.buildOptions(t))}buildOptions(e){if(!this.defaults.headers&&!e?.headers)return e;return{...e,headers:{...this.defaults.headers,...e?.headers}}}}var q={getQuerier:()=>new m("/api")};function de(e){q=e}function F(){return q}function pe(){return F().getQuerier()}export{m as HttpQuerier,k as RequestError,R as get,pe as getQuerier,F as getQuerierPool,y as notify,_ as on,x as patch,h as post,Q as put,K as query,g as remove,de as setQuerierPool};
1
+ var p=[];function y(e){for(let r of p)r(e)}function _(e){p.push(e);let r=p.length-1;return()=>{p.splice(r,1)}}var P=["$select","$populate","$exclude","$where","$sort"],E=["$count"],O=["$skip","$limit"],f=["$candidates"],T=["$distinct"],A=["$lock",...E,...f];var b=Symbol("rawValue"),W=Symbol("rawAlias");function c(e){return e?Object.keys(e):[]}function C(e){if(typeof e!=="object"||e===null)return!0;if(Array.isArray(e))return!1;let r=Object.getPrototypeOf(e);return r!==Object.prototype&&r!==null}var Y=new Set([...P,...E,...O,...f,...T,"hardDelete","count"]);function i(e){if(!e)return"";let r=new URLSearchParams;for(let n of c(e)){let o=e[n];if(o===void 0)continue;r.append(n,typeof o==="object"&&o!==null?u(o):String(o))}let t=r.toString();return t?`?${t}`:""}function u(e){return JSON.stringify(e,(r,t)=>{if(typeof t!=="object"||t===null)return t;if(b in t)throw TypeError("raw SQL cannot travel over HTTP: what leaves the browser is JSON");if(t instanceof ArrayBuffer||ArrayBuffer.isView(t))throw TypeError("binary cannot travel over HTTP: what leaves the browser is JSON");return t})}class k extends Error{status;constructor(e,r){super(e);this.status=r;this.name="RequestError"}}function R(e,r){return a(e,{method:"get"},r)}function h(e,r,t){return a(e,{method:"post",body:u(r)},t)}function x(e,r,t){return a(e,{method:"patch",body:u(r)},t)}function Q(e,r,t){return a(e,{method:"put",body:u(r)},t)}function g(e,r){return a(e,{method:"delete"},r)}function K(e,r,t){return a(e,{method:"QUERY",body:u(r)},t)}function a(e,r,t){if(y({phase:"start",opts:t}),r.headers={accept:"application/json","content-type":"application/json",...t?.headers},t?.signal)r.signal=t.signal;return fetch(e,r).then((n)=>n.json().then((o)=>{if(n.status>=200&&n.status<300)return y({phase:"success",opts:t}),o;let S=o,l={message:S?.error?.message??n.statusText,code:S?.error?.code??n.status};throw y({phase:"error",error:l,opts:t}),new k(l.message,l.code)})).finally(()=>{y({phase:"complete",opts:t})})}function U(e){let r=e.charAt(0).toLowerCase();for(let t=1;t<e.length;++t)r+=e[t]===e[t].toUpperCase()?"-"+e[t].toLowerCase():e[t];return r}var s={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:""}},F=c(s),re=new Map(F.filter((e)=>s[e].method==="GET"&&s[e].path!=="/:id").map((e)=>[s[e].path,e]));function j(e){return U(e.name)}function w(e,r){if(!C(r))throw TypeError(`'${e.name}' was addressed by an id object, which the HTTP route cannot carry.`);return String(r)}class m{basePath;defaults;constructor(e,r={}){this.basePath=e;this.defaults=r}async findOneById(e,r,t,n){let o=this.getBasePath(e),d=i(t);return R(`${o}/${w(e,r)}${d}`,this.buildOptions(n))}findOne(e,r,t){return this.read(`${this.getBasePath(e)}${s.findOne.path}`,r,t)}findMany(e,r,t){let n={...r};if(t?.count)n.count=!0;return this.read(this.getBasePath(e),n,t)}async findManyAndCount(e,r,t){let n=await this.findMany(e,r,{...t,count:!0});if(typeof n.count!=="number")throw TypeError("findManyAndCount response has an invalid count");return{...n,count:n.count}}count(e,r,t){return this.read(`${this.getBasePath(e)}${s.count.path}`,r,t)}async exists(e,r,t){let n=await this.count(e,{...r,$limit:1},t);return{...n,data:n.data>0}}insertOne(e,r,t){let n=this.getBasePath(e);return h(n,r,this.buildOptions(t))}insertMany(e,r,t){let n=this.getBasePath(e);return h(`${n}${s.insertMany.path}`,r,this.buildOptions(t))}async updateOneById(e,r,t,n){let o=this.getBasePath(e);return x(`${o}/${w(e,r)}`,t,this.buildOptions(n))}updateMany(e,r,t,n){let o=this.getBasePath(e),d=i(r);return x(`${o}${d}`,t,this.buildOptions(n))}saveOne(e,r,t){let n=this.getBasePath(e);return Q(n,r,this.buildOptions(t))}saveMany(e,r,t){let n=this.getBasePath(e);return Q(`${n}${s.saveMany.path}`,r,this.buildOptions(t))}async deleteOneById(e,r,t={}){let n=this.getBasePath(e),o=t.hardDelete?i({hardDelete:t.hardDelete}):"";return g(`${n}/${w(e,r)}${o}`,this.buildOptions(t))}deleteMany(e,r,t={}){let n=this.getBasePath(e),o=i(t.hardDelete?{...r,hardDelete:t.hardDelete}:r);return g(`${n}${o}`,this.buildOptions(t))}getBasePath(e){return`${this.basePath}/${(this.defaults.entityPath??j)(e)}`}read(e,r,t){if(this.defaults.readMethod==="QUERY")return K(e,r??{},this.buildOptions(t));return R(`${e}${i(r)}`,this.buildOptions(t))}buildOptions(e){if(!this.defaults.headers&&!e?.headers)return e;return{...e,headers:{...this.defaults.headers,...e?.headers}}}}var q={getQuerier:()=>new m("/api")};function de(e){q=e}function V(){return q}function pe(){return V().getQuerier()}export{m as HttpQuerier,k as RequestError,R as get,pe as getQuerier,V as getQuerierPool,y as notify,_ as on,x as patch,h as post,Q as put,K as query,g as remove,de as setQuerierPool};
2
2
 
3
- //# debugId=A77F643B44C4A82864756E2164756E21
3
+ //# debugId=F1CD14D3A7DD82FE64756E2164756E21
4
4
  //# sourceMappingURL=uql-browser.min.js.map
@@ -3,7 +3,7 @@
3
3
  "sources": ["../../src/browser/http/bus.ts", "../../src/type/query.ts", "../../src/type/queryRaw.ts", "../../src/util/object.util.ts", "../../src/http/query.ts", "../../src/browser/http/http.ts", "../../src/util/string.util.ts", "../../src/http/contract.ts", "../../src/browser/querier/httpQuerier.ts", "../../src/browser/options.ts"],
4
4
  "sourcesContent": [
5
5
  "import type { RequestCallback, RequestNotification } from '../type/index.js';\n\nconst subscriptors: RequestCallback[] = [];\n\nexport function notify(notification: RequestNotification): void {\n for (const subscriptor of subscriptors) {\n subscriptor(notification);\n }\n}\n\nexport function on(cb: RequestCallback): () => void {\n subscriptors.push(cb);\n const index = subscriptors.length - 1;\n return (): void => {\n subscriptors.splice(index, 1);\n };\n}\n",
6
- "import type { FieldKey, JsonFieldPaths, RelationKey, RelationTarget, ToManyRelationKey, WrittenId } from './entity.js';\nimport type { QueryLock } from './queryLock.js';\nimport type { QueryRaw } from './queryRaw.js';\nimport type { QueryWhere } from './queryWhere.js';\nimport type { BooleanLike, Except, IsMany, PrimaryKey } from './utility.js';\nimport type { QueryVectorSearch } from './vector.js';\n\nexport type QueryOptions = {\n /**\n * Toggle named entity filters for this query. `false` disables all filters;\n * `{ softDelete: false }` disables one; `{ myFilter: true }` force-enables a `default: false` filter.\n * Security filters cannot be disabled here.\n */\n filters?: false | Record<string, boolean>;\n /**\n * Delete only: physically remove rows instead of soft-deleting, ignoring the soft-delete filter so\n * already-deleted rows are removed too. No effect on entities without a soft-delete field.\n */\n hardDelete?: boolean;\n /**\n * `updateMany`/`deleteMany` only: address every row of the table on purpose. Without it a bulk write\n * that names none - no `$where` and no `$limit` - is refused, since a forgotten filter and the whole\n * table look alike. The entity's own filters never count as naming one.\n */\n unfiltered?: boolean;\n /**\n * prefix the query with this.\n */\n prefix?: string;\n /**\n * automatically infer the prefix for the query.\n */\n autoPrefix?: boolean;\n};\n\n/**\n * Field selection - `{ name: true }` whitelists fields; relations go in `$populate`. Declared over\n * `F extends keyof E`, like every map keyed by an entity's members, so each key stays linked to its\n * property and an editor rename reaches it. `F` is also how a projection passes its captured key set.\n */\nexport type QuerySelect<E, F extends keyof E = FieldKey<E>, V = BooleanLike> = {\n [K in F]?: V;\n};\n\n/**\n * Accepted `$select` value: a field map, or raw SQL projections built with `raw()`\n * (e.g. ``[raw`*`, raw`LOG10(points)`.as('score')]``). The raw form is SQL-only.\n */\nexport type QuerySelectValue<E, Raw = QueryRaw> = QuerySelect<E> | readonly Raw[];\n\n/**\n * Fields to exclude from the query result - `{ name: true }` blacklists fields.\n * Mutually exclusive with positive field selections in `$select`.\n */\nexport type QueryExclude<E> = QuerySelect<E>;\n\n/**\n * relation population map.\n */\nexport type QueryPopulate<E, Raw = QueryRaw, R extends keyof E = RelationKey<E>> = {\n [K in R]?: BooleanLike | QueryPopulateRelationOptions<E[K], Raw>;\n};\n\n/**\n * The key a read carries its relation tallies under. One spelling for the type and the runtime that\n * fills it: they sit in different modules, so a drift would type-check and answer `undefined`.\n */\nexport const COUNT_RESULT_KEY = '_count';\n\n/**\n * How many rows each named relation holds per parent, `true` for all of them or a filter to narrow\n * which ones count: a correlated count in the read's own statement, so no related row is loaded. Comes\n * back under `_count`, which keeps it clear of a relation of the same name `$populate` filled.\n */\nexport type QueryCount<E, Raw = QueryRaw, R extends keyof E = ToManyRelationKey<E>> = {\n [K in R]?: BooleanLike | QueryFilter<RelationTarget<E[K]>, Raw>;\n};\n\n/**\n * query conflict paths - subset of field keys used to detect upsert conflicts.\n */\nexport type QueryConflictPaths<E> = QuerySelect<E, FieldKey<E>, true>;\n\n/**\n * Options to populate a relation declared as `V`, by its cardinality.\n */\nexport type QueryPopulateRelationOptions<V, Raw = QueryRaw> =\n IsMany<V> extends true\n ? RelationQuery<RelationTarget<V>, Raw>\n : QueryUnique<RelationTarget<V>, Raw> & { $required?: boolean };\n\n/**\n * The per-request context parameterized filters read, set with `withContext(ctx, cb)`. An interface,\n * so its keys can be typed once: `declare module 'uql-orm' { interface UqlContext { tenantId: number } }`.\n */\nexport interface UqlContext {\n [key: string]: unknown;\n}\n\n/**\n * A filter's `$where` fragment: a plain fragment, or a function of the ambient {@link UqlContext}.\n * Return `undefined` when the condition can't resolve (see {@link FilterOptions.onMissing}).\n */\nexport type FilterWhere<E> = QueryWhere<E> | ((context: UqlContext | undefined) => QueryWhere<E> | undefined);\n\n/**\n * What to do when a filter's condition returns `undefined`. `skip` omits it (convenience filters);\n * `throw` fails closed (the default for `security` filters).\n */\nexport type FilterOnMissing = 'skip' | 'throw';\n\n/**\n * Authoring shape for `@Entity({ filters })` / `@Filter` / `defineFilter`.\n */\nexport type FilterOptions<E = unknown> = {\n readonly where: FilterWhere<E>;\n /** Applied to every query unless bypassed via `QueryOptions.filters`. Defaults to `true`. */\n readonly default?: boolean;\n} & (\n | {\n readonly security?: false;\n /** What to do when {@link FilterOptions.where} returns `undefined`. Defaults to `skip`. */\n readonly onMissing?: FilterOnMissing;\n }\n | {\n /**\n * Row-level-security filter: always applied (ignores `QueryOptions.filters` bypass) and\n * AND-merged so a client `$where` on the same field can't override it. It fails closed.\n */\n readonly security: true;\n readonly onMissing?: 'throw';\n }\n);\n\n/**\n * direction for the sort.\n */\nexport type QuerySortDirection = -1 | 1 | 'asc' | 'desc';\n\n/**\n * Accepted value for a field in `$sort` - either a direction or a vector similarity search.\n */\nexport type QuerySortValue = QuerySortDirection | QueryVectorSearch;\n\n/**\n * Ordering parents by how many rows a to-many relation holds - \"the ten users with the most posts\".\n * The tally is computed per parent as a correlated count, never by loading the rows.\n */\nexport type QuerySortByCount = {\n $count: QuerySortDirection;\n};\n\n/**\n * Ordering by relevance to the `$text` at the root of `$where`, in either direction as any key sorts. The\n * object form also answers it under the name `$project` gives it, most relevant first unless `$order` says.\n */\nexport type QuerySortByText = {\n $text?: QuerySortDirection | { readonly $project: string; readonly $order?: QuerySortDirection };\n};\n\n/**\n * A row with the relevance a `$sort: { $text: { $project } }` names, which is not inferred:\n * `(await querier.findMany(Post, q)) as WithScore<Post, 'score'>[]`.\n */\nexport type WithScore<E, K extends string> = E & Record<K, number>;\n\n/**\n * A sort by fields, JSON paths, a to-one relation's fields, a to-many's `$count`, or a vector distance or\n * `$text` relevance, which `Vector` confines to the queried entity. One mapped type over the key sets: an\n * intersection is checked once per member, which made this the costliest type to check.\n */\nexport type QuerySortMap<E, Vector extends boolean = true, K extends keyof E = FieldKey<E> | RelationKey<E>> = {\n [P in K]?: P extends RelationKey<E>\n ? // A to-many has no single value to order by, so what it offers instead is its own size.\n IsMany<E[P]> extends true\n ? QuerySortByCount\n : QuerySortMap<RelationTarget<E[P]>, false>\n : Vector extends true\n ? NonNullable<E[P]> extends readonly number[]\n ? QuerySortValue\n : QuerySortDirection\n : QuerySortDirection;\n} & ([JsonFieldPaths<E>] extends [never] ? unknown : { [P in JsonFieldPaths<E>]?: QuerySortDirection }) &\n (Vector extends true ? QuerySortByText : unknown);\n\n/**\n * pager options.\n */\nexport type QueryPager = {\n /**\n * Index from where start the search\n */\n $skip?: number;\n\n /**\n * Max number of records to retrieve\n */\n $limit?: number;\n};\n\n/**\n * Which rows a statement addresses.\n */\nexport type QueryFilter<E, Raw = QueryRaw> = {\n /**\n * filtering options.\n */\n $where?: QueryWhere<E, Raw>;\n};\n\n/**\n * A filter plus the page `count` takes. No `$sort`: ordering picks *which* rows a page holds, never\n * how many, so a count that accepted one would promise an influence it cannot have.\n */\nexport type QueryPage<E, Raw = QueryRaw> = QueryFilter<E, Raw> & QueryPager;\n\n/**\n * A filter plus the ordering and page `updateMany`/`deleteMany` take. Both settle the\n * rows they address with a SELECT first, so the page is portable rather than MySQL-only, and a\n * vector `$sort` is as valid here as on a read: it ranks the settle query's rows, which has the\n * projection list to hold the distance. `$lock` stays off these, declared on {@link Query} instead.\n */\nexport type QuerySearch<E, Raw = QueryRaw> = QueryPage<E, Raw> & {\n /**\n * sorting options.\n */\n $sort?: QuerySortMap<E>;\n};\n\n/**\n * query options.\n */\nexport type Query<E, Raw = QueryRaw> = {\n /**\n * field selection - `{ name: true }` whitelists fields, or raw SQL projections\n * (``[raw`LOG10(points)`.as('score')]``, SQL dialects only - MongoDB rejects the raw-array form).\n * Mutually exclusive with `$exclude`.\n */\n $select?: QuerySelectValue<E, Raw>;\n\n /**\n * relation population options.\n */\n $populate?: QueryPopulate<E, Raw>;\n\n /**\n * how many rows each named relation holds, under `_count` on every row. See {@link QueryCount}.\n */\n $count?: QueryCount<E, Raw>;\n\n /**\n * field exclusion - `{ name: true }` blacklists fields. Mutually exclusive with positive `$select`.\n * Keys a relation is assembled from (a joined row's primary key, a to-many's foreign key) are kept\n * regardless, since subtracting them would leave the relation unfilled.\n */\n $exclude?: QueryExclude<E>;\n\n /**\n * sorting options, vector similarity search included: a SELECT is the one statement with a\n * projection list to hold the distance such a search computes.\n */\n $sort?: QuerySortMap<E>;\n\n /**\n * whether to return only distinct rows.\n */\n $distinct?: boolean;\n\n /**\n * Lock the rows this query returns, `SELECT ... FOR UPDATE`, inside an open transaction: outside one\n * it is refused, since the lock would drop before the rows are used. SQL only, and not the SQLite family.\n */\n $lock?: QueryLock;\n\n /**\n * How many candidates an ANN index explores before ranking a vector search, in that index's own units\n * (`hnsw.ef_search`, `numCandidates`...); ignored where the search is exact. Postgres needs a transaction.\n */\n $candidates?: number;\n\n // `$where`, `$skip` and `$limit` are declared here rather than intersected in from\n // {@link QueryFilter} and {@link QueryPager}: an assignability check against an intersection is\n // repeated per constituent, and every query in a consuming codebase pays that. The two shapes are\n // pinned together in `queryStatementClauses.test-d.ts` so the copies cannot drift.\n\n /**\n * filtering options.\n */\n $where?: QueryWhere<E, Raw>;\n\n /**\n * Index from where start the search\n */\n $skip?: number;\n\n /**\n * Max number of records to retrieve\n */\n $limit?: number;\n};\n\n/**\n * A {@link Query} as it travels as JSON, which a `raw` SQL fragment cannot: what the browser client takes,\n * and what an RPC contract (tRPC, oRPC, TanStack Start) declares as its input.\n */\nexport type WireQuery<E> = Query<E, never>;\n\n/**\n * `Query`'s clauses grouped by the shape of their value, for the wire parser and the relation query\n * check alike; `satisfies` keeps them in step with `Query`.\n */\nexport const QUERY_OBJECT_CLAUSES = [\n '$select',\n '$populate',\n '$exclude',\n '$where',\n '$sort',\n] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Object clauses only the statement itself takes: a populated relation's rows keep their declared type,\n * so a `$count` inside one would have no `_count` to land in.\n */\nexport const QUERY_ROOT_OBJECT_CLAUSES = ['$count'] as const satisfies readonly (keyof Query<unknown>)[];\n\nexport const QUERY_NUMBER_CLAUSES = ['$skip', '$limit'] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Number clauses only the statement itself takes - the numeric mirror of {@link QUERY_ROOT_OBJECT_CLAUSES}.\n * `$candidates` tunes the index behind a vector search, and a vector search only ever ranks the rows\n * the statement returns, so a relation's own query has nothing to tune.\n */\nexport const QUERY_ROOT_NUMBER_CLAUSES = ['$candidates'] as const satisfies readonly (keyof Query<unknown>)[];\n\nexport const QUERY_BOOLEAN_CLAUSES = ['$distinct'] as const satisfies readonly (keyof Query<unknown>)[];\n\n/** The clauses that describe the statement, which a populated relation's own query refuses by name. */\nexport const QUERY_STATEMENT_CLAUSES = [\n '$lock',\n ...QUERY_ROOT_OBJECT_CLAUSES,\n ...QUERY_ROOT_NUMBER_CLAUSES,\n] as const satisfies readonly (keyof Query<unknown>)[];\n\ntype RelationClause = (\n | typeof QUERY_OBJECT_CLAUSES\n | typeof QUERY_NUMBER_CLAUSES\n | typeof QUERY_BOOLEAN_CLAUSES\n)[number];\n\n/**\n * A populated relation's own query: the clause groups its runtime check accepts, so the two cannot\n * drift, and a clause added to {@link Query} stays off it until it joins one of them.\n */\nexport type RelationQuery<E = object, Raw = QueryRaw> = Pick<Query<E, Raw>, RelationClause> & {\n $required?: boolean;\n};\n\n/**\n * options to get a single record.\n */\nexport type QueryOne<E, Raw = QueryRaw> = Except<Query<E, Raw>, '$limit'>;\n\n/**\n * options to get an unique record.\n */\nexport type QueryUnique<E, Raw = QueryRaw> = Pick<QueryOne<E, Raw>, '$select' | '$exclude' | '$populate' | '$where'>;\n\n/**\n * The clauses that shape a row, captured as key sets rather than maps: a naked type parameter skips\n * excess-property checks, while a key set fails its own constraint on a typo.\n * @internal\n */\ntype QueryProjection<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n Raw = QueryRaw,\n> = {\n $select?: QuerySelect<E, S, V> | readonly Raw[];\n $exclude?: QuerySelect<E, X, V>;\n $populate?: QueryPopulate<E, Raw, P>;\n // Narrowing the captured names to the to-many ones leaves a to-one relation no key here at all,\n // so counting one is an excess property rather than a value to check.\n $count?: QueryCount<E, Raw, C & ToManyRelationKey<E>>;\n};\n\n/**\n * A {@link Query} whose projection is captured, so {@link QueryFindResult} can shape the row.\n */\nexport type QueryProjected<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E> = never,\n Raw = QueryRaw,\n> = Query<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;\n\n/**\n * A {@link QueryOne} whose projection is captured, so {@link QueryFindResult} can shape the row.\n */\nexport type QueryOneProjected<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E> = never,\n Raw = QueryRaw,\n> = QueryOne<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;\n\n/**\n * The keys a query comes back with, as the runtime projects them: a positive `$select`'s, or every\n * field minus what `$select` or `$exclude` subtracts, plus the populated relations.\n * @internal\n */\ntype ProjectedKeys<E, S, V, X, P> =\n | ([V] extends [false | 0] ? Exclude<FieldKey<E>, S> : [S] extends [never] ? Exclude<FieldKey<E>, X> : S)\n | P;\n\n/**\n * Whether every entry of the captured map says the same thing: all selected, or all subtracted.\n * @internal\n */\ntype IsUniform<V> = [V] extends [true | 1] ? true : [V] extends [false | 0] ? true : false;\n\n/**\n * A find's row: the entity narrowed to what the query projected and populated, so reading anything\n * else does not compile. The entity itself where the projection is raw, absent or not uniform.\n * @example `QueryFindResult<User, 'id' | 'name'>`\n */\nexport type QueryFindResult<\n E,\n S extends FieldKey<E> = never,\n // A whitelist by default, so the hand-written form reads `QueryFindResult<User, 'id' | 'name'>`.\n V = true,\n X extends FieldKey<E> = never,\n P extends RelationKey<E> = never,\n C extends RelationKey<E> = never,\n> = QueryProjectedRow<E, S, V, X, P, C> & CountedRelations<C>;\n\n/**\n * The `_count` a query asked for, or an inert intersection member when it asked for none - so a read\n * without `$count` keeps exactly the row type it had.\n */\ntype CountedRelations<C extends PropertyKey> = [C] extends [never]\n ? unknown\n : { [K in typeof COUNT_RESULT_KEY]: { [R in C]: number } };\n\n/** @internal */\ntype QueryProjectedRow<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n> = [S | X] extends [never]\n ? E\n : IsUniform<V> extends true\n ? [PopulatedToMany<E, P>] extends [never]\n ? // `Pick`, not a key remap: an entity keyed by an index signature - a content type defined at\n // runtime - has `string` for its keys, and a remap keeps no literal one, so every projection\n // over one came back as `{}`.\n Pick<E, ProjectedKeys<E, S, V, X, P> & keyof E>\n : // A populated to-many is always a list, empty where the parent has no children, so it maps\n // and counts without a guard. Only that promotion needs a second member, and only a query\n // that populates one pays for it; every other key keeps the modifier the entity declared,\n // a to-one relation included, since a join that finds no row leaves it absent.\n Pick<E, Exclude<ProjectedKeys<E, S, V, X, P>, PopulatedToMany<E, P>> & keyof E> & {\n [K in PopulatedToMany<E, P>]-?: NonNullable<E[K]>;\n }\n : E;\n\n/** The to-many relations a query populated, which come back as lists rather than as optional ones. */\ntype PopulatedToMany<E, P> = Extract<P, ToManyRelationKey<E>>;\n\n/**\n * stringified query.\n */\nexport type QueryStringified = {\n [K in keyof Query<unknown>]?: string;\n};\n\n/** What upserting one row reports. `created` is only knowable for a single statement, so a batch has none. */\nexport type QueryUpsertOneResult<E> = {\n readonly id?: WrittenId<E>;\n readonly changes?: number;\n /** Whether the record was created (`true`) or updated (`false`), where the dialect can tell. */\n readonly created?: boolean;\n};\n\n/**\n * What upserting many rows reports. `ids` is payload-aligned like an insert's, so it zips with the\n * rows that were passed, and carries a composite key as the map naming it.\n */\nexport type QueryUpsertManyResult<E> = {\n readonly ids: (WrittenId<E> | undefined)[];\n readonly changes?: number;\n};\n\n/**\n * result of an update operation, as the driver reports it - which is what `run` hands back, where\n * there is no entity to name the ids against. The `QueryUpsert*Result` pair is the entity-level shape.\n */\nexport type QueryUpdateResult = {\n /**\n * number of affected records.\n */\n changes?: number;\n /**\n * the IDs the statement reported, in payload order, `undefined` where it reported none for that\n * row - a MongoDB upsert names only the documents it inserted. Exact on `'returning'` dialects;\n * inferred from the driver header on the others (see {@link InsertIdSource}), and absent\n * altogether when the header reports nothing.\n */\n ids?: (PrimaryKey | undefined)[];\n /**\n * first inserted ID.\n */\n firstId?: PrimaryKey;\n /**\n * whether the record was created (`true`) or updated (`false`).\n * `undefined` when the dialect cannot determine this (e.g. SQLite).\n */\n created?: boolean;\n};\n",
6
+ "import type { FieldKey, JsonFieldPaths, RelationKey, RelationTarget, ToManyRelationKey, WrittenId } from './entity.js';\nimport type { QueryLock } from './queryLock.js';\nimport type { QueryRaw } from './queryRaw.js';\nimport type { QueryWhere } from './queryWhere.js';\nimport type { BooleanLike, Except, IsMany, PrimaryKey } from './utility.js';\nimport type { QueryVectorSearch } from './vector.js';\n\nexport type QueryOptions = {\n /**\n * Toggle named entity filters for this query. `false` disables all filters;\n * `{ softDelete: false }` disables one; `{ myFilter: true }` force-enables a `default: false` filter.\n * Security filters cannot be disabled here.\n */\n filters?: false | Record<string, boolean>;\n /**\n * Delete only: physically remove rows instead of soft-deleting, ignoring the soft-delete filter so\n * already-deleted rows are removed too. No effect on entities without a soft-delete field.\n */\n hardDelete?: boolean;\n /**\n * `updateMany`/`deleteMany` only: address every row of the table on purpose. Without it a bulk write\n * that names none - no `$where` and no `$limit` - is refused, since a forgotten filter and the whole\n * table look alike. The entity's own filters never count as naming one.\n */\n unfiltered?: boolean;\n /**\n * prefix the query with this.\n */\n prefix?: string;\n /**\n * automatically infer the prefix for the query.\n */\n autoPrefix?: boolean;\n};\n\n/**\n * Field selection - `{ name: true }` whitelists fields; relations go in `$populate`. Declared over\n * `F extends keyof E`, like every map keyed by an entity's members, so each key stays linked to its\n * property and an editor rename reaches it. `F` is also how a projection passes its captured key set.\n */\nexport type QuerySelect<E, F extends keyof E = FieldKey<E>, V = BooleanLike> = {\n [K in F]?: V;\n};\n\n/**\n * Accepted `$select` value: a field map, or raw SQL projections built with `raw()`\n * (e.g. ``[raw`*`, raw`LOG10(points)`.as('score')]``). The raw form is SQL-only.\n */\nexport type QuerySelectValue<E, Raw = QueryRaw> = QuerySelect<E> | readonly Raw[];\n\n/**\n * Fields to exclude from the query result - `{ name: true }` blacklists fields.\n * Mutually exclusive with positive field selections in `$select`.\n */\nexport type QueryExclude<E> = QuerySelect<E>;\n\n/**\n * relation population map.\n */\nexport type QueryPopulate<E, Raw = QueryRaw, R extends keyof E = RelationKey<E>> = {\n [K in R]?: BooleanLike | QueryPopulateRelationOptions<E[K], Raw>;\n};\n\n/**\n * The key a read carries its relation tallies under. One spelling for the type and the runtime that\n * fills it: they sit in different modules, so a drift would type-check and answer `undefined`.\n */\nexport const COUNT_RESULT_KEY = '_count';\n\n/**\n * How many rows each named relation holds per parent, `true` for all of them or a filter to narrow\n * which ones count: a correlated count in the read's own statement, so no related row is loaded. Comes\n * back under `_count`, which keeps it clear of a relation of the same name `$populate` filled.\n */\nexport type QueryCount<E, Raw = QueryRaw, R extends keyof E = ToManyRelationKey<E>> = {\n [K in R]?: BooleanLike | QueryFilter<RelationTarget<E[K]>, Raw>;\n};\n\n/**\n * query conflict paths - subset of field keys used to detect upsert conflicts.\n */\nexport type QueryConflictPaths<E> = QuerySelect<E, FieldKey<E>, true>;\n\n/**\n * Options to populate a relation declared as `V`, by its cardinality.\n */\nexport type QueryPopulateRelationOptions<V, Raw = QueryRaw> =\n IsMany<V> extends true\n ? RelationQuery<RelationTarget<V>, Raw>\n : QueryUnique<RelationTarget<V>, Raw> & { $required?: boolean };\n\n/**\n * The per-request context parameterized filters read, set with `withContext(ctx, cb)`. An interface,\n * so its keys can be typed once: `declare module 'uql-orm' { interface UqlContext { tenantId: number } }`.\n */\nexport interface UqlContext {\n [key: string]: unknown;\n}\n\n/**\n * A filter's `$where` fragment: a plain fragment, or a function of the ambient {@link UqlContext}.\n * Return `undefined` when the condition can't resolve (see {@link FilterOptions.onMissing}).\n */\nexport type FilterWhere<E> = QueryWhere<E> | ((context: UqlContext | undefined) => QueryWhere<E> | undefined);\n\n/**\n * What to do when a filter's condition returns `undefined`. `skip` omits it (convenience filters);\n * `throw` fails closed (the default for `security` filters).\n */\nexport type FilterOnMissing = 'skip' | 'throw';\n\n/**\n * Authoring shape for `@Entity({ filters })` / `@Filter` / `defineFilter`.\n */\nexport type FilterOptions<E = unknown> = {\n readonly where: FilterWhere<E>;\n /** Applied to every query unless bypassed via `QueryOptions.filters`. Defaults to `true`. */\n readonly default?: boolean;\n} & (\n | {\n readonly security?: false;\n /** What to do when {@link FilterOptions.where} returns `undefined`. Defaults to `skip`. */\n readonly onMissing?: FilterOnMissing;\n }\n | {\n /**\n * Row-level-security filter: always applied (ignores `QueryOptions.filters` bypass) and\n * AND-merged so a client `$where` on the same field can't override it. It fails closed.\n */\n readonly security: true;\n readonly onMissing?: 'throw';\n }\n);\n\n/**\n * direction for the sort.\n */\nexport type QuerySortDirection = -1 | 1 | 'asc' | 'desc';\n\n/**\n * Accepted value for a field in `$sort` - either a direction or a vector similarity search.\n */\nexport type QuerySortValue = QuerySortDirection | QueryVectorSearch;\n\n/**\n * Ordering parents by how many rows a to-many relation holds - \"the ten users with the most posts\".\n * The tally is computed per parent as a correlated count, never by loading the rows.\n */\nexport type QuerySortByCount = {\n $count: QuerySortDirection;\n};\n\n/**\n * Ordering by relevance to the `$text` at the root of `$where`, in either direction as any key sorts. The\n * object form also answers it under the name `$project` gives it, most relevant first unless `$order` says.\n */\nexport type QuerySortByText = {\n $text?: QuerySortDirection | { readonly $project: string; readonly $order?: QuerySortDirection };\n};\n\n/**\n * A row with the value a `$sort` projects under the name its `$project` gives - a vector's distance, or a\n * `$text` relevance - which is not inferred: `(await querier.findMany(Post, q)) as WithProjection<Post, 'score'>[]`.\n */\nexport type WithProjection<E, K extends string> = E & Record<K, number>;\n\n/**\n * A sort by fields, JSON paths, a to-one relation's fields, a to-many's `$count`, or - where `Root` says it\n * sorts the queried entity itself, not a relation's rows - a vector distance or a `$text` relevance. One\n * mapped type over the key sets: an intersection is checked once per member, which made this the costliest.\n */\nexport type QuerySortMap<E, Root extends boolean = true, K extends keyof E = FieldKey<E> | RelationKey<E>> = {\n [P in K]?: P extends RelationKey<E>\n ? // A to-many has no single value to order by, so what it offers instead is its own size.\n IsMany<E[P]> extends true\n ? QuerySortByCount\n : QuerySortMap<RelationTarget<E[P]>, false>\n : Root extends true\n ? NonNullable<E[P]> extends readonly number[]\n ? QuerySortValue\n : QuerySortDirection\n : QuerySortDirection;\n} & ([JsonFieldPaths<E>] extends [never] ? unknown : { [P in JsonFieldPaths<E>]?: QuerySortDirection }) &\n (Root extends true ? QuerySortByText : unknown);\n\n/**\n * pager options.\n */\nexport type QueryPager = {\n /**\n * Index from where start the search\n */\n $skip?: number;\n\n /**\n * Max number of records to retrieve\n */\n $limit?: number;\n};\n\n/**\n * Which rows a statement addresses.\n */\nexport type QueryFilter<E, Raw = QueryRaw> = {\n /**\n * filtering options.\n */\n $where?: QueryWhere<E, Raw>;\n};\n\n/**\n * A filter plus the page `count` takes. No `$sort`: ordering picks *which* rows a page holds, never\n * how many, so a count that accepted one would promise an influence it cannot have.\n */\nexport type QueryPage<E, Raw = QueryRaw> = QueryFilter<E, Raw> & QueryPager;\n\n/**\n * A filter plus the ordering and page `updateMany`/`deleteMany` take. Both settle the\n * rows they address with a SELECT first, so the page is portable rather than MySQL-only, and a\n * vector `$sort` is as valid here as on a read: it ranks the settle query's rows, which has the\n * projection list to hold the distance. `$lock` stays off these, declared on {@link Query} instead.\n */\nexport type QuerySearch<E, Raw = QueryRaw> = QueryPage<E, Raw> & {\n /**\n * sorting options.\n */\n $sort?: QuerySortMap<E>;\n};\n\n/**\n * query options.\n */\nexport type Query<E, Raw = QueryRaw> = {\n /**\n * field selection - `{ name: true }` whitelists fields, or raw SQL projections\n * (``[raw`LOG10(points)`.as('score')]``, SQL dialects only - MongoDB rejects the raw-array form).\n * Mutually exclusive with `$exclude`.\n */\n $select?: QuerySelectValue<E, Raw>;\n\n /**\n * relation population options.\n */\n $populate?: QueryPopulate<E, Raw>;\n\n /**\n * how many rows each named relation holds, under `_count` on every row. See {@link QueryCount}.\n */\n $count?: QueryCount<E, Raw>;\n\n /**\n * field exclusion - `{ name: true }` blacklists fields. Mutually exclusive with positive `$select`.\n * Keys a relation is assembled from (a joined row's primary key, a to-many's foreign key) are kept\n * regardless, since subtracting them would leave the relation unfilled.\n */\n $exclude?: QueryExclude<E>;\n\n /**\n * sorting options, vector similarity search included: a SELECT is the one statement with a\n * projection list to hold the distance such a search computes.\n */\n $sort?: QuerySortMap<E>;\n\n /**\n * whether to return only distinct rows.\n */\n $distinct?: boolean;\n\n /**\n * Lock the rows this query returns, `SELECT ... FOR UPDATE`, inside an open transaction: outside one\n * it is refused, since the lock would drop before the rows are used. SQL only, and not the SQLite family.\n */\n $lock?: QueryLock;\n\n /**\n * How many candidates an ANN index explores before ranking a vector search, in that index's own units\n * (`hnsw.ef_search`, `numCandidates`...); ignored where the search is exact. Postgres needs a transaction.\n */\n $candidates?: number;\n\n // `$where`, `$skip` and `$limit` are declared here rather than intersected in from\n // {@link QueryFilter} and {@link QueryPager}: an assignability check against an intersection is\n // repeated per constituent, and every query in a consuming codebase pays that. The two shapes are\n // pinned together in `queryStatementClauses.test-d.ts` so the copies cannot drift.\n\n /**\n * filtering options.\n */\n $where?: QueryWhere<E, Raw>;\n\n /**\n * Index from where start the search\n */\n $skip?: number;\n\n /**\n * Max number of records to retrieve\n */\n $limit?: number;\n};\n\n/**\n * A {@link Query} as it travels as JSON, which a `raw` SQL fragment cannot: what the browser client takes,\n * and what an RPC contract (tRPC, oRPC, TanStack Start) declares as its input.\n */\nexport type WireQuery<E> = Query<E, never>;\n\n/**\n * `Query`'s clauses grouped by the shape of their value, for the wire parser and the relation query\n * check alike; `satisfies` keeps them in step with `Query`.\n */\nexport const QUERY_OBJECT_CLAUSES = [\n '$select',\n '$populate',\n '$exclude',\n '$where',\n '$sort',\n] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Object clauses only the statement itself takes: a populated relation's rows keep their declared type,\n * so a `$count` inside one would have no `_count` to land in.\n */\nexport const QUERY_ROOT_OBJECT_CLAUSES = ['$count'] as const satisfies readonly (keyof Query<unknown>)[];\n\nexport const QUERY_NUMBER_CLAUSES = ['$skip', '$limit'] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Number clauses only the statement itself takes - the numeric mirror of {@link QUERY_ROOT_OBJECT_CLAUSES}.\n * `$candidates` tunes the index behind a vector search, and a vector search only ever ranks the rows\n * the statement returns, so a relation's own query has nothing to tune.\n */\nexport const QUERY_ROOT_NUMBER_CLAUSES = ['$candidates'] as const satisfies readonly (keyof Query<unknown>)[];\n\nexport const QUERY_BOOLEAN_CLAUSES = ['$distinct'] as const satisfies readonly (keyof Query<unknown>)[];\n\n/** The clauses that describe the statement, which a populated relation's own query refuses by name. */\nexport const QUERY_STATEMENT_CLAUSES = [\n '$lock',\n ...QUERY_ROOT_OBJECT_CLAUSES,\n ...QUERY_ROOT_NUMBER_CLAUSES,\n] as const satisfies readonly (keyof Query<unknown>)[];\n\ntype RelationClause = (\n | typeof QUERY_OBJECT_CLAUSES\n | typeof QUERY_NUMBER_CLAUSES\n | typeof QUERY_BOOLEAN_CLAUSES\n)[number];\n\n/**\n * A populated relation's own query: the clause groups its runtime check accepts, so the two cannot\n * drift, and a clause added to {@link Query} stays off it until it joins one of them.\n */\nexport type RelationQuery<E = object, Raw = QueryRaw> = Pick<Query<E, Raw>, RelationClause> & {\n $required?: boolean;\n};\n\n/**\n * options to get a single record.\n */\nexport type QueryOne<E, Raw = QueryRaw> = Except<Query<E, Raw>, '$limit'>;\n\n/**\n * options to get an unique record.\n */\nexport type QueryUnique<E, Raw = QueryRaw> = Pick<QueryOne<E, Raw>, '$select' | '$exclude' | '$populate' | '$where'>;\n\n/**\n * The clauses that shape a row, captured as key sets rather than maps: a naked type parameter skips\n * excess-property checks, while a key set fails its own constraint on a typo.\n * @internal\n */\ntype QueryProjection<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n Raw = QueryRaw,\n> = {\n $select?: QuerySelect<E, S, V> | readonly Raw[];\n $exclude?: QuerySelect<E, X, V>;\n $populate?: QueryPopulate<E, Raw, P>;\n // Narrowing the captured names to the to-many ones leaves a to-one relation no key here at all,\n // so counting one is an excess property rather than a value to check.\n $count?: QueryCount<E, Raw, C & ToManyRelationKey<E>>;\n};\n\n/**\n * A {@link Query} whose projection is captured, so {@link QueryFindResult} can shape the row.\n */\nexport type QueryProjected<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E> = never,\n Raw = QueryRaw,\n> = Query<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;\n\n/**\n * A {@link QueryOne} whose projection is captured, so {@link QueryFindResult} can shape the row.\n */\nexport type QueryOneProjected<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E> = never,\n Raw = QueryRaw,\n> = QueryOne<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;\n\n/**\n * The keys a query comes back with, as the runtime projects them: a positive `$select`'s, or every\n * field minus what `$select` or `$exclude` subtracts, plus the populated relations.\n * @internal\n */\ntype ProjectedKeys<E, S, V, X, P> =\n | ([V] extends [false | 0] ? Exclude<FieldKey<E>, S> : [S] extends [never] ? Exclude<FieldKey<E>, X> : S)\n | P;\n\n/**\n * Whether every entry of the captured map says the same thing: all selected, or all subtracted.\n * @internal\n */\ntype IsUniform<V> = [V] extends [true | 1] ? true : [V] extends [false | 0] ? true : false;\n\n/**\n * A find's row: the entity narrowed to what the query projected and populated, so reading anything\n * else does not compile. The entity itself where the projection is raw, absent or not uniform.\n * @example `QueryFindResult<User, 'id' | 'name'>`\n */\nexport type QueryFindResult<\n E,\n S extends FieldKey<E> = never,\n // A whitelist by default, so the hand-written form reads `QueryFindResult<User, 'id' | 'name'>`.\n V = true,\n X extends FieldKey<E> = never,\n P extends RelationKey<E> = never,\n C extends RelationKey<E> = never,\n> = QueryProjectedRow<E, S, V, X, P, C> & CountedRelations<C>;\n\n/**\n * The `_count` a query asked for, or an inert intersection member when it asked for none - so a read\n * without `$count` keeps exactly the row type it had.\n */\ntype CountedRelations<C extends PropertyKey> = [C] extends [never]\n ? unknown\n : { [K in typeof COUNT_RESULT_KEY]: { [R in C]: number } };\n\n/** @internal */\ntype QueryProjectedRow<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n> = [S | X] extends [never]\n ? E\n : IsUniform<V> extends true\n ? [PopulatedToMany<E, P>] extends [never]\n ? // `Pick`, not a key remap: an entity keyed by an index signature - a content type defined at\n // runtime - has `string` for its keys, and a remap keeps no literal one, so every projection\n // over one came back as `{}`.\n Pick<E, ProjectedKeys<E, S, V, X, P> & keyof E>\n : // A populated to-many is always a list, empty where the parent has no children, so it maps\n // and counts without a guard. Only that promotion needs a second member, and only a query\n // that populates one pays for it; every other key keeps the modifier the entity declared,\n // a to-one relation included, since a join that finds no row leaves it absent.\n Pick<E, Exclude<ProjectedKeys<E, S, V, X, P>, PopulatedToMany<E, P>> & keyof E> & {\n [K in PopulatedToMany<E, P>]-?: NonNullable<E[K]>;\n }\n : E;\n\n/** The to-many relations a query populated, which come back as lists rather than as optional ones. */\ntype PopulatedToMany<E, P> = Extract<P, ToManyRelationKey<E>>;\n\n/**\n * stringified query.\n */\nexport type QueryStringified = {\n [K in keyof Query<unknown>]?: string;\n};\n\n/** What upserting one row reports. `created` is only knowable for a single statement, so a batch has none. */\nexport type QueryUpsertOneResult<E> = {\n readonly id?: WrittenId<E>;\n readonly changes?: number;\n /** Whether the record was created (`true`) or updated (`false`), where the dialect can tell. */\n readonly created?: boolean;\n};\n\n/**\n * What upserting many rows reports. `ids` is payload-aligned like an insert's, so it zips with the\n * rows that were passed, and carries a composite key as the map naming it.\n */\nexport type QueryUpsertManyResult<E> = {\n readonly ids: (WrittenId<E> | undefined)[];\n readonly changes?: number;\n};\n\n/**\n * result of an update operation, as the driver reports it - which is what `run` hands back, where\n * there is no entity to name the ids against. The `QueryUpsert*Result` pair is the entity-level shape.\n */\nexport type QueryUpdateResult = {\n /**\n * number of affected records.\n */\n changes?: number;\n /**\n * the IDs the statement reported, in payload order, `undefined` where it reported none for that\n * row - a MongoDB upsert names only the documents it inserted. Exact on `'returning'` dialects;\n * inferred from the driver header on the others (see {@link InsertIdSource}), and absent\n * altogether when the header reports nothing.\n */\n ids?: (PrimaryKey | undefined)[];\n /**\n * first inserted ID.\n */\n firstId?: PrimaryKey;\n /**\n * whether the record was created (`true`) or updated (`false`).\n * `undefined` when the dialect cannot determine this (e.g. SQLite).\n */\n created?: boolean;\n};\n",
7
7
  "import type { QueryContext, RelationAggregateSpec, SqlQueryDialect } from './dialect.js';\nimport type { Type } from './utility.js';\n\n/** What a `raw` callback receives. See {@link QueryRawFn}. */\nexport type QueryRawRenderOptions = {\n /** The dialect rendering the SQL. */\n dialect: SqlQueryDialect;\n /** The alias of the table in scope, unescaped; empty where there is none. */\n prefix: string;\n /** {@link prefix} escaped, with its trailing dot. */\n escapedPrefix: string;\n /** The query context the SQL is written into. */\n ctx: QueryContext;\n /**\n * The entity being rendered, which a ref read off a definition's map resolves its column against: a\n * computed field's own, or the one whose schema is built. Absent where a statement renders SQL.\n */\n entity?: Type<unknown>;\n};\n\n/** {@link QueryRawRenderOptions} as the callers along the way fill them in, every one still optional. */\nexport type QueryRawFnOptions = Partial<QueryRawRenderOptions>;\n\n/**\n * A `raw` callback: write into `ctx`, or return a string or number to have it appended. Anything else\n * it returns is ignored, which is why the return type is `unknown` rather than `void | Scalar` - the\n * latter rejected `({ ctx }) => ctx.append(...)`, the form every computed field is written in, because\n * TypeScript's \"returning a value where void is expected\" allowance does not apply to a union.\n */\nexport type QueryRawFn = (opts: QueryRawRenderOptions) => unknown;\n\nexport const RAW_VALUE: unique symbol = Symbol('rawValue');\nexport const RAW_ALIAS: unique symbol = Symbol('rawAlias');\n\nexport class QueryRaw {\n readonly [RAW_VALUE]: QueryRawFn;\n readonly [RAW_ALIAS]?: string;\n\n constructor(value: QueryRawFn, alias?: string) {\n this[RAW_VALUE] = value;\n this[RAW_ALIAS] = alias;\n }\n\n /** The same expression under an alias, for a `$select` projection. */\n as(alias: string): QueryRaw {\n return new QueryRaw(this[RAW_VALUE], alias);\n }\n\n /** Writes the expression into `opts.ctx`. The alias is the projection's to write, after the term. */\n render(opts: QueryRawRenderOptions): void {\n const emitted = this[RAW_VALUE](opts);\n if (typeof emitted === 'string' || (typeof emitted === 'number' && !Number.isNaN(emitted))) {\n opts.ctx.append(String(emitted));\n }\n }\n}\n\n/**\n * A field of an entity as SQL, read off `refs(Entity)` or a definition's refs: interpolated into `raw`, it\n * renders as the field's column. Its `key` is how an index tells a column from an expression.\n */\nexport class ColumnRef<K extends string = string> extends QueryRaw {\n constructor(\n readonly key: K,\n value: QueryRawFn,\n ) {\n super(value);\n }\n}\n\n/**\n * A relation aggregate as SQL, read off a `computed` field's refs: `(user) => user.resources.count()`.\n * It renders as the correlated subquery a `$count` reads, so a field holding one is read, filtered and\n * sorted like any other.\n *\n * `V` is the value it reads and `Storable` whether a trigger could keep it, both carried in phantom\n * fields so the aggregate a field declares decides the property's type and refuses `stored: true` on\n * one no delta can maintain.\n */\nexport class RelationAggregate<V = unknown, Storable extends boolean = boolean> extends QueryRaw {\n declare private readonly __value: V;\n declare private readonly __storable: Storable;\n\n constructor(\n /** What it reads, kept beside the SQL so a read decodes the value the way the target's field does. */\n readonly spec: RelationAggregateSpec,\n value: QueryRawFn,\n ) {\n super(value);\n }\n}\n",
8
8
  "import type { EntityMeta } from '../type/index.js';\n\nexport function throwPendingTransaction(): never {\n throw TypeError('pending transaction');\n}\n\nexport function throwNoPendingTransaction(): never {\n throw TypeError('not a pending transaction');\n}\n\nexport function clone<T>(value: T): T {\n if (typeof value !== 'object' || value === null) {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map((it) => clone(it)) as T;\n }\n return { ...value };\n}\n\n/** Whether `obj` has at least one enumerable key. Narrows away `undefined`/`null` for callers. */\nexport function hasKeys<T>(obj: T): obj is NonNullable<T> {\n if (typeof obj !== 'object' || obj === null) return false;\n for (const _ in obj) return true;\n return false;\n}\n\n/**\n * Whether any enumerable key of `obj` satisfies `pred`, short-circuiting on the first match\n * without materializing a key array (unlike `Object.keys(obj).some(pred)`).\n */\nexport function someKey<T extends object>(obj: T, pred: (key: keyof T & string) => boolean): boolean {\n for (const key in obj) {\n if (pred(key)) return true;\n }\n return false;\n}\n\n/** Whether `key` names an operator (`$eq`, `$push`...) rather than a field. */\nexport function isOperatorKey(key: string): boolean {\n return key.startsWith('$');\n}\n\n/** Whether `value` is a non-empty object with an operator key (`$eq`, `$push`...): the one test every dialect classifies with. */\nexport function isOperatorObject(value: unknown): value is Record<string, unknown> {\n return isRecord(value) && someKey(value, isOperatorKey);\n}\n\n/** Whether `value` is an object that is not an array, whose keys can be read. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nexport function getKeys<T extends object>(obj: T | null | undefined): (keyof T & string)[] {\n return obj ? (Object.keys(obj) as (keyof T & string)[]) : [];\n}\n\n/** The entries of `record` holding a value: a key declared but left `undefined` is no entry at all. */\nexport function definedEntries<K extends string, V>(record: Partial<Record<K, V>>): [K, V][] {\n return (Object.entries(record) as [K, V | undefined][]).filter((entry): entry is [K, V] => entry[1] !== undefined);\n}\n\n/**\n * The entity's own name, declared or its class's. `meta.name` holds only what the author wrote, so\n * the fallback is what an entity that named no table is called - which is why the sites spelling this\n * out reached for three different fallbacks, `?? ''` among them, and named nothing at all.\n */\nexport function entityName<E>(meta: EntityMeta<E>): string {\n return meta.name ?? meta.entity.name;\n}\n\n/**\n * Whether `value` addresses a row by itself rather than naming columns: every primitive, and the\n * object ids a driver deals in (`ObjectId`, `Date`, bytes). Only a plain object names columns, which\n * is what a `$where` map and a composite key's id object both are; an array is a list of either.\n */\nexport function isScalarId(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) {\n return true;\n }\n if (Array.isArray(value)) {\n return false;\n }\n // `null` as well as `Object.prototype`: an object with no prototype is what a query-string parser\n // hands back (`qs`, express's `req.params`), and reading one as a bare id would name one column\n // with a map of several.\n const proto = Object.getPrototypeOf(value);\n return proto !== Object.prototype && proto !== null;\n}\n\n/** Whether `value` is a plain object naming columns, the one shape a `$where` takes. */\nexport function isWhereMap(value: unknown): value is Record<string, unknown> {\n return !Array.isArray(value) && !isScalarId(value);\n}\n",
9
9
  "import type { QueryOptions, WireQuery } from '../type/index.js';\n// the clause lists themselves, not the barrel: this module is in the browser bundle's graph\nimport {\n QUERY_BOOLEAN_CLAUSES,\n QUERY_NUMBER_CLAUSES,\n QUERY_OBJECT_CLAUSES,\n QUERY_ROOT_NUMBER_CLAUSES,\n QUERY_ROOT_OBJECT_CLAUSES,\n} from '../type/query.js';\n// the brand alone, not the class: importing `QueryRaw` for an `instanceof` kept it, and `ColumnRef`\n// with it, in the browser bundle, which is on a size budget\nimport { RAW_VALUE } from '../type/queryRaw.js';\n// the specific util module, not the barrel, so the browser bundle does not pull in entity metadata\nimport { getKeys, isWhereMap } from '../util/object.util.js';\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>([\n ...QUERY_OBJECT_CLAUSES,\n ...QUERY_ROOT_OBJECT_CLAUSES,\n ...QUERY_NUMBER_CLAUSES,\n ...QUERY_ROOT_NUMBER_CLAUSES,\n ...QUERY_BOOLEAN_CLAUSES,\n 'hardDelete',\n 'count',\n] satisfies (keyof WireQuery<unknown> | keyof Pick<QueryOptions, 'hardDelete'> | 'count')[]);\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 WireQuery<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> = {}): WireQuery<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 [...QUERY_OBJECT_CLAUSES, ...QUERY_ROOT_OBJECT_CLAUSES]) {\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 if (!isWhereMap(query['$where'])) {\n throw Object.assign(new TypeError(\"'$where' must be a JSON object\"), { status: 400 });\n }\n\n // A query string carries every value as text, so what decodes a clause is the shape its group\n // declares. `'false'` is the reason the boolean pass exists rather than the raw value being taken:\n // it is a non-empty string, so a `$distinct=false` would otherwise read as asking for one.\n for (const key of [...QUERY_NUMBER_CLAUSES, ...QUERY_ROOT_NUMBER_CLAUSES]) {\n if (query[key] !== undefined) {\n query[key] = Number(query[key]);\n }\n }\n for (const key of QUERY_BOOLEAN_CLAUSES) {\n if (query[key] !== undefined) {\n query[key] = query[key] === true || query[key] === 'true';\n }\n }\n\n return query as WireQuery<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 ? wireJson(value) : String(value));\n }\n const qs = params.toString();\n return qs ? `?${qs}` : '';\n}\n\n/**\n * What leaves the browser, as JSON, refusing what JSON keeps nothing of rather than letting the server\n * build a statement around the remains. A `raw` fragment renders SQL against a dialect the client does not\n * have and arrives as `{}`; binary arrives as an object keyed by index. A `Date` is not among them - it\n * serializes to ISO 8601, which is what a date column reads. This is what a cast, or a JavaScript caller,\n * hits where the client's types already refuse a fragment.\n */\nexport function wireJson(value: unknown): string {\n return JSON.stringify(value, (_key: string, held: unknown) => {\n if (typeof held !== 'object' || held === null) {\n return held;\n }\n if (RAW_VALUE in held) {\n throw new TypeError('raw SQL cannot travel over HTTP: what leaves the browser is JSON');\n }\n // A blob is a field value, so no type parameter reaches it: this is the only place it is caught.\n if (held instanceof ArrayBuffer || ArrayBuffer.isView(held)) {\n throw new TypeError('binary cannot travel over HTTP: what leaves the browser is JSON');\n }\n return held;\n });\n}\n",
@@ -14,6 +14,6 @@
14
14
  "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"
15
15
  ],
16
16
  "mappings": "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",
17
- "debugId": "A77F643B44C4A82864756E2164756E21",
17
+ "debugId": "F1CD14D3A7DD82FE64756E2164756E21",
18
18
  "names": []
19
19
  }
@@ -13,6 +13,9 @@ export interface DialectOptions {
13
13
  * each pool's constructor.
14
14
  */
15
15
  export declare function dialectOptionsFrom(extra: ExtraOptions | undefined): DialectOptions;
16
+ /**
17
+ * Base abstract class for all database dialects (SQL and NoSQL).
18
+ */
16
19
  export declare abstract class AbstractDialect {
17
20
  protected readonly options: DialectOptions;
18
21
  abstract readonly dialectName: DialectName;
@@ -11,11 +11,11 @@ import { qualifyName } from '../util/sql.util.js';
11
11
  export function dialectOptionsFrom(extra) {
12
12
  return { namingStrategy: extra?.namingStrategy, schema: extra?.schema };
13
13
  }
14
+ /** A name a score can be projected under: what every engine takes as a key, with nothing to escape. */
15
+ const PLAIN_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
14
16
  /**
15
17
  * Base abstract class for all database dialects (SQL and NoSQL).
16
18
  */
17
- /** A name a score can be projected under: what every engine takes as a key, with nothing to escape. */
18
- const PLAIN_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
19
19
  export class AbstractDialect {
20
20
  options;
21
21
  namingStrategy;
@@ -1067,7 +1067,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1067
1067
  ? this.aggregateOperand(ctx, join.entity, key, join.alias)
1068
1068
  : this.aggregateOperand(ctx, entity, key, prefix);
1069
1069
  }
1070
- const arg = entry.fieldRef === '*' ? undefined : this.aggregateOperand(ctx, entity, entry.fieldRef, prefix);
1070
+ const arg = entry.field === undefined ? undefined : this.aggregateOperand(ctx, entity, entry.field, prefix);
1071
1071
  const { where } = entry;
1072
1072
  const condition = where &&
1073
1073
  this.buildFragment(ctx, (fragment) => this.renderWhere(fragment, entity, where, { clause: false, prefix }));
@@ -1274,7 +1274,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1274
1274
  this.formatJsonUpdate(ctx, escapedCol, value, field);
1275
1275
  }
1276
1276
  else if (isFieldUpdateOp(value)) {
1277
- const [op, operand] = fieldUpdateOf(value);
1277
+ const [op, operand] = fieldUpdateOf(key, value);
1278
1278
  ctx.append(`${escapedCol} = COALESCE(${escapedCol}, 0) ${SQL_ARITHMETIC[op]} `);
1279
1279
  ctx.addValue(operand);
1280
1280
  }
@@ -1638,7 +1638,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1638
1638
  const relatedMeta = getMeta(relatedEntity);
1639
1639
  const parent = opts.prefix ?? this.resolveTableAlias(meta);
1640
1640
  // Resolved before any SQL is emitted: it also decides whether the junction form reaches the target.
1641
- const targetWhere = this.scopedWhere(relatedMeta, read.query?.$where ?? {});
1641
+ const targetWhere = this.scopedWhere(relatedMeta, read.where ?? {});
1642
1642
  if (rel.through) {
1643
1643
  // The rows here are the junction's own, so a column of the far side is read as a page instead.
1644
1644
  if (read.field) {
@@ -1685,8 +1685,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1685
1685
  const meta = getMeta(entity);
1686
1686
  const rel = relationOf(meta, aggregate.relation);
1687
1687
  const parent = prefix || this.resolveTableAlias(meta);
1688
- const { $limit, $skip } = aggregate.query ?? {};
1689
- if ($limit === undefined && $skip === undefined) {
1688
+ if (aggregate.page?.$limit === undefined && aggregate.page?.$skip === undefined) {
1690
1689
  this.appendRelationSubquery(ctx, meta, aggregate.relation, rel, { prefix: parent }, aggregate);
1691
1690
  return;
1692
1691
  }
@@ -1707,7 +1706,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1707
1706
  const entity = rel.entity();
1708
1707
  const alias = ctx.claimAlias(aggregate.relation, parent);
1709
1708
  const correlation = raw(({ ctx: pageCtx }) => this.appendCorrelation(pageCtx, meta, rel, parent, alias));
1710
- const { $where, ...page } = aggregate.query ?? {};
1709
+ const { where: $where, page } = aggregate;
1711
1710
  // `1` where nothing is aggregated: a tally counts the rows the page holds, whatever they carry.
1712
1711
  const read = aggregate.field ? refs(entity)[aggregate.field] : raw `1`;
1713
1712
  const query = {
@@ -1752,7 +1751,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1752
1751
  /** Each relation a read's `$count` tallies, as a correlated count of its select list. */
1753
1752
  selectRelationCounts(ctx, meta, count, parent) {
1754
1753
  return countedRelations(meta, count).map(({ relKey, relation, where }) => {
1755
- const sql = this.buildFragment(ctx, (fragmentCtx) => this.appendRelationSubquery(fragmentCtx, meta, relKey, relation, { prefix: parent }, { op: '$count', query: { $where: where } }));
1754
+ const sql = this.buildFragment(ctx, (fragmentCtx) => this.appendRelationSubquery(fragmentCtx, meta, relKey, relation, { prefix: parent }, { op: '$count', where }));
1756
1755
  return { sql, key: `${COUNT_RESULT_KEY}.${relKey}` };
1757
1756
  });
1758
1757
  }
@@ -1851,7 +1850,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1851
1850
  /** Filter by relation: a parent matches when {@link appendRelationSubquery} finds one target row. */
1852
1851
  compareRelation(ctx, entity, relKey, val, rel, opts) {
1853
1852
  ctx.append('EXISTS ');
1854
- this.appendRelationSubquery(ctx, getMeta(entity), relKey, rel, opts, { op: 'exists', query: { $where: val } });
1853
+ this.appendRelationSubquery(ctx, getMeta(entity), relKey, rel, opts, { op: 'exists', where: val });
1855
1854
  }
1856
1855
  /** Filter by relation size: the same subquery, counting instead of testing for existence. */
1857
1856
  compareRelationSize(ctx, entity, relKey, sizeVal, rel, opts) {
@@ -21,7 +21,7 @@ function vectorDecoder(cast) {
21
21
  : (parseVectorLiteral(text, cast) ?? value));
22
22
  }
23
23
  const denseVector = vectorDecoder('vector');
24
- /** A vector bound as bytes: packed float32s as a driver returns them, else hex or text, as JSON or an older row carries it. */
24
+ /** A vector bound as bytes: packed float32s as a driver returns them, else the hex that crossed JSON. */
25
25
  const float32Decoder = (value) => {
26
26
  if (value instanceof ArrayBuffer) {
27
27
  return decodeFloat32s(new Uint8Array(value));
@@ -60,9 +60,9 @@ export function groupPathField(joins, path) {
60
60
  */
61
61
  export function aggregateColumnField(meta, joins, entry) {
62
62
  if (entry.kind === 'fn') {
63
- return entry.op === '$count' || entry.op === '$avg'
63
+ return entry.op === '$count' || entry.op === '$avg' || entry.field === undefined
64
64
  ? undefined
65
- : { field: meta.fields[entry.fieldRef] };
65
+ : { field: meta.fields[entry.field] };
66
66
  }
67
67
  const { key, join } = groupPathField(joins, entry.path);
68
68
  return join ? { field: join.meta.fields[key], join } : { field: meta.fields[key] };
@@ -1,5 +1,6 @@
1
1
  import { getMeta } from '../../entity/index.js';
2
- import { MongoDialect, textLanguage } from '../../mongo/mongoDialect.js';
2
+ import { MongoDialect } from '../../mongo/mongoDialect.js';
3
+ import { textLanguage } from '../../mongo/textLanguage.js';
3
4
  import { QueryRaw, } from '../../type/index.js';
4
5
  import { indexDistance, unsupportedVectorMetric } from '../../type/vector.js';
5
6
  import { declaredIndexes, declaredIndexName, renderIndexColumn } from '../../util/ddlExpression.util.js';
@@ -1,4 +1,4 @@
1
- import { textConfigOf } from '../../mongo/mongoDialect.js';
1
+ import { textConfigOf } from '../../mongo/textLanguage.js';
2
2
  import { createTableNode, SchemaAST } from '../../schema/schemaAST.js';
3
3
  import { isMongoQuerier, } from '../../type/index.js';
4
4
  /** What a server without Atlas Search answers a search index command with. */
@@ -2,3 +2,4 @@ export { MongoSchemaGenerator } from '../migrate/generator/mongoSchemaGenerator.
2
2
  export * from './mongoDialect.js';
3
3
  export * from './mongodbQuerier.js';
4
4
  export * from './mongodbQuerierPool.js';
5
+ export * from './textLanguage.js';
@@ -2,3 +2,4 @@ export { MongoSchemaGenerator } from '../migrate/generator/mongoSchemaGenerator.
2
2
  export * from './mongoDialect.js';
3
3
  export * from './mongodbQuerier.js';
4
4
  export * from './mongodbQuerierPool.js';
5
+ export * from './textLanguage.js';
@@ -19,13 +19,6 @@ type RelationLookups = {
19
19
  readonly stages: MongoAggregationPipelineEntry<Document>[];
20
20
  readonly temps: string[];
21
21
  };
22
- /**
23
- * A text-search config as MongoDB names the language: the same word for each language both know, and
24
- * `'none'` for the no-stemming parser Postgres calls `'simple'`. {@link textConfigOf} reads one back.
25
- */
26
- export declare function textLanguage(config: string): string;
27
- /** A MongoDB language as the text-search config it is, the inverse of {@link textLanguage}. */
28
- export declare function textConfigOf(language: string): string;
29
22
  /** Default {@link DialectFeatures} for MongoDB. */
30
23
  export declare const mongoDialectFeatures: DialectFeatures;
31
24
  export declare class MongoDialect extends AbstractDialect {
@@ -7,19 +7,9 @@ import { COUNT_RESULT_KEY } from '../type/query.js';
7
7
  import { QueryRaw } from '../type/queryRaw.js';
8
8
  import { aggregateOf, asSelectMap, assertAggregateColumns, assertNonNegativeInteger, columnFamily, countedRelations, entityName, fieldUpdateOf, fillOnFields, filterFieldKeys, findVectorIndex, findVectorSort, getKeys, getRelationRequestSummary, hasKeys, isFieldUpdateOp, isJsonObject, isJsonUpdateOp, isOperatorMap, isOperatorObject, isRecord, isVectorSearch, normalizeScalarFieldSelection, parentJoins, parseGroupMap, parseRelationAtKey, parseRelationSize, parseSortByCount, rankedTextSearch, someKey, targetKeyColumns, textSortOf, } from '../util/index.js';
9
9
  import { decodeBigIntsExcept } from '../util/wideNumber.js';
10
+ import { textLanguage } from './textLanguage.js';
10
11
  /** A scalar field's operator as the aggregation operator computing it. */
11
12
  const MONGO_ARITHMETIC = { $inc: '$add', $mul: '$multiply' };
12
- /**
13
- * A text-search config as MongoDB names the language: the same word for each language both know, and
14
- * `'none'` for the no-stemming parser Postgres calls `'simple'`. {@link textConfigOf} reads one back.
15
- */
16
- export function textLanguage(config) {
17
- return config === 'simple' ? 'none' : config;
18
- }
19
- /** A MongoDB language as the text-search config it is, the inverse of {@link textLanguage}. */
20
- export function textConfigOf(language) {
21
- return language === 'none' ? 'simple' : language;
22
- }
23
13
  /** Default {@link DialectFeatures} for MongoDB. */
24
14
  export const mongoDialectFeatures = {
25
15
  ifNotExists: false,
@@ -578,10 +568,10 @@ export class MongoDialect extends AbstractDialect {
578
568
  */
579
569
  aggregateStages(meta, spec, temp, field) {
580
570
  const relOpts = relationOf(meta, spec.relation);
581
- const query = spec.query ?? {};
571
+ const page = spec.page ?? {};
582
572
  const tail = [
583
- ...(query.$sort ? [{ $sort: this.sort(relOpts.entity(), query) }] : []),
584
- ...this.pagerStages(query),
573
+ ...(page.$sort ? [{ $sort: this.sort(relOpts.entity(), page) }] : []),
574
+ ...this.pagerStages(page),
585
575
  spec.field
586
576
  ? {
587
577
  $group: {
@@ -592,7 +582,7 @@ export class MongoDialect extends AbstractDialect {
592
582
  : { $count: AGGREGATE_VALUE_ALIAS },
593
583
  ];
594
584
  return [
595
- this.relationLookup(meta, relOpts, query.$where ?? {}, temp, tail),
585
+ this.relationLookup(meta, relOpts, spec.where ?? {}, temp, tail),
596
586
  { $addFields: { [field]: this.tally(temp, spec.op) } },
597
587
  ];
598
588
  }
@@ -619,7 +609,7 @@ export class MongoDialect extends AbstractDialect {
619
609
  for (const { relKey, where } of countedRelations(meta, q.$count)) {
620
610
  const temp = `${REL_TEMP_PREFIX}count_${relKey}`;
621
611
  temps.push(temp);
622
- const spec = { relation: relKey, op: '$count', query: { $where: where } };
612
+ const spec = { relation: relKey, op: '$count', where };
623
613
  stages.push(...this.aggregateStages(meta, spec, temp, `${COUNT_RESULT_KEY}.${relKey}`));
624
614
  }
625
615
  return temps.length ? [...stages, { $unset: temps }] : stages;
@@ -970,7 +960,7 @@ export class MongoDialect extends AbstractDialect {
970
960
  const unset = new Set();
971
961
  for (const [key, value] of Object.entries(persistable)) {
972
962
  if (isFieldUpdateOp(value)) {
973
- const [op, operand] = fieldUpdateOf(value);
963
+ const [op, operand] = fieldUpdateOf(key, value);
974
964
  arithmetic[key] = { [MONGO_ARITHMETIC[op]]: [{ $ifNull: [`$${key}`, 0] }, { $literal: operand }] };
975
965
  continue;
976
966
  }
@@ -1129,19 +1119,27 @@ export class MongoDialect extends AbstractDialect {
1129
1119
  columns[entry.alias] = `$_id.${entry.alias}`;
1130
1120
  continue;
1131
1121
  }
1132
- named.push(entry.fieldRef);
1133
- const ref = `$${this.columnOf(meta, entry.fieldRef)}`;
1122
+ const { field } = entry;
1123
+ if (field !== undefined) {
1124
+ named.push(field);
1125
+ }
1134
1126
  const test = entry.where && this.whereExpression(meta, entry.where, named);
1135
1127
  // What the accumulator reads from a row its own `$where` passes, and from one it does not.
1136
1128
  const read = (passed, failed) => (test ? { $cond: [test, passed, failed] } : passed);
1137
1129
  columns[entry.alias] = 1;
1130
+ if (field === undefined) {
1131
+ // COUNT(*): every row, as SQL counts one.
1132
+ accumulators[entry.alias] = { $sum: read(1, 0) };
1133
+ continue;
1134
+ }
1135
+ const ref = `$${this.columnOf(meta, field)}`;
1138
1136
  if (entry.distinct) {
1139
1137
  accumulators[entry.alias] = { $addToSet: read(ref, '$$REMOVE') };
1140
1138
  columns[entry.alias] = { $size: `$${entry.alias}` };
1141
1139
  }
1142
1140
  else if (entry.op === '$count') {
1143
- // COUNT(*) counts every row; COUNT(field) counts non-null values, matching SQL.
1144
- accumulators[entry.alias] = { $sum: entry.fieldRef === '*' ? read(1, 0) : read(MongoDialect.countOf(ref), 0) };
1141
+ // COUNT(field) counts the non-null values, as SQL does.
1142
+ accumulators[entry.alias] = { $sum: read(MongoDialect.countOf(ref), 0) };
1145
1143
  }
1146
1144
  else {
1147
1145
  // `$sum`, `$avg`, `$min` and `$max` are MongoDB accumulators of the same name, and skip a null.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A text-search config as MongoDB names the language: the same word for each language both know, and
3
+ * `'none'` for the no-stemming parser Postgres calls `'simple'`. {@link textConfigOf} reads one back.
4
+ */
5
+ export declare function textLanguage(config: string): string;
6
+ /** A MongoDB language as the text-search config it is, the inverse of {@link textLanguage}. */
7
+ export declare function textConfigOf(language: string): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * A text-search config as MongoDB names the language: the same word for each language both know, and
3
+ * `'none'` for the no-stemming parser Postgres calls `'simple'`. {@link textConfigOf} reads one back.
4
+ */
5
+ export function textLanguage(config) {
6
+ return config === 'simple' ? 'none' : config;
7
+ }
8
+ /** A MongoDB language as the text-search config it is, the inverse of {@link textLanguage}. */
9
+ export function textConfigOf(language) {
10
+ return language === 'none' ? 'simple' : language;
11
+ }
@@ -333,13 +333,8 @@ export function canonicalToTypeScript(type) {
333
333
  * the engine settles an unstated bound. Migrations and drift both compare through it.
334
334
  */
335
335
  export function engineType(dialect) {
336
- return (type) => {
337
- const stored = sqlToCanonical(canonicalToSql(type, dialect));
338
- // SQLite keeps a vector in any column, so one created as `TEXT` before vectors were blobs stays as it is.
339
- return dialect.dialectName === 'sqlite' && isVectorCategory(stored.category) ? SQLITE_TEXT : stored;
340
- };
336
+ return (type) => sqlToCanonical(canonicalToSql(type, dialect));
341
337
  }
342
- const SQLITE_TEXT = { category: 'string', size: 'small' };
343
338
  /**
344
339
  * Convert UQL FieldOptions to a canonical type.
345
340
  */
@@ -155,7 +155,8 @@ export class SqliteDialect extends AbstractSqlDialect {
155
155
  carriedFields = {
156
156
  numeric: (expr, field) => (isIntegerColumn(field) ? `CAST(${expr} AS TEXT)` : expr),
157
157
  blob: (expr) => this.bytesAsText(expr),
158
- vector: (expr) => `CASE WHEN typeof(${expr}) = 'blob' THEN ${this.bytesAsText(expr)} ELSE ${expr} END`,
158
+ // D1 keeps a vector as its text; every other engine here, as float32 bytes.
159
+ vector: (expr) => (this.features.vectorBytes ? this.bytesAsText(expr) : expr),
159
160
  };
160
161
  bytesAsText(expr) {
161
162
  return `${this.escape(BYTES_PREFIX)} || hex(${expr})`;
@@ -1,6 +1,7 @@
1
1
  import type { EntityMeta, UpdatePayload } from './entity.js';
2
2
  import type { Query, QueryConflictPaths, QueryOptions, QueryPage, QuerySearch, RelationQuery } from './query.js';
3
3
  import type { QueryAggMap, QueryAggregate, QueryAggregateOp, QueryGroupMap } from './queryAggregate.js';
4
+ import type { QueryWhere } from './queryWhere.js';
4
5
  import type { Type } from './utility.js';
5
6
  /**
6
7
  * comparison options.
@@ -236,6 +237,16 @@ export interface SqlQueryDialect {
236
237
  * a row change turns into a delta.
237
238
  */
238
239
  export type RelationAggregateOp = QueryAggregateOp;
240
+ /**
241
+ * One aggregate as every renderer reads it: its op, the field it reads - none counts the rows - and the
242
+ * rows it reads, where not all of them. A statement's `$select` entry and a relation aggregate are each
243
+ * this, beside what names the rows they aggregate over.
244
+ */
245
+ export type AggregateCall<E = object> = {
246
+ readonly op: QueryAggregateOp;
247
+ readonly field?: string;
248
+ readonly where?: QueryWhere<E>;
249
+ };
239
250
  /** What a relation aggregate reads: how many rows, or one of the target's columns. */
240
251
  export type RelationAggregateProjection = {
241
252
  readonly op: '$count';
@@ -244,14 +255,16 @@ export type RelationAggregateProjection = {
244
255
  readonly op: Exclude<RelationAggregateOp, '$count'>;
245
256
  readonly field: string;
246
257
  };
247
- /** A relation aggregate as a `computed` field holds it: what it reads, off which relation, filtered how. */
248
- export type RelationAggregateSpec = RelationAggregateProjection & {
258
+ /**
259
+ * A relation aggregate as a `computed` field holds it: an {@link AggregateCall} over the rows of the
260
+ * relation it names, capped where it declared a page.
261
+ */
262
+ export type RelationAggregateSpec = RelationAggregateProjection & Pick<AggregateCall, 'where'> & {
249
263
  readonly relation: string;
250
- /** Which of the related rows it reads, and the page it caps them to, as the field declared them. */
251
- readonly query?: RelationSubqueryQuery;
264
+ readonly page?: RelationAggregatePage;
252
265
  };
253
- /** The rows a relation subquery reads: which ones, in what order, and the page capping them. */
254
- export type RelationSubqueryQuery = Pick<RelationQuery, '$where' | '$sort' | '$limit' | '$skip'>;
266
+ /** The page of a relation's rows an aggregate reads, and the order picking them. */
267
+ export type RelationAggregatePage = Pick<RelationQuery, '$sort' | '$limit' | '$skip'>;
255
268
  /**
256
269
  * Supported SQL dialect identifiers.
257
270
  */
@@ -140,20 +140,20 @@ export type QuerySortByText = {
140
140
  };
141
141
  };
142
142
  /**
143
- * A row with the relevance a `$sort: { $text: { $project } }` names, which is not inferred:
144
- * `(await querier.findMany(Post, q)) as WithScore<Post, 'score'>[]`.
143
+ * A row with the value a `$sort` projects under the name its `$project` gives - a vector's distance, or a
144
+ * `$text` relevance - which is not inferred: `(await querier.findMany(Post, q)) as WithProjection<Post, 'score'>[]`.
145
145
  */
146
- export type WithScore<E, K extends string> = E & Record<K, number>;
146
+ export type WithProjection<E, K extends string> = E & Record<K, number>;
147
147
  /**
148
- * A sort by fields, JSON paths, a to-one relation's fields, a to-many's `$count`, or a vector distance or
149
- * `$text` relevance, which `Vector` confines to the queried entity. One mapped type over the key sets: an
150
- * intersection is checked once per member, which made this the costliest type to check.
148
+ * A sort by fields, JSON paths, a to-one relation's fields, a to-many's `$count`, or - where `Root` says it
149
+ * sorts the queried entity itself, not a relation's rows - a vector distance or a `$text` relevance. One
150
+ * mapped type over the key sets: an intersection is checked once per member, which made this the costliest.
151
151
  */
152
- export type QuerySortMap<E, Vector extends boolean = true, K extends keyof E = FieldKey<E> | RelationKey<E>> = {
153
- [P in K]?: P extends RelationKey<E> ? IsMany<E[P]> extends true ? QuerySortByCount : QuerySortMap<RelationTarget<E[P]>, false> : Vector extends true ? NonNullable<E[P]> extends readonly number[] ? QuerySortValue : QuerySortDirection : QuerySortDirection;
152
+ export type QuerySortMap<E, Root extends boolean = true, K extends keyof E = FieldKey<E> | RelationKey<E>> = {
153
+ [P in K]?: P extends RelationKey<E> ? IsMany<E[P]> extends true ? QuerySortByCount : QuerySortMap<RelationTarget<E[P]>, false> : Root extends true ? NonNullable<E[P]> extends readonly number[] ? QuerySortValue : QuerySortDirection : QuerySortDirection;
154
154
  } & ([JsonFieldPaths<E>] extends [never] ? unknown : {
155
155
  [P in JsonFieldPaths<E>]?: QuerySortDirection;
156
- }) & (Vector extends true ? QuerySortByText : unknown);
156
+ }) & (Root extends true ? QuerySortByText : unknown);
157
157
  /**
158
158
  * pager options.
159
159
  */
@@ -23,11 +23,6 @@ export interface QueryVectorSearch extends QueryVectorQuery {
23
23
  /** Project the computed distance as a named field in the result. */
24
24
  readonly $project?: string;
25
25
  }
26
- /**
27
- * A row with the distance a `$sort` `$project` names, which is not inferred:
28
- * `(await querier.findMany(Article, q)) as WithDistance<Article, 'similarity'>[]`.
29
- */
30
- export type WithDistance<E, K extends string = '_distance'> = E & Record<K, number>;
31
26
  /**
32
27
  * How a dialect spells a metric: an operator, or a function taking the metric by name where `metricArg`
33
28
  * says so, and `index`, how the dialect's vector index names it where it builds one. One map serves the
@@ -1,5 +1,5 @@
1
1
  import type { IndexType } from '../schema/types.js';
2
- import { type CascadeType, type EntityData, type EntityId, type EntityIndexMeta, type EntityMeta, type FieldKey, type FieldOptions, type FieldUpdateOp, type JsonUpdateOp, type OnFieldCallback, type Query, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySearch, type QuerySelect, type QuerySelectValue, type QuerySortDirection, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryVectorSearch, type QueryWhere, type RelationKey, type UpdatePayload } from '../type/index.js';
2
+ import { type AggregateCall, type CascadeType, type EntityData, type EntityId, type EntityIndexMeta, type EntityMeta, type FieldKey, type FieldOptions, type FieldUpdateOp, type JsonUpdateOp, type OnFieldCallback, type Query, type QueryAggMap, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySearch, type QuerySelect, type QuerySelectValue, type QuerySortDirection, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryVectorSearch, type QueryWhere, type RelationKey, type UpdatePayload } from '../type/index.js';
3
3
  export type CallbackKey = keyof Pick<FieldOptions, 'onInsert' | 'onUpdate'>;
4
4
  /** The keys of `payload` a write persists as columns. */
5
5
  export declare function filterFieldKeys<E>(meta: EntityMeta<E>, payload: EntityData<E> | UpdatePayload<E>, callbackKey: CallbackKey): FieldKey<E>[];
@@ -90,8 +90,11 @@ export declare function hasVectorNear(where: unknown): boolean;
90
90
  export declare function isJsonUpdateOp(value: unknown): value is JsonUpdateOp;
91
91
  /** Type guard: checks whether an update payload value is a scalar field's operator. */
92
92
  export declare function isFieldUpdateOp(value: unknown): value is FieldUpdateOp;
93
- /** The one operator a scalar field's update carries, and its operand. */
94
- export declare function fieldUpdateOf(value: FieldUpdateOp): [keyof FieldUpdateOp, number | bigint];
93
+ /**
94
+ * The one operator a scalar field's update carries, and its operand. Naming both throws rather than
95
+ * reading one: their order would change the result, and an untyped payload is how both arrive.
96
+ */
97
+ export declare function fieldUpdateOf(key: string, value: FieldUpdateOp): [keyof FieldUpdateOp, number | bigint];
95
98
  /**
96
99
  * The `$where` naming rows by key: a bare value names the one key column (refused on a composite), a
97
100
  * composite's key map is a `$where` already, and a list is an `IN` of bare values or an OR of maps.
@@ -118,16 +121,12 @@ export type ParsedGroupEntry<E = object> = {
118
121
  readonly alias: string;
119
122
  /** The field it reads, behind the to-one relations leading to it: `['transaction', 'orderId']`. */
120
123
  readonly path: readonly string[];
121
- } | {
124
+ } | (AggregateCall<E> & {
122
125
  readonly kind: 'fn';
123
126
  readonly alias: string;
124
- readonly op: QueryAggregateOp;
125
- readonly fieldRef: string;
126
127
  /** `true` for `$countDistinct`: `COUNT(DISTINCT field)`. */
127
128
  readonly distinct: boolean;
128
- /** The rows it reads, where not all of the statement's. */
129
- readonly where?: QueryWhere<E>;
130
- };
129
+ });
131
130
  /**
132
131
  * The `$size` of a relation condition, `{ comments: { $size: { $gte: 2 } } }`, or `undefined` where it
133
132
  * filters the target's fields; a mix of the two, `{ $size: 2, name: 'x' }`, is refused.
@@ -242,8 +242,14 @@ const FIELD_UPDATE_OPS = ['$inc', '$mul'];
242
242
  export function isFieldUpdateOp(value) {
243
243
  return isRecord(value) && someKey(value, (key) => FIELD_UPDATE_OPS.includes(key));
244
244
  }
245
- /** The one operator a scalar field's update carries, and its operand. */
246
- export function fieldUpdateOf(value) {
245
+ /**
246
+ * The one operator a scalar field's update carries, and its operand. Naming both throws rather than
247
+ * reading one: their order would change the result, and an untyped payload is how both arrive.
248
+ */
249
+ export function fieldUpdateOf(key, value) {
250
+ if (value.$inc !== undefined && value.$mul !== undefined) {
251
+ throw new TypeError(`'${key}' takes one of $inc and $mul`);
252
+ }
247
253
  return value.$inc === undefined ? ['$mul', value.$mul] : ['$inc', value.$inc];
248
254
  }
249
255
  /**
@@ -379,8 +385,11 @@ export function parseGroupMap(group, select) {
379
385
  }
380
386
  // `$countDistinct` normalizes to `$count` plus a `distinct` flag.
381
387
  const { op, distinct } = resolveAggregateOp(key);
382
- const fieldRef = aggregateFieldRef(alias, call[key]);
383
- entries.push({ kind: 'fn', alias, op, fieldRef, distinct, ...(hasKeys(where) ? { where } : {}) });
388
+ const field = aggregateField(alias, call[key]);
389
+ if (field === undefined && (op !== '$count' || distinct)) {
390
+ throw new TypeError(`aggregate '${alias}' takes '*' only as a $count`);
391
+ }
392
+ entries.push({ kind: 'fn', alias, op, distinct, ...(field && { field }), ...(hasKeys(where) ? { where } : {}) });
384
393
  }
385
394
  return entries;
386
395
  }
@@ -392,9 +401,12 @@ function groupRefPath(alias, ref) {
392
401
  }
393
402
  return ref[key] === true ? [key] : [key, ...groupRefPath(alias, ref[key])];
394
403
  }
395
- /** The column an aggregate reads: `'*'`, or the one field its `{ field: true }` names. */
396
- function aggregateFieldRef(alias, arg) {
397
- const [field, ...rest] = arg === '*' ? [arg] : namedKeys(arg);
404
+ /** The field an aggregate reads, the one its `{ field: true }` names; none for `'*'`, which counts the rows. */
405
+ function aggregateField(alias, arg) {
406
+ if (arg === '*') {
407
+ return undefined;
408
+ }
409
+ const [field, ...rest] = namedKeys(arg);
398
410
  if (field === undefined || rest.length) {
399
411
  throw new TypeError(`aggregate '${alias}' takes one field as { field: true }, or '*': got ${JSON.stringify(arg)}`);
400
412
  }
package/dist/util/raw.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { getMeta } from '../entity/metadata/definition.js';
2
2
  import { ColumnRef, QueryRaw, RelationAggregate, } from '../type/index.js';
3
3
  import { isInlinedExpression } from './field.util.js';
4
+ import { hasKeys } from './object.util.js';
4
5
  export function raw(value, ...rest) {
5
6
  if (!isTemplateStrings(value)) {
6
7
  return new QueryRaw(value);
@@ -49,15 +50,20 @@ export function entityWhere(where) {
49
50
  * a member decorator sees no class and so cannot know which the key is; the types keep them apart.
50
51
  */
51
52
  function memberRef(relation) {
52
- const over = (op) => (pick, q) => relationAggregate({ relation, op, field: pick(memberRefs()).key, ...(q && { query: q }) });
53
+ const over = (op) => (pick, q) => relationAggregate({ relation, op, field: pick(memberRefs()).key, ...rowsOf(q) });
53
54
  return Object.assign(columnRef(undefined, relation), {
54
- count: (q) => relationAggregate({ relation, op: '$count', ...(q && { query: q }) }),
55
+ count: (q) => relationAggregate({ relation, op: '$count', ...rowsOf(q) }),
55
56
  sum: over('$sum'),
56
57
  min: over('$min'),
57
58
  max: over('$max'),
58
59
  avg: over('$avg'),
59
60
  });
60
61
  }
62
+ /** The rows an aggregate's declared query reads: its `$where`, and the page the rest of it caps them to. */
63
+ function rowsOf(q) {
64
+ const { $where, ...page } = q ?? {};
65
+ return { ...($where && { where: $where }), ...(hasKeys(page) && { page }) };
66
+ }
61
67
  /**
62
68
  * A relation aggregate as SQL: the dialect writes the same correlated subquery a `$count` reads,
63
69
  * correlated to whichever alias the clause naming the field is rendering under.
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "homepage": "https://uql-orm.dev",
4
4
  "description": "The JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
5
5
  "license": "MIT",
6
- "version": "0.72.2",
6
+ "version": "0.73.1",
7
7
  "type": "module",
8
8
  "engines": {
9
9
  "node": ">=24"
@@ -103,6 +103,9 @@ const users = await pool.findMany(User, {
103
103
  - `$where` takes a value for equality or an operator map: `$eq`, `$ne`, `$lt`, `$lte`, `$gt`, `$gte`, `$in`,
104
104
  `$nin`, `$between`, `$like`, `$ilike`, `$regex`, `$startsWith`, `$endsWith`, `$includes`, `$isNull`,
105
105
  `$isNotNull`. `$and`, `$or`, `$not` and `$nor` combine clauses.
106
+ - `$text: { $value }` in `$where` searches text on every engine with full-text search, through the entity's
107
+ `@Index(..., { type: 'fulltext', config })`, whose columns may carry a `weight`. `$sort: { $text: 'desc' }` ranks by
108
+ relevance, and `{ $text: { $project: 'score' } }` also returns it, typed with `WithProjection<E, 'score'>`.
106
109
  - A result is narrowed to what the query selected and populated: reading an unselected field is a compile error.
107
110
  Name that shape with `QueryFindResult<User, 'id' | 'email'>` rather than widening the query.
108
111
  - `$populate` loads relations in the same statement. Nothing is lazy: a relation not populated is not there.