uql-orm 0.49.0 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { type HttpMethod } from '../../http/contract.js';
2
- import type { EntityData, EntityId, FieldKey, IdValue, QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, RelationKey, RequestCountedSuccessResponse, RequestSuccessResponse, Type, UpdatePayload } from '../../type/index.js';
2
+ import type { EntityData, EntityId, FieldKey, QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, RelationKey, RequestCountedSuccessResponse, RequestSuccessResponse, Type, UpdatePayload, WrittenId } from '../../type/index.js';
3
3
  import type { ClientQuerier, RequestFindOptions, RequestOptions } from '../type/index.js';
4
4
  export type HttpQuerierDefaults = {
5
5
  /**
@@ -34,12 +34,12 @@ export declare class HttpQuerier implements ClientQuerier {
34
34
  count?: number;
35
35
  data: boolean;
36
36
  }>;
37
- insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<IdValue<E> | undefined>>;
38
- insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions): Promise<RequestSuccessResponse<(IdValue<E> | undefined)[]>>;
37
+ insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<WrittenId<E> | undefined>>;
38
+ insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions): Promise<RequestSuccessResponse<(WrittenId<E> | undefined)[]>>;
39
39
  updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdatePayload<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<number>>;
40
40
  updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<number>>;
41
- saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<EntityId<E> | undefined>>;
42
- saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions): Promise<RequestSuccessResponse<(EntityId<E> | undefined)[]>>;
41
+ saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<WrittenId<E> | undefined>>;
42
+ saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions): Promise<RequestSuccessResponse<(WrittenId<E> | undefined)[]>>;
43
43
  deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts?: QueryOptions & RequestOptions): Promise<RequestSuccessResponse<number>>;
44
44
  deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions & RequestOptions): Promise<RequestSuccessResponse<number>>;
45
45
  getBasePath<E>(entity: Type<E>): string;
@@ -1,4 +1,4 @@
1
- import type { EntityData, EntityId, IdValue, QuerierResult, QueryOptions, SharedQuerier, Type } from '../../type/index.js';
1
+ import type { EntityData, QuerierResult, QueryOptions, SharedQuerier, Type, WrittenId } from '../../type/index.js';
2
2
  import type { RequestOptions } from './request.js';
3
3
  /**
4
4
  * Client-side querier: {@link SharedQuerier} on the `'client'` transport, so every result arrives in
@@ -7,8 +7,8 @@ import type { RequestOptions } from './request.js';
7
7
  * @see UniversalQuerier for the same operations with direct return types.
8
8
  */
9
9
  export interface ClientQuerier extends SharedQuerier<'client', RequestOptions, QueryOptions & RequestOptions> {
10
- insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions): QuerierResult<'client', IdValue<E> | undefined>;
11
- insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions): QuerierResult<'client', (IdValue<E> | undefined)[]>;
12
- saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions): QuerierResult<'client', EntityId<E> | undefined>;
13
- saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions): QuerierResult<'client', (EntityId<E> | undefined)[]>;
10
+ insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions): QuerierResult<'client', WrittenId<E> | undefined>;
11
+ insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions): QuerierResult<'client', (WrittenId<E> | undefined)[]>;
12
+ saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions): QuerierResult<'client', WrittenId<E> | undefined>;
13
+ saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions): QuerierResult<'client', (WrittenId<E> | undefined)[]>;
14
14
  }
@@ -1,4 +1,4 @@
1
- var d=[];function a(e){for(let t of d)t(e)}function q(e){d.push(e);let t=d.length-1;return()=>{d.splice(t,1)}}class g extends Error{status;constructor(e,t){super(e);this.status=t;this.name="RequestError"}}function l(e,t){return i(e,{method:"get"},t)}function E(e,t,n){let r=JSON.stringify(t);return i(e,{method:"post",body:r},n)}function f(e,t,n){let r=JSON.stringify(t);return i(e,{method:"patch",body:r},n)}function h(e,t,n){let r=JSON.stringify(t);return i(e,{method:"put",body:r},n)}function x(e,t){return i(e,{method:"delete"},t)}function m(e,t,n){let r=JSON.stringify(t);return i(e,{method:"QUERY",body:r},n)}function i(e,t,n){if(a({phase:"start",opts:n}),t.headers={accept:"application/json","content-type":"application/json",...n?.headers},n?.signal)t.signal=n.signal;return fetch(e,t).then((r)=>r.json().then((o)=>{if(r.status>=200&&r.status<300)return a({phase:"success",opts:n}),o;let P=o,c={message:P?.error?.message??r.statusText,code:P?.error?.code??r.status};throw a({phase:"error",error:c,opts:n}),new g(c.message,c.code)})).finally(()=>{a({phase:"complete",opts:n})})}function p(e){return e?Object.keys(e):[]}function S(e){if(typeof e!=="object"||e===null)return!0;if(Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t!==Object.prototype&&t!==null}function O(e){let t=e.charAt(0).toLowerCase();for(let n=1;n<e.length;++n)t+=e[n]===e[n].toUpperCase()?"-"+e[n].toLowerCase():e[n];return t}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:""}},j=p(s),X=new Map(j.filter((e)=>s[e].method==="GET"&&s[e].path!=="/:id").map((e)=>[s[e].path,e]));function K(e){return O(e.name)}var T=["$select","$populate","$exclude","$where","$sort"],C=["$count"],b=["$skip","$limit"],k=["$candidates"],w=["$distinct"];var W=new Set([...T,...C,...b,...k,...w,"hardDelete","count"]);function u(e){if(!e)return"";let t=new URLSearchParams;for(let r of p(e)){let o=e[r];if(o===void 0)continue;t.append(r,typeof o==="object"&&o!==null?JSON.stringify(o):String(o))}let n=t.toString();return n?`?${n}`:""}function R(e,t){if(!S(t))throw TypeError(`'${e.name}' was addressed by an id object, which the HTTP route cannot carry.`);return String(t)}class Q{basePath;defaults;constructor(e,t={}){this.basePath=e;this.defaults=t}async findOneById(e,t,n,r){let o=this.getBasePath(e),y=u(n);return l(`${o}/${R(e,t)}${y}`,this.buildOptions(r))}findOne(e,t,n){return this.read(`${this.getBasePath(e)}${s.findOne.path}`,t,n)}findMany(e,t,n){let r={...t};if(n?.count)r.count=!0;return this.read(this.getBasePath(e),r,n)}async findManyAndCount(e,t,n){let r=await this.findMany(e,t,{...n,count:!0});if(typeof r.count!=="number")throw TypeError("findManyAndCount response has an invalid count");return{...r,count:r.count}}count(e,t,n){return this.read(`${this.getBasePath(e)}${s.count.path}`,t,n)}async exists(e,t,n){let r=await this.count(e,{...t,$limit:1},n);return{...r,data:r.data>0}}insertOne(e,t,n){let r=this.getBasePath(e);return E(r,t,this.buildOptions(n))}insertMany(e,t,n){let r=this.getBasePath(e);return E(`${r}${s.insertMany.path}`,t,this.buildOptions(n))}async updateOneById(e,t,n,r){let o=this.getBasePath(e);return f(`${o}/${R(e,t)}`,n,this.buildOptions(r))}updateMany(e,t,n,r){let o=this.getBasePath(e),y=u(t);return f(`${o}${y}`,n,this.buildOptions(r))}saveOne(e,t,n){let r=this.getBasePath(e);return h(r,t,this.buildOptions(n))}saveMany(e,t,n){let r=this.getBasePath(e);return h(`${r}${s.saveMany.path}`,t,this.buildOptions(n))}async deleteOneById(e,t,n={}){let r=this.getBasePath(e),o=n.hardDelete?u({hardDelete:n.hardDelete}):"";return x(`${r}/${R(e,t)}${o}`,this.buildOptions(n))}deleteMany(e,t,n={}){let r=this.getBasePath(e),o=u(n.hardDelete?{...t,hardDelete:n.hardDelete}:t);return x(`${r}${o}`,this.buildOptions(n))}getBasePath(e){return`${this.basePath}/${(this.defaults.entityPath??K)(e)}`}read(e,t,n){if(this.defaults.readMethod==="QUERY")return m(e,t??{},this.buildOptions(n));return l(`${e}${u(t)}`,this.buildOptions(n))}buildOptions(e){if(!this.defaults.headers&&!e?.headers)return e;return{...e,headers:{...this.defaults.headers,...e?.headers}}}}var U={getQuerier:()=>new Q("/api")};function ne(e){U=e}function F(){return U}function re(){return F().getQuerier()}export{Q as HttpQuerier,g as RequestError,l as get,re as getQuerier,F as getQuerierPool,a as notify,q as on,f as patch,E as post,h as put,m as query,x as remove,ne as setQuerierPool};
1
+ var d=[];function a(e){for(let t of d)t(e)}function q(e){d.push(e);let t=d.length-1;return()=>{d.splice(t,1)}}class g extends Error{status;constructor(e,t){super(e);this.status=t;this.name="RequestError"}}function l(e,t){return i(e,{method:"get"},t)}function E(e,t,n){let r=JSON.stringify(t);return i(e,{method:"post",body:r},n)}function f(e,t,n){let r=JSON.stringify(t);return i(e,{method:"patch",body:r},n)}function h(e,t,n){let r=JSON.stringify(t);return i(e,{method:"put",body:r},n)}function x(e,t){return i(e,{method:"delete"},t)}function m(e,t,n){let r=JSON.stringify(t);return i(e,{method:"QUERY",body:r},n)}function i(e,t,n){if(a({phase:"start",opts:n}),t.headers={accept:"application/json","content-type":"application/json",...n?.headers},n?.signal)t.signal=n.signal;return fetch(e,t).then((r)=>r.json().then((o)=>{if(r.status>=200&&r.status<300)return a({phase:"success",opts:n}),o;let P=o,c={message:P?.error?.message??r.statusText,code:P?.error?.code??r.status};throw a({phase:"error",error:c,opts:n}),new g(c.message,c.code)})).finally(()=>{a({phase:"complete",opts:n})})}function p(e){return e?Object.keys(e):[]}function O(e){if(typeof e!=="object"||e===null)return!0;if(Array.isArray(e))return!1;let t=Object.getPrototypeOf(e);return t!==Object.prototype&&t!==null}function S(e){let t=e.charAt(0).toLowerCase();for(let n=1;n<e.length;++n)t+=e[n]===e[n].toUpperCase()?"-"+e[n].toLowerCase():e[n];return t}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:""}},j=p(s),A=new Map(j.filter((e)=>s[e].method==="GET"&&s[e].path!=="/:id").map((e)=>[s[e].path,e]));function K(e){return S(e.name)}var T=["$select","$populate","$exclude","$where","$sort"],C=["$count"],b=["$skip","$limit"],k=["$candidates"],U=["$distinct"];var Y=new Set([...T,...C,...b,...k,...U,"hardDelete","count"]);function u(e){if(!e)return"";let t=new URLSearchParams;for(let r of p(e)){let o=e[r];if(o===void 0)continue;t.append(r,typeof o==="object"&&o!==null?JSON.stringify(o):String(o))}let n=t.toString();return n?`?${n}`:""}function R(e,t){if(!O(t))throw TypeError(`'${e.name}' was addressed by an id object, which the HTTP route cannot carry.`);return String(t)}class Q{basePath;defaults;constructor(e,t={}){this.basePath=e;this.defaults=t}async findOneById(e,t,n,r){let o=this.getBasePath(e),y=u(n);return l(`${o}/${R(e,t)}${y}`,this.buildOptions(r))}findOne(e,t,n){return this.read(`${this.getBasePath(e)}${s.findOne.path}`,t,n)}findMany(e,t,n){let r={...t};if(n?.count)r.count=!0;return this.read(this.getBasePath(e),r,n)}async findManyAndCount(e,t,n){let r=await this.findMany(e,t,{...n,count:!0});if(typeof r.count!=="number")throw TypeError("findManyAndCount response has an invalid count");return{...r,count:r.count}}count(e,t,n){return this.read(`${this.getBasePath(e)}${s.count.path}`,t,n)}async exists(e,t,n){let r=await this.count(e,{...t,$limit:1},n);return{...r,data:r.data>0}}insertOne(e,t,n){let r=this.getBasePath(e);return E(r,t,this.buildOptions(n))}insertMany(e,t,n){let r=this.getBasePath(e);return E(`${r}${s.insertMany.path}`,t,this.buildOptions(n))}async updateOneById(e,t,n,r){let o=this.getBasePath(e);return f(`${o}/${R(e,t)}`,n,this.buildOptions(r))}updateMany(e,t,n,r){let o=this.getBasePath(e),y=u(t);return f(`${o}${y}`,n,this.buildOptions(r))}saveOne(e,t,n){let r=this.getBasePath(e);return h(r,t,this.buildOptions(n))}saveMany(e,t,n){let r=this.getBasePath(e);return h(`${r}${s.saveMany.path}`,t,this.buildOptions(n))}async deleteOneById(e,t,n={}){let r=this.getBasePath(e),o=n.hardDelete?u({hardDelete:n.hardDelete}):"";return x(`${r}/${R(e,t)}${o}`,this.buildOptions(n))}deleteMany(e,t,n={}){let r=this.getBasePath(e),o=u(n.hardDelete?{...t,hardDelete:n.hardDelete}:t);return x(`${r}${o}`,this.buildOptions(n))}getBasePath(e){return`${this.basePath}/${(this.defaults.entityPath??K)(e)}`}read(e,t,n){if(this.defaults.readMethod==="QUERY")return m(e,t??{},this.buildOptions(n));return l(`${e}${u(t)}`,this.buildOptions(n))}buildOptions(e){if(!this.defaults.headers&&!e?.headers)return e;return{...e,headers:{...this.defaults.headers,...e?.headers}}}}var w={getQuerier:()=>new Q("/api")};function ne(e){w=e}function F(){return w}function re(){return F().getQuerier()}export{Q as HttpQuerier,g as RequestError,l as get,re as getQuerier,F as getQuerierPool,a as notify,q as on,f as patch,E as post,h as put,m as query,x as remove,ne as setQuerierPool};
2
2
 
3
- //# debugId=565BC245D18F9E8464756E2164756E21
3
+ //# debugId=00C82941ED3E3C5464756E2164756E21
4
4
  //# sourceMappingURL=uql-browser.min.js.map
@@ -7,12 +7,12 @@
7
7
  "import type { EntityMeta, FieldKey, FieldOptions } 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 any enumerable value of `obj` satisfies `pred`, short-circuiting like {@link someKey}. */\nexport function someValue(obj: object, pred: (value: unknown) => boolean): boolean {\n return someKey(obj, (key) => pred((obj as Record<string, unknown>)[key]));\n}\n\nconst isOperatorKey = (key: string) => key.startsWith('$');\n\n/**\n * Whether `value` is a non-empty object whose keys are query/update operators (`$eq`, `$push`, ...).\n * The single source of this test: the SQL dialects, the MongoDB dialect and the `$elemMatch` walker\n * all classify operator objects with it, and they used to disagree about `{}`.\n */\nexport function isOperatorObject(value: unknown): value is Record<string, unknown> {\n return hasKeys(value) && !Array.isArray(value) && someKey(value, isOperatorKey);\n}\n\n/** Whether every key of the non-empty object `value` is an operator (no plain field names mixed in). */\nexport function isOperatorOnlyObject(value: unknown): value is Record<string, unknown> {\n return hasKeys(value) && !Array.isArray(value) && !someKey(value, (key) => !isOperatorKey(key));\n}\n\nexport function getKeys<T extends object>(obj: T): (keyof T & string)[] {\n return obj ? (Object.keys(obj) as (keyof T & string)[]) : [];\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\nexport function getFieldKeys<E>(fields: {\n [K in FieldKey<E>]?: FieldOptions;\n}): FieldKey<E>[] {\n return getKeys(fields).filter((field) => fields[field]!.eager ?? true);\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",
8
8
  "export function kebabCase(val: string): string {\n let resp = val.charAt(0).toLowerCase();\n for (let i = 1; i < val.length; ++i) {\n resp += val[i] === val[i].toUpperCase() ? '-' + val[i].toLowerCase() : val[i];\n }\n return resp;\n}\n\nexport function upperFirst(text: string): string {\n if (!text) return text;\n return text[0].toUpperCase() + text.slice(1);\n}\n\nexport function lowerFirst(text: string): string {\n if (!text) return text;\n return text[0].toLowerCase() + text.slice(1);\n}\n\nexport function snakeCase(val: string): string {\n if (val === null || val === undefined) return val as string;\n if (!val) return '';\n let resp = val.charAt(0).toLowerCase();\n for (let i = 1; i < val.length; ++i) {\n const char = val[i];\n const charLower = char.toLowerCase();\n if (char !== charLower && char === char.toUpperCase()) {\n resp += '_' + charLower;\n } else {\n resp += char;\n }\n }\n return resp;\n}\n\n/**\n * Convert a string to PascalCase (UpperCamelCase).\n * @example 'user_profile' -> 'UserProfile'\n * @example 'some-text' -> 'SomeText'\n */\nexport function pascalCase(str: string): string {\n if (!str) return '';\n return str\n .split(/[_\\s-]+/)\n .map((word) => {\n // Lower-casing the rest is only right for a word that carries no case of its own: it turns\n // `USER_ID` into `UserId`, but it also turns `tenantId` into `Tenantid`.\n const rest = word === word.toUpperCase() ? word.slice(1).toLowerCase() : word.slice(1);\n return word.charAt(0).toUpperCase() + rest;\n })\n .join('');\n}\n\n/**\n * Convert a string to camelCase.\n * @example 'user_profile' -> 'userProfile'\n * @example 'SomeText' -> 'someText'\n */\nexport function camelCase(str: string): string {\n const pascal = pascalCase(str);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\n/**\n * Simple singularize function for English words.\n * @example 'users' -> 'user'\n * @example 'categories' -> 'category'\n */\nexport function singularize(name: string): string {\n if (!name) return '';\n if (name.endsWith('ies')) {\n return name.slice(0, -3) + 'y';\n }\n if (name.endsWith('ses') || name.endsWith('xes') || name.endsWith('zes')) {\n return name.slice(0, -2);\n }\n if (name.endsWith('s') && !name.endsWith('ss')) {\n return name.slice(0, -1);\n }\n return name;\n}\n\n/**\n * Simple pluralize function for English words.\n * @example 'user' -> 'users'\n * @example 'category' -> 'categories'\n */\nexport function pluralize(name: string): string {\n if (!name) return '';\n if (name.endsWith('y') && name.length > 1 && !/[aeiou]/.test(name[name.length - 2])) {\n return name.slice(0, -1) + 'ies';\n }\n if (name.endsWith('s') || name.endsWith('x') || name.endsWith('z') || name.endsWith('ch') || name.endsWith('sh')) {\n return name + 'es';\n }\n return name + 's';\n}\n",
9
9
  "import type { Type, UniversalQuerier } from '../type/index.js';\n// the specific util modules, not the barrel, so the browser bundle does not pull in entity metadata\nimport { getKeys } from '../util/object.util.js';\nimport { kebabCase } from '../util/string.util.js';\n\ntype RouteShape = {\n readonly method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n readonly path: '' | `/${string}`;\n};\n\n/**\n * Single source of truth for the CRUD-over-HTTP surface, shared by server adapters and the browser client.\n * Keys are constrained to {@link UniversalQuerier} method names, so renaming a querier method\n * (or routing a non-existent one) is a compile error.\n */\nexport const CRUD_ROUTES = {\n findMany: { method: 'GET', path: '' },\n findOne: { method: 'GET', path: '/one' },\n count: { method: 'GET', path: '/count' },\n findOneById: { method: 'GET', path: '/:id' },\n insertOne: { method: 'POST', path: '' },\n insertMany: { method: 'POST', path: '/many' },\n saveOne: { method: 'PUT', path: '' },\n saveMany: { method: 'PUT', path: '/many' },\n updateMany: { method: 'PATCH', path: '' },\n updateOneById: { method: 'PATCH', path: '/:id' },\n deleteOneById: { method: 'DELETE', path: '/:id' },\n deleteMany: { method: 'DELETE', path: '' },\n} as const satisfies Partial<Record<keyof UniversalQuerier, RouteShape>>;\n\nexport type CrudOperation = keyof typeof CRUD_ROUTES;\n\nexport type CrudRoute = (typeof CRUD_ROUTES)[CrudOperation];\n\n/**\n * `QUERY` (RFC 10008) is an alternate transport for the read operations: same semantics as the\n * GET routes, but the JSON query travels in the request body instead of the query string,\n * avoiding URL-length limits for large queries.\n */\nexport type HttpMethod = CrudRoute['method'] | 'QUERY';\n\nconst CRUD_OPS = getKeys(CRUD_ROUTES);\n\n// derived from CRUD_ROUTES (the literal-path GET routes) so the sub-paths live in exactly one place\nconst QUERY_READ_OPS: ReadonlyMap<string, CrudOperation> = new Map(\n CRUD_OPS.filter((op) => CRUD_ROUTES[op].method === 'GET' && CRUD_ROUTES[op].path !== '/:id').map((op) => [\n CRUD_ROUTES[op].path,\n op,\n ]),\n);\n\n/**\n * URL segment for an entity, e.g. `entityPath(UserProfile) === 'user-profile'`.\n */\nexport function entityPath<E>(entity: Type<E>): string {\n return kebabCase(entity.name);\n}\n\nexport type RouteMatch = {\n readonly op: CrudOperation;\n /**\n * the resolved transport method - differs from the op's canonical route method for QUERY.\n */\n readonly method: HttpMethod;\n readonly id?: string;\n};\n\n/**\n * Resolve a (method, sub-path) pair to a CRUD operation. Literal sub-paths win over `:id`.\n */\nexport function matchRoute(method: string, subPath: string | undefined): RouteMatch | undefined {\n const raw = method.toUpperCase();\n const literal = subPath === undefined ? '' : `/${subPath}`;\n if (raw === 'QUERY') {\n const op = QUERY_READ_OPS.get(literal);\n return op ? { op, method: 'QUERY' } : undefined;\n }\n // HEAD reads like GET per HTTP semantics; the server runtime omits the response body\n const verb = raw === 'HEAD' ? 'GET' : raw;\n let idOp: CrudOperation | undefined;\n for (const op of CRUD_OPS) {\n const route = CRUD_ROUTES[op];\n if (route.method !== verb) {\n continue;\n }\n if (route.path === literal) {\n return { op, method: route.method };\n }\n if (route.path === '/:id') {\n idOp = op;\n }\n }\n return idOp && subPath !== undefined ? { op: idOp, method: CRUD_ROUTES[idOp].method, id: subPath } : undefined;\n}\n\nexport type RequestErrorResponse = {\n readonly error: {\n readonly message: string;\n readonly code: number;\n };\n};\n\n/**\n * Map a thrown error to the wire error envelope. Honors a numeric `status` on the error\n * (e.g. hooks throwing 403), defaults to 500; `code` mirrors the HTTP status.\n */\nexport function toErrorResponse(err: unknown): { status: number; body: RequestErrorResponse } {\n const status = err instanceof Error && 'status' in err && typeof err.status === 'number' ? err.status : 500;\n const message = err instanceof Error ? err.message : 'Internal Server Error';\n return { status, body: { error: { message, code: status } } };\n}\n",
10
- "import type { FieldKey, IdKey, JsonFieldPaths, RelationKey, RelationTarget } 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 * prefix the query with this.\n */\n prefix?: string;\n /**\n * automatically infer the prefix for the query.\n */\n autoPrefix?: boolean;\n};\n\nexport type QuerySelectOptions = {\n /**\n * prefix the query with this.\n */\n prefix?: string;\n /**\n * automatically add the prefix for the alias.\n */\n autoPrefixAlias?: boolean;\n};\n\n/**\n * Query field selection - `{ name: true }` whitelists specific fields. Fields only: a relation is a\n * sub-query rather than a projection flag, and a whitelist naming one could not say whether the\n * scalars come with it. Relations go in `$populate`.\n */\nexport type QuerySelect<E> = {\n [K in FieldKey<E>]?: BooleanLike;\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> = QuerySelect<E> | readonly QueryRaw[];\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> = {\n [K in RelationKey<E>]?: BooleanLike | QueryPopulateRelationOptions<E[K]>;\n};\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. One statement per relation named here, batched over every parent at once, so it\n * stays flat however many rows the read returned. Comes back under `_count`, which keeps it clear of\n * a relation of the same name that `$populate` filled with rows.\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\nexport type QueryCount<E> = {\n [K in ToManyRelationKey<E>]?: BooleanLike | QueryFilter<RelationTarget<E[K]>>;\n};\n\n/**\n * query conflict paths - subset of field keys used to detect upsert conflicts.\n */\nexport type QueryConflictPaths<E> = {\n [K in FieldKey<E>]?: true;\n};\n\n/**\n * Options to populate a relation declared as `V`, by its cardinality.\n */\nexport type QueryPopulateRelationOptions<V> = (IsMany<V> extends true\n ? // `$lock` is statement-level, so it is excluded here rather than being silently ignored per\n // relation. `QueryUnique` is a `Pick` and already leaves it out.\n Except<Query<RelationTarget<V>>, '$lock'>\n : QueryUnique<RelationTarget<V>>) & {\n $required?: boolean;\n};\n\n/**\n * Ambient per-request context (e.g. `{ tenantId, userId, roles }`) resolved by parameterized\n * filters. Set with `withContext(ctx, cb)`. It's an `interface` (not a type alias) so you can type\n * your keys once via declaration merging and get them typed wherever context is read:\n *\n * ```ts\n * declare module 'uql-orm' {\n * interface UqlContext { tenantId: number; userId: string }\n * }\n * ```\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 FilterCondition<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 condition: FilterCondition<E>;\n /** Applied to every query unless bypassed via `QueryOptions.filters`. Defaults to `true`. */\n readonly default?: boolean;\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.\n */\n readonly security?: boolean;\n /** What to do when the condition returns `undefined`. Defaults to `skip`, or `throw` for `security`. */\n readonly onMissing?: FilterOnMissing;\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 * To-one relations only: a parent holds many rows of a to-many, so there is no single value to order\n * it by, and joining one in would duplicate the parent instead. Order those inside `$populate`.\n */\ntype ToOneRelationKey<E> = { [K in RelationKey<E>]: IsMany<E[K]> extends true ? never : K }[RelationKey<E>];\n\n/** The relation names a parent holds many rows of, which a populated query fills with a list. */\ntype ToManyRelationKey<E> = Exclude<RelationKey<E>, ToOneRelationKey<E>>;\n\n/**\n * sort by map - supports field keys, JSON dot-notation paths (restricted to real JSON fields,\n * like `QueryWhereMap`), relation sort via nested objects, and vector similarity search on\n * `number[]` fields. `Vector` is what confines a vector search to the level the statement ranks:\n * the queried entity. A relation of it is joined in one row at a time, so there is nothing to rank\n * there - the SQL dialects throw, and MongoDB would quietly drop it, so this is its only guard.\n *\n * One mapped type over the three key sets rather than three intersected. The sets are disjoint - a\n * JSON path is dotted, and a field key cannot also be a relation key - and an assignability check\n * against an intersection is repeated per constituent, which made this the single most expensive\n * type in the package to check.\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\nexport type QuerySortMap<E, Vector extends boolean = true> = {\n [K in FieldKey<E> | JsonFieldPaths<E> | RelationKey<E>]?: K 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[K]> extends true\n ? QuerySortByCount\n : QuerySortMap<RelationTarget<E[K]>, false>\n : K extends FieldKey<E>\n ? Vector extends true\n ? NonNullable<E[K]> extends readonly number[]\n ? QuerySortValue\n : QuerySortDirection\n : QuerySortDirection\n : QuerySortDirection;\n};\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> = {\n /**\n * filtering options.\n */\n $where?: QueryWhere<E>;\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> = QueryFilter<E> & 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> = QueryPage<E> & {\n /**\n * sorting options.\n */\n $sort?: QuerySortMap<E>;\n};\n\n/**\n * query options.\n */\nexport type Query<E> = {\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>;\n\n /**\n * relation population options.\n */\n $populate?: QueryPopulate<E>;\n\n /**\n * how many rows each named relation holds, under `_count` on every row. See {@link QueryCount}.\n */\n $count?: QueryCount<E>;\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 * take a row-level lock on the rows this query returns (`SELECT ... FOR UPDATE`). Needs an open\n * transaction: outside one the statement commits and drops the lock before the caller can act on\n * the rows, so it is rejected rather than emitted. Locks only the queried entity, never anything\n * reached through `$populate`. SQL only; MongoDB and the SQLite family reject it.\n *\n * Declared here rather than on {@link QuerySearch}, which `update`/`delete` take: that placement\n * is what keeps the clause off those statements at the type level.\n */\n $lock?: QueryLock;\n\n /**\n * how many candidates an approximate-nearest-neighbour index explores before ranking, for a vector\n * search. Higher trades speed for recall; the default is whatever the engine's own is, which is\n * tuned for speed. Ignored where the search is exact (SQLite, libSQL and Turso scan every row) and\n * where the field carries no ANN index, since there is nothing to widen.\n *\n * The units are the index's, not UQL's, so the number is not comparable across index types: it\n * becomes `hnsw.ef_search` or `ivfflat.probes` on Postgres, `mhnsw_ef_search` on MariaDB, and\n * `numCandidates` on MongoDB Atlas. On Postgres it needs an open transaction, since a `SET LOCAL`\n * outside one applies to nothing.\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>;\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 * `Query`'s clauses grouped by the shape of their value - what a parser reading one off the wire and\n * a validator checking a relation's own query both need, and what each used to enumerate for itself.\n * Declared beside the type they describe so the two cannot drift, and `satisfies` fails the build\n * rather than the runtime if a clause is ever renamed.\n *\n * `$lock` belongs to no group on purpose: it is the one clause neither a wire query nor a relation's\n * query accepts, so leaving it out is what excludes it from both.\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, never a relation's own query - the mirror of\n * `$lock`, which neither takes. Counting a relation is batched over the rows a read returned, and a\n * populated relation's rows are assembled after that, so there is nothing for a nested one to count.\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/**\n * options to get a single record.\n */\nexport type QueryOne<E> = Except<Query<E>, '$limit'>;\n\n/**\n * options to get an unique record.\n */\nexport type QueryUnique<E> = Pick<QueryOne<E>, '$select' | '$exclude' | '$populate' | '$where'>;\n\n/**\n * The clauses that decide a row's shape, captured from the query as written: the field names\n * `$select` and `$exclude` list, the value those maps carry (a falsy one subtracts instead of\n * selecting, as it does at runtime, and a widened map is how a projection that is not statically\n * known announces itself), and the relation names `$populate` lists.\n *\n * Each is captured as a *key set* rather than as the map itself, which is what keeps the checks\n * intact: TypeScript skips excess-property checking on a naked type parameter, so a captured map\n * would take a typo'd key without a word, while a captured key set makes that typo fail its own\n * `FieldKey<E>` / `RelationKey<E>` constraint. Every other clause - `$where`, `$sort`, and each\n * populated relation's own query - stays the concrete {@link Query} it is today.\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> = QueryStreamProjection<E, S, V, X, P> & {\n // Intersecting the captured names with {@link QueryCount}'s own leaves a to-one relation no key\n // here at all, so counting one is an excess property rather than a value to check. Narrowing the\n // key rather than the value also instantiates `QueryCount<E>` once instead of once per counted\n // relation, worth ~87k instantiations in a consuming project.\n $count?: { [K in C & keyof QueryCount<E>]?: QueryCount<E>[K] };\n};\n\n/**\n * {@link QueryProjection} without `$count`, which a stream cannot honor. Split out rather than\n * subtracted afterwards: an optional key is a *known* key even when its value maps over `never`, so\n * a statement that must not take the clause has to be built without it in the first place.\n * @internal\n */\ntype QueryStreamProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>> = {\n $select?: { [K in S]?: V } | readonly QueryRaw[];\n $exclude?: { [K in X]?: V };\n $populate?: { [K in P]?: QueryPopulate<E>[K] };\n};\n\n/**\n * A {@link QueryProjected} a stream can honor: no `$count`, which is batched over a result set a\n * stream never holds all of.\n */\nexport type QueryStreamProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>> = Except<\n Query<E>,\n '$count'\n> &\n QueryStreamProjection<E, S, V, X, P>;\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> = Query<E> & QueryProjection<E, S, V, X, P, C>;\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> = QueryOne<E> & QueryProjection<E, S, V, X, P, C>;\n\n/**\n * The keys a query comes back with, mirroring what the runtime projects: the fields a positive\n * `$select` names, or every field minus what a falsy `$select` entry or a truthy `$exclude` entry\n * subtracts, plus the relations `$populate` asked for. A positive `$select` wins outright, which is\n * why `$exclude` is only read on the branch where there is none.\n * @internal\n */\ntype ProjectedKeys<E, S, V, X, P, C> =\n | ([V] extends [false | 0] ? Exclude<FieldKey<E>, S> : [S] extends [never] ? Exclude<FieldKey<E>, X> : S)\n | P\n // Populating a relation keeps the id whatever the projection says, and so does counting one: both\n // assemble their result by it (`selectFields` puts it back, as does MongoDB's `pipelineProjection`).\n | ([P | C] extends [never] ? never : NamedIdKey<E>);\n\n/**\n * The id key when it can be named, and nothing when it cannot: {@link IdKey} widens to *every* field\n * for an entity whose id is neither branded nor called `id`/`_id`/`uuid`, and adding that back would\n * hand the caller a row claiming fields the query never fetched. Missing an id costs a `$select`\n * entry; promising absent fields is the bug this type exists to prevent.\n * @internal\n */\ntype NamedIdKey<E> = [FieldKey<E>] extends [IdKey<E>] ? never : IdKey<E>;\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 row of a find result: the entity narrowed to the fields the query projected, plus the relations\n * it populated - reading anything the query left out is a compile error rather than a silent\n * `undefined`. Modifiers are preserved, so an optional field stays optional. Name a projected row\n * with it where a helper has to take one: `QueryFindResult<User, 'id' | 'name'>`.\n *\n * The entity itself when the query projects nothing, when it uses a raw-projection array (columns,\n * not fields), and when the projection is not uniform - a `Query<E>` built elsewhere, or a map\n * mixing selected and subtracted entries, whose positive keys inference cannot recover. Relations\n * keep their declared type: narrowing them means capturing their queries as maps, which costs those\n * queries their own checks.\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, C> & 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, C>, 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/**\n * result of an update operation.\n */\nexport type QueryUpdateResult = {\n /**\n * number of affected records.\n */\n changes?: number;\n /**\n * the inserted IDs, in insertion order. Exact on `'returning'` dialects; inferred from the\n * driver header on the others (see {@link InsertIdSource}), and empty when the header\n * reports no generated ID.\n */\n ids?: PrimaryKey[];\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",
10
+ "import type { FieldKey, IdKey, JsonFieldPaths, RelationKey, RelationTarget, 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 * prefix the query with this.\n */\n prefix?: string;\n /**\n * automatically infer the prefix for the query.\n */\n autoPrefix?: boolean;\n};\n\nexport type QuerySelectOptions = {\n /**\n * prefix the query with this.\n */\n prefix?: string;\n /**\n * automatically add the prefix for the alias.\n */\n autoPrefixAlias?: boolean;\n};\n\n/**\n * Query field selection - `{ name: true }` whitelists specific fields. Fields only: a relation is a\n * sub-query rather than a projection flag, and a whitelist naming one could not say whether the\n * scalars come with it. Relations go in `$populate`.\n */\nexport type QuerySelect<E> = {\n [K in FieldKey<E>]?: BooleanLike;\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> = QuerySelect<E> | readonly QueryRaw[];\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> = {\n [K in RelationKey<E>]?: BooleanLike | QueryPopulateRelationOptions<E[K]>;\n};\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. One statement per relation named here, batched over every parent at once, so it\n * stays flat however many rows the read returned. Comes back under `_count`, which keeps it clear of\n * a relation of the same name that `$populate` filled with rows.\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\nexport type QueryCount<E> = {\n [K in ToManyRelationKey<E>]?: BooleanLike | QueryFilter<RelationTarget<E[K]>>;\n};\n\n/**\n * query conflict paths - subset of field keys used to detect upsert conflicts.\n */\nexport type QueryConflictPaths<E> = {\n [K in FieldKey<E>]?: true;\n};\n\n/**\n * Options to populate a relation declared as `V`, by its cardinality.\n */\nexport type QueryPopulateRelationOptions<V> = (IsMany<V> extends true\n ? // `$lock` is statement-level, so it is excluded here rather than being silently ignored per\n // relation. `QueryUnique` is a `Pick` and already leaves it out.\n Except<Query<RelationTarget<V>>, '$lock'>\n : QueryUnique<RelationTarget<V>>) & {\n $required?: boolean;\n};\n\n/**\n * Ambient per-request context (e.g. `{ tenantId, userId, roles }`) resolved by parameterized\n * filters. Set with `withContext(ctx, cb)`. It's an `interface` (not a type alias) so you can type\n * your keys once via declaration merging and get them typed wherever context is read:\n *\n * ```ts\n * declare module 'uql-orm' {\n * interface UqlContext { tenantId: number; userId: string }\n * }\n * ```\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 FilterCondition<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 condition: FilterCondition<E>;\n /** Applied to every query unless bypassed via `QueryOptions.filters`. Defaults to `true`. */\n readonly default?: boolean;\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.\n */\n readonly security?: boolean;\n /** What to do when the condition returns `undefined`. Defaults to `skip`, or `throw` for `security`. */\n readonly onMissing?: FilterOnMissing;\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 * To-one relations only: a parent holds many rows of a to-many, so there is no single value to order\n * it by, and joining one in would duplicate the parent instead. Order those inside `$populate`.\n */\ntype ToOneRelationKey<E> = { [K in RelationKey<E>]: IsMany<E[K]> extends true ? never : K }[RelationKey<E>];\n\n/** The relation names a parent holds many rows of, which a populated query fills with a list. */\ntype ToManyRelationKey<E> = Exclude<RelationKey<E>, ToOneRelationKey<E>>;\n\n/**\n * sort by map - supports field keys, JSON dot-notation paths (restricted to real JSON fields,\n * like `QueryWhereMap`), relation sort via nested objects, and vector similarity search on\n * `number[]` fields. `Vector` is what confines a vector search to the level the statement ranks:\n * the queried entity. A relation of it is joined in one row at a time, so there is nothing to rank\n * there - the SQL dialects throw, and MongoDB would quietly drop it, so this is its only guard.\n *\n * One mapped type over the three key sets rather than three intersected. The sets are disjoint - a\n * JSON path is dotted, and a field key cannot also be a relation key - and an assignability check\n * against an intersection is repeated per constituent, which made this the single most expensive\n * type in the package to check.\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\nexport type QuerySortMap<E, Vector extends boolean = true> = {\n [K in FieldKey<E> | JsonFieldPaths<E> | RelationKey<E>]?: K 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[K]> extends true\n ? QuerySortByCount\n : QuerySortMap<RelationTarget<E[K]>, false>\n : K extends FieldKey<E>\n ? Vector extends true\n ? NonNullable<E[K]> extends readonly number[]\n ? QuerySortValue\n : QuerySortDirection\n : QuerySortDirection\n : QuerySortDirection;\n};\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> = {\n /**\n * filtering options.\n */\n $where?: QueryWhere<E>;\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> = QueryFilter<E> & 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> = QueryPage<E> & {\n /**\n * sorting options.\n */\n $sort?: QuerySortMap<E>;\n};\n\n/**\n * query options.\n */\nexport type Query<E> = {\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>;\n\n /**\n * relation population options.\n */\n $populate?: QueryPopulate<E>;\n\n /**\n * how many rows each named relation holds, under `_count` on every row. See {@link QueryCount}.\n */\n $count?: QueryCount<E>;\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 * take a row-level lock on the rows this query returns (`SELECT ... FOR UPDATE`). Needs an open\n * transaction: outside one the statement commits and drops the lock before the caller can act on\n * the rows, so it is rejected rather than emitted. Locks only the queried entity, never anything\n * reached through `$populate`. SQL only; MongoDB and the SQLite family reject it.\n *\n * Declared here rather than on {@link QuerySearch}, which `update`/`delete` take: that placement\n * is what keeps the clause off those statements at the type level.\n */\n $lock?: QueryLock;\n\n /**\n * how many candidates an approximate-nearest-neighbour index explores before ranking, for a vector\n * search. Higher trades speed for recall; the default is whatever the engine's own is, which is\n * tuned for speed. Ignored where the search is exact (SQLite, libSQL and Turso scan every row) and\n * where the field carries no ANN index, since there is nothing to widen.\n *\n * The units are the index's, not UQL's, so the number is not comparable across index types: it\n * becomes `hnsw.ef_search` or `ivfflat.probes` on Postgres, `mhnsw_ef_search` on MariaDB, and\n * `numCandidates` on MongoDB Atlas. On Postgres it needs an open transaction, since a `SET LOCAL`\n * outside one applies to nothing.\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>;\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 * `Query`'s clauses grouped by the shape of their value - what a parser reading one off the wire and\n * a validator checking a relation's own query both need, and what each used to enumerate for itself.\n * Declared beside the type they describe so the two cannot drift, and `satisfies` fails the build\n * rather than the runtime if a clause is ever renamed.\n *\n * `$lock` belongs to no group on purpose: it is the one clause neither a wire query nor a relation's\n * query accepts, so leaving it out is what excludes it from both.\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, never a relation's own query - the mirror of\n * `$lock`, which neither takes. Counting a relation is batched over the rows a read returned, and a\n * populated relation's rows are assembled after that, so there is nothing for a nested one to count.\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/**\n * options to get a single record.\n */\nexport type QueryOne<E> = Except<Query<E>, '$limit'>;\n\n/**\n * options to get an unique record.\n */\nexport type QueryUnique<E> = Pick<QueryOne<E>, '$select' | '$exclude' | '$populate' | '$where'>;\n\n/**\n * The clauses that decide a row's shape, captured from the query as written: the field names\n * `$select` and `$exclude` list, the value those maps carry (a falsy one subtracts instead of\n * selecting, as it does at runtime, and a widened map is how a projection that is not statically\n * known announces itself), and the relation names `$populate` lists.\n *\n * Each is captured as a *key set* rather than as the map itself, which is what keeps the checks\n * intact: TypeScript skips excess-property checking on a naked type parameter, so a captured map\n * would take a typo'd key without a word, while a captured key set makes that typo fail its own\n * `FieldKey<E>` / `RelationKey<E>` constraint. Every other clause - `$where`, `$sort`, and each\n * populated relation's own query - stays the concrete {@link Query} it is today.\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> = QueryStreamProjection<E, S, V, X, P> & {\n // Intersecting the captured names with {@link QueryCount}'s own leaves a to-one relation no key\n // here at all, so counting one is an excess property rather than a value to check. Narrowing the\n // key rather than the value also instantiates `QueryCount<E>` once instead of once per counted\n // relation, worth ~87k instantiations in a consuming project.\n $count?: { [K in C & keyof QueryCount<E>]?: QueryCount<E>[K] };\n};\n\n/**\n * {@link QueryProjection} without `$count`, which a stream cannot honor. Split out rather than\n * subtracted afterwards: an optional key is a *known* key even when its value maps over `never`, so\n * a statement that must not take the clause has to be built without it in the first place.\n * @internal\n */\ntype QueryStreamProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>> = {\n $select?: { [K in S]?: V } | readonly QueryRaw[];\n $exclude?: { [K in X]?: V };\n $populate?: { [K in P]?: QueryPopulate<E>[K] };\n};\n\n/**\n * A {@link QueryProjected} a stream can honor: no `$count`, which is batched over a result set a\n * stream never holds all of.\n */\nexport type QueryStreamProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>> = Except<\n Query<E>,\n '$count'\n> &\n QueryStreamProjection<E, S, V, X, P>;\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> = Query<E> & QueryProjection<E, S, V, X, P, C>;\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> = QueryOne<E> & QueryProjection<E, S, V, X, P, C>;\n\n/**\n * The keys a query comes back with, mirroring what the runtime projects: the fields a positive\n * `$select` names, or every field minus what a falsy `$select` entry or a truthy `$exclude` entry\n * subtracts, plus the relations `$populate` asked for. A positive `$select` wins outright, which is\n * why `$exclude` is only read on the branch where there is none.\n * @internal\n */\ntype ProjectedKeys<E, S, V, X, P, C> =\n | ([V] extends [false | 0] ? Exclude<FieldKey<E>, S> : [S] extends [never] ? Exclude<FieldKey<E>, X> : S)\n | P\n // Populating a relation keeps the id whatever the projection says, and so does counting one: both\n // assemble their result by it (`selectFields` puts it back, as does MongoDB's `pipelineProjection`).\n | ([P | C] extends [never] ? never : NamedIdKey<E>);\n\n/**\n * The id key when it can be named, and nothing when it cannot: {@link IdKey} widens to *every* field\n * for an entity whose id is neither branded nor called `id`/`_id`/`uuid`, and adding that back would\n * hand the caller a row claiming fields the query never fetched. Missing an id costs a `$select`\n * entry; promising absent fields is the bug this type exists to prevent.\n * @internal\n */\ntype NamedIdKey<E> = [FieldKey<E>] extends [IdKey<E>] ? never : IdKey<E>;\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 row of a find result: the entity narrowed to the fields the query projected, plus the relations\n * it populated - reading anything the query left out is a compile error rather than a silent\n * `undefined`. Modifiers are preserved, so an optional field stays optional. Name a projected row\n * with it where a helper has to take one: `QueryFindResult<User, 'id' | 'name'>`.\n *\n * The entity itself when the query projects nothing, when it uses a raw-projection array (columns,\n * not fields), and when the projection is not uniform - a `Query<E>` built elsewhere, or a map\n * mixing selected and subtracted entries, whose positive keys inference cannot recover. Relations\n * keep their declared type: narrowing them means capturing their queries as maps, which costs those\n * queries their own checks.\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, C> & 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, C>, 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/**\n * What upserting one row reports, against the entity rather than the driver.\n *\n * `created` is here and not on {@link QueryUpsertManyResult} because it is only ever knowable for a\n * single statement: a batch's `affectedRows` is a weighted sum on the dialects that report one at\n * all, and a batch of mixed shapes is several statements.\n */\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",
11
11
  "import type { Query, QueryOptions } 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 specific util module, not the barrel, so the browser bundle does not pull in entity metadata\nimport { getKeys } 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 Query<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 Query<unknown>)[]);\n\n/**\n * Parse raw query-string entries (with JSON-stringified values) into a UQL query object.\n * Symmetric counterpart of {@link stringifyQuery}. Only {@link ALLOWED_QUERY_KEYS} are honored.\n */\nexport function parseQueryParams(params: Record<string, unknown> = {}): Query<unknown> {\n const query: Record<string, unknown> = {};\n for (const key of getKeys(params)) {\n if (REJECTED_QUERY_KEYS.has(key)) {\n throw Object.assign(new TypeError(`'${key}' is not supported over HTTP`), { status: 400 });\n }\n if (ALLOWED_QUERY_KEYS.has(key)) {\n query[key] = params[key];\n }\n }\n\n for (const key of [...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\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 Query<unknown>;\n}\n\n/**\n * Serialize a UQL query object into a percent-encoded query string where object values\n * are JSON-stringified. Symmetric counterpart of {@link parseQueryParams}.\n */\nexport function stringifyQuery(query?: Record<string, unknown>): string {\n if (!query) {\n return '';\n }\n const params = new URLSearchParams();\n for (const key of getKeys(query)) {\n const value = query[key];\n if (value === undefined) {\n continue;\n }\n params.append(key, typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value));\n }\n const qs = params.toString();\n return qs ? `?${qs}` : '';\n}\n",
12
- "import { CRUD_ROUTES, entityPath, type HttpMethod } from '../../http/contract.js';\nimport { stringifyQuery } from '../../http/query.js';\nimport type {\n EntityData,\n EntityId,\n FieldKey,\n IdValue,\n Query,\n QueryFilter,\n QueryFindResult,\n QueryOneProjected,\n QueryOptions,\n QueryPage,\n QueryProjected,\n QuerySearch,\n RelationKey,\n RequestCountedSuccessResponse,\n RequestSuccessResponse,\n Type,\n UpdatePayload,\n} from '../../type/index.js';\nimport { isScalarId } from '../../util/object.util.js';\nimport { get, query as httpQuery, patch, post, put, remove } from '../http/index.js';\nimport type { ClientQuerier, RequestFindOptions, RequestOptions } from '../type/index.js';\n\nexport type HttpQuerierDefaults = {\n /**\n * headers sent with every request from this instance, merged under per-call headers.\n * Create one instance per request (e.g. during SSR) to scope auth headers safely.\n */\n readonly headers?: Record<string, string>;\n /**\n * transport for read queries (findOne, findMany, count). 'QUERY' (RFC 10008) sends the\n * JSON query in the request body, avoiding URL-length limits for large queries; requires\n * infrastructure (proxies, CDNs) that forwards the QUERY method. Defaults to 'GET'.\n */\n readonly readMethod?: Extract<HttpMethod, 'GET' | 'QUERY'>;\n /**\n * The URL segment an entity is addressed by, defaulting to its kebab-cased class name - the same\n * option the server handler takes, so one map serves both. State it where the default cannot: a\n * build that minifies class names renames every route.\n */\n readonly entityPath?: (entity: Type<unknown>) => string;\n};\n\n/**\n * The id as one path segment.\n *\n * A composite key has no spelling here yet - the route is `/:id`, and how several columns share one\n * segment is a serialization to invent rather than copy. Refused rather than interpolated, which\n * would have sent `[object Object]` for the server to reject. Its callers are `async` so this\n * surfaces as a rejection, like every other failure they can hand back.\n */\nfunction idSegment<E>(entity: Type<E>, id: EntityId<E>): string {\n if (!isScalarId(id)) {\n throw new TypeError(`'${entity.name}' was addressed by an id object, which the HTTP route cannot carry.`);\n }\n return String(id);\n}\n\nexport class HttpQuerier implements ClientQuerier {\n constructor(\n readonly basePath: string,\n readonly defaults: HttpQuerierDefaults = {},\n ) {}\n\n async findOneById<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n id: EntityId<E>,\n q?: QueryOneProjected<E, S, V, X, P, C>,\n opts?: RequestOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C> | undefined>> {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(q);\n return get<QueryFindResult<E, S, V, X, P, C> | undefined>(\n `${basePath}/${idSegment(entity, id)}${qs}`,\n this.buildOptions(opts),\n );\n }\n\n findOne<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n q: QueryOneProjected<E, S, V, X, P, C>,\n opts?: RequestOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C> | undefined>> {\n return this.read<QueryFindResult<E, S, V, X, P, C> | undefined>(\n `${this.getBasePath(entity)}${CRUD_ROUTES.findOne.path}`,\n q,\n opts,\n );\n }\n\n findMany<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n q: QueryProjected<E, S, V, X, P, C>,\n opts?: RequestFindOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>> {\n const data: Query<E> & { count?: boolean } = { ...q };\n if (opts?.count) {\n data.count = true;\n }\n return this.read<QueryFindResult<E, S, V, X, P, C>[]>(this.getBasePath(entity), data, opts);\n }\n\n async findManyAndCount<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n q: QueryProjected<E, S, V, X, P, C>,\n opts?: RequestFindOptions,\n ): Promise<RequestCountedSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>> {\n const response = await this.findMany(entity, q, { ...opts, count: true });\n if (typeof response.count !== 'number') {\n throw new TypeError('findManyAndCount response has an invalid count');\n }\n return { ...response, count: response.count };\n }\n\n count<E extends object>(entity: Type<E>, q?: QueryPage<E>, opts?: RequestOptions) {\n return this.read<number>(`${this.getBasePath(entity)}${CRUD_ROUTES.count.path}`, q, opts);\n }\n\n /** The `count` route capped at one row, so existence needs no endpoint of its own. */\n async exists<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: RequestOptions) {\n const res = await this.count(entity, { ...q, $limit: 1 }, opts);\n return { ...res, data: res.data > 0 };\n }\n\n insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return post<IdValue<E> | undefined>(basePath, payload, this.buildOptions(opts));\n }\n\n insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return post<(IdValue<E> | undefined)[]>(\n `${basePath}${CRUD_ROUTES.insertMany.path}`,\n payload,\n this.buildOptions(opts),\n );\n }\n\n async updateOneById<E extends object>(\n entity: Type<E>,\n id: EntityId<E>,\n payload: UpdatePayload<E>,\n opts?: RequestOptions,\n ) {\n const basePath = this.getBasePath(entity);\n return patch<number>(`${basePath}/${idSegment(entity, id)}`, payload, this.buildOptions(opts));\n }\n\n updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(q);\n return patch<number>(`${basePath}${qs}`, payload, this.buildOptions(opts));\n }\n\n saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return put<EntityId<E> | undefined>(basePath, payload, this.buildOptions(opts));\n }\n\n saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return put<(EntityId<E> | undefined)[]>(\n `${basePath}${CRUD_ROUTES.saveMany.path}`,\n payload,\n this.buildOptions(opts),\n );\n }\n\n async deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts: QueryOptions & RequestOptions = {}) {\n const basePath = this.getBasePath(entity);\n const qs = opts.hardDelete ? stringifyQuery({ hardDelete: opts.hardDelete }) : '';\n return remove<number>(`${basePath}/${idSegment(entity, id)}${qs}`, this.buildOptions(opts));\n }\n\n deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts: QueryOptions & RequestOptions = {}) {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(opts.hardDelete ? { ...q, hardDelete: opts.hardDelete } : q);\n return remove<number>(`${basePath}${qs}`, this.buildOptions(opts));\n }\n\n getBasePath<E>(entity: Type<E>) {\n return `${this.basePath}/${(this.defaults.entityPath ?? entityPath)(entity)}`;\n }\n\n protected read<T>(path: string, q: Record<string, unknown> | undefined, opts?: RequestOptions) {\n if (this.defaults.readMethod === 'QUERY') {\n return httpQuery<T>(path, q ?? {}, this.buildOptions(opts));\n }\n return get<T>(`${path}${stringifyQuery(q)}`, this.buildOptions(opts));\n }\n\n protected buildOptions(opts?: RequestOptions): RequestOptions | undefined {\n if (!this.defaults.headers && !opts?.headers) {\n return opts;\n }\n return { ...opts, headers: { ...this.defaults.headers, ...opts?.headers } };\n }\n}\n",
12
+ "import { CRUD_ROUTES, entityPath, type HttpMethod } from '../../http/contract.js';\nimport { stringifyQuery } from '../../http/query.js';\nimport type {\n EntityData,\n EntityId,\n FieldKey,\n Query,\n QueryFilter,\n QueryFindResult,\n QueryOneProjected,\n QueryOptions,\n QueryPage,\n QueryProjected,\n QuerySearch,\n RelationKey,\n RequestCountedSuccessResponse,\n RequestSuccessResponse,\n Type,\n UpdatePayload,\n WrittenId,\n} from '../../type/index.js';\nimport { isScalarId } from '../../util/object.util.js';\nimport { get, query as httpQuery, patch, post, put, remove } from '../http/index.js';\nimport type { ClientQuerier, RequestFindOptions, RequestOptions } from '../type/index.js';\n\nexport type HttpQuerierDefaults = {\n /**\n * headers sent with every request from this instance, merged under per-call headers.\n * Create one instance per request (e.g. during SSR) to scope auth headers safely.\n */\n readonly headers?: Record<string, string>;\n /**\n * transport for read queries (findOne, findMany, count). 'QUERY' (RFC 10008) sends the\n * JSON query in the request body, avoiding URL-length limits for large queries; requires\n * infrastructure (proxies, CDNs) that forwards the QUERY method. Defaults to 'GET'.\n */\n readonly readMethod?: Extract<HttpMethod, 'GET' | 'QUERY'>;\n /**\n * The URL segment an entity is addressed by, defaulting to its kebab-cased class name - the same\n * option the server handler takes, so one map serves both. State it where the default cannot: a\n * build that minifies class names renames every route.\n */\n readonly entityPath?: (entity: Type<unknown>) => string;\n};\n\n/**\n * The id as one path segment.\n *\n * A composite key has no spelling here yet - the route is `/:id`, and how several columns share one\n * segment is a serialization to invent rather than copy. Refused rather than interpolated, which\n * would have sent `[object Object]` for the server to reject. Its callers are `async` so this\n * surfaces as a rejection, like every other failure they can hand back.\n */\nfunction idSegment<E>(entity: Type<E>, id: EntityId<E>): string {\n if (!isScalarId(id)) {\n throw new TypeError(`'${entity.name}' was addressed by an id object, which the HTTP route cannot carry.`);\n }\n return String(id);\n}\n\nexport class HttpQuerier implements ClientQuerier {\n constructor(\n readonly basePath: string,\n readonly defaults: HttpQuerierDefaults = {},\n ) {}\n\n async findOneById<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n id: EntityId<E>,\n q?: QueryOneProjected<E, S, V, X, P, C>,\n opts?: RequestOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C> | undefined>> {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(q);\n return get<QueryFindResult<E, S, V, X, P, C> | undefined>(\n `${basePath}/${idSegment(entity, id)}${qs}`,\n this.buildOptions(opts),\n );\n }\n\n findOne<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n q: QueryOneProjected<E, S, V, X, P, C>,\n opts?: RequestOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C> | undefined>> {\n return this.read<QueryFindResult<E, S, V, X, P, C> | undefined>(\n `${this.getBasePath(entity)}${CRUD_ROUTES.findOne.path}`,\n q,\n opts,\n );\n }\n\n findMany<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n q: QueryProjected<E, S, V, X, P, C>,\n opts?: RequestFindOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>> {\n const data: Query<E> & { count?: boolean } = { ...q };\n if (opts?.count) {\n data.count = true;\n }\n return this.read<QueryFindResult<E, S, V, X, P, C>[]>(this.getBasePath(entity), data, opts);\n }\n\n async findManyAndCount<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n q: QueryProjected<E, S, V, X, P, C>,\n opts?: RequestFindOptions,\n ): Promise<RequestCountedSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>> {\n const response = await this.findMany(entity, q, { ...opts, count: true });\n if (typeof response.count !== 'number') {\n throw new TypeError('findManyAndCount response has an invalid count');\n }\n return { ...response, count: response.count };\n }\n\n count<E extends object>(entity: Type<E>, q?: QueryPage<E>, opts?: RequestOptions) {\n return this.read<number>(`${this.getBasePath(entity)}${CRUD_ROUTES.count.path}`, q, opts);\n }\n\n /** The `count` route capped at one row, so existence needs no endpoint of its own. */\n async exists<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: RequestOptions) {\n const res = await this.count(entity, { ...q, $limit: 1 }, opts);\n return { ...res, data: res.data > 0 };\n }\n\n insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return post<WrittenId<E> | undefined>(basePath, payload, this.buildOptions(opts));\n }\n\n insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return post<(WrittenId<E> | undefined)[]>(\n `${basePath}${CRUD_ROUTES.insertMany.path}`,\n payload,\n this.buildOptions(opts),\n );\n }\n\n async updateOneById<E extends object>(\n entity: Type<E>,\n id: EntityId<E>,\n payload: UpdatePayload<E>,\n opts?: RequestOptions,\n ) {\n const basePath = this.getBasePath(entity);\n return patch<number>(`${basePath}/${idSegment(entity, id)}`, payload, this.buildOptions(opts));\n }\n\n updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(q);\n return patch<number>(`${basePath}${qs}`, payload, this.buildOptions(opts));\n }\n\n saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return put<WrittenId<E> | undefined>(basePath, payload, this.buildOptions(opts));\n }\n\n saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return put<(WrittenId<E> | undefined)[]>(\n `${basePath}${CRUD_ROUTES.saveMany.path}`,\n payload,\n this.buildOptions(opts),\n );\n }\n\n async deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts: QueryOptions & RequestOptions = {}) {\n const basePath = this.getBasePath(entity);\n const qs = opts.hardDelete ? stringifyQuery({ hardDelete: opts.hardDelete }) : '';\n return remove<number>(`${basePath}/${idSegment(entity, id)}${qs}`, this.buildOptions(opts));\n }\n\n deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts: QueryOptions & RequestOptions = {}) {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(opts.hardDelete ? { ...q, hardDelete: opts.hardDelete } : q);\n return remove<number>(`${basePath}${qs}`, this.buildOptions(opts));\n }\n\n getBasePath<E>(entity: Type<E>) {\n return `${this.basePath}/${(this.defaults.entityPath ?? entityPath)(entity)}`;\n }\n\n protected read<T>(path: string, q: Record<string, unknown> | undefined, opts?: RequestOptions) {\n if (this.defaults.readMethod === 'QUERY') {\n return httpQuery<T>(path, q ?? {}, this.buildOptions(opts));\n }\n return get<T>(`${path}${stringifyQuery(q)}`, this.buildOptions(opts));\n }\n\n protected buildOptions(opts?: RequestOptions): RequestOptions | undefined {\n if (!this.defaults.headers && !opts?.headers) {\n return opts;\n }\n return { ...opts, headers: { ...this.defaults.headers, ...opts?.headers } };\n }\n}\n",
13
13
  "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"
14
14
  ],
15
- "mappings": "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",
16
- "debugId": "565BC245D18F9E8464756E2164756E21",
15
+ "mappings": "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",
16
+ "debugId": "00C82941ED3E3C5464756E2164756E21",
17
17
  "names": []
18
18
  }
@@ -104,7 +104,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
104
104
  * The alias names one column, and every column of a composite came from the caller, so there is no
105
105
  * id the statement could report that the payload does not already carry - the same "no id to give"
106
106
  * a `firstId` dialect already answers with. Empty rather than a refusal, so an insert and an upsert
107
- * of a composite row both run; `idOf(meta, row)` names such a row.
107
+ * of a composite row both run, and the querier names those rows with `idOf`.
108
108
  */
109
109
  returningId<E>(meta: EntityMeta<E>): string;
110
110
  /** `<id column> AS id` on its own, for a statement composing a `RETURNING` list of several items. */
@@ -118,7 +118,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
118
118
  protected selectFieldExpr(escapedColumn: string, _field: FieldOptions): string;
119
119
  /**
120
120
  * The `$text` full-text predicate, which every engine spells differently: `MATCH ... AGAINST`
121
- * (MySQL family), `to_tsvector @@ websearch_to_tsquery` (Postgres-wire), an FTS5 `MATCH` against
121
+ * (MySQL family), `TO_TSVECTOR @@ WEBSEARCH_TO_TSQUERY` (Postgres-wire), an FTS5 `MATCH` against
122
122
  * the table itself (SQLite). No portable form exists, so a dialect without one says so here rather
123
123
  * than inheriting another engine's syntax.
124
124
  */
@@ -253,7 +253,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
253
253
  */
254
254
  protected readonly jsonContainmentIsPartial: boolean;
255
255
  /**
256
- * Whether an exploded *scalar* element keeps its SQL type. SQLite's `json_each` yields JSON
256
+ * Whether an exploded *scalar* element keeps its SQL type. SQLite's `JSON_EACH` yields JSON
257
257
  * booleans as `0`/`1` integers and numbers as numbers, so such an element compares directly to a
258
258
  * bound value; PostgreSQL and MySQL explode scalars to text, losing the type, so a non-string
259
259
  * operand there has to compare as JSON (see {@link isJsonbOp}).
@@ -562,7 +562,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
562
562
  private buildExprComparison;
563
563
  /**
564
564
  * Build a complete `$size` comparison expression.
565
- * @param sizeExprFn - function that appends the size expression to ctx (e.g. `jsonb_array_length("col")`)
565
+ * @param sizeExprFn - function that appends the size expression to ctx (e.g. `JSONB_ARRAY_LENGTH("col")`)
566
566
  */
567
567
  protected buildSizeComparison(ctx: QueryContext, sizeExprFn: () => void, sizeVal: number | QuerySizeComparisonOps): void;
568
568
  /**
@@ -150,7 +150,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
150
150
  * The alias names one column, and every column of a composite came from the caller, so there is no
151
151
  * id the statement could report that the payload does not already carry - the same "no id to give"
152
152
  * a `firstId` dialect already answers with. Empty rather than a refusal, so an insert and an upsert
153
- * of a composite row both run; `idOf(meta, row)` names such a row.
153
+ * of a composite row both run, and the querier names those rows with `idOf`.
154
154
  */
155
155
  returningId(meta) {
156
156
  const expression = this.returningIdExpression(meta);
@@ -236,7 +236,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
236
236
  }
237
237
  /**
238
238
  * The `$text` full-text predicate, which every engine spells differently: `MATCH ... AGAINST`
239
- * (MySQL family), `to_tsvector @@ websearch_to_tsquery` (Postgres-wire), an FTS5 `MATCH` against
239
+ * (MySQL family), `TO_TSVECTOR @@ WEBSEARCH_TO_TSQUERY` (Postgres-wire), an FTS5 `MATCH` against
240
240
  * the table itself (SQLite). No portable form exists, so a dialect without one says so here rather
241
241
  * than inheriting another engine's syntax.
242
242
  */
@@ -679,7 +679,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
679
679
  */
680
680
  jsonContainmentIsPartial = true;
681
681
  /**
682
- * Whether an exploded *scalar* element keeps its SQL type. SQLite's `json_each` yields JSON
682
+ * Whether an exploded *scalar* element keeps its SQL type. SQLite's `JSON_EACH` yields JSON
683
683
  * booleans as `0`/`1` integers and numbers as numbers, so such an element compares directly to a
684
684
  * bound value; PostgreSQL and MySQL explode scalars to text, losing the type, so a non-string
685
685
  * operand there has to compare as JSON (see {@link isJsonbOp}).
@@ -1597,7 +1597,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1597
1597
  }
1598
1598
  /**
1599
1599
  * Build a complete `$size` comparison expression.
1600
- * @param sizeExprFn - function that appends the size expression to ctx (e.g. `jsonb_array_length("col")`)
1600
+ * @param sizeExprFn - function that appends the size expression to ctx (e.g. `JSONB_ARRAY_LENGTH("col")`)
1601
1601
  */
1602
1602
  buildSizeComparison(ctx, sizeExprFn, sizeVal) {
1603
1603
  if (typeof sizeVal === 'number') {
@@ -7,7 +7,7 @@ import type { FieldOptions, FieldType } from '../type/index.js';
7
7
  export declare function jsonPath(path: string, suffix?: string): string;
8
8
  /**
9
9
  * `FN(target, path, value, ...)` - the multi-pair JSON assignment shape shared by MySQL's
10
- * `JSON_SET` and SQLite's `json_set`/`json_insert`. `pathSuffix` appends an accessor per key
10
+ * `JSON_SET` and SQLite's `JSON_SET`/`JSON_INSERT`. `pathSuffix` appends an accessor per key
11
11
  * (SQLite's `[#]` append). Values bind in key order through `bindValue`, the caller's
12
12
  * `jsonScalarParam` bound to its `QueryContext`.
13
13
  */
@@ -54,7 +54,7 @@ export declare function jsonTypeMode(type: FieldType): JsonAccessMode;
54
54
  * Whether the operator reads the JSON *value* instead of its text form. The array operators always
55
55
  * do. Equality joins them for boolean operands, because extracting JSON as text loses the type in
56
56
  * a way no cast recovers portably: PostgreSQL raises `operator does not exist: text = boolean`,
57
- * MySQL compares `'true'` to `1` and silently matches nothing, and SQLite's `json_extract` yields
57
+ * MySQL compares `'true'` to `1` and silently matches nothing, and SQLite's `JSON_EXTRACT` yields
58
58
  * `1`. Comparing the JSON value against a JSON-encoded parameter is exact on every dialect.
59
59
  *
60
60
  * Numbers stay on the text accessor with a numeric cast, which keeps `1` equal to `1.0` - JSON
@@ -11,7 +11,7 @@ export function jsonPath(path, suffix = '') {
11
11
  }
12
12
  /**
13
13
  * `FN(target, path, value, ...)` - the multi-pair JSON assignment shape shared by MySQL's
14
- * `JSON_SET` and SQLite's `json_set`/`json_insert`. `pathSuffix` appends an accessor per key
14
+ * `JSON_SET` and SQLite's `JSON_SET`/`JSON_INSERT`. `pathSuffix` appends an accessor per key
15
15
  * (SQLite's `[#]` append). Values bind in key order through `bindValue`, the caller's
16
16
  * `jsonScalarParam` bound to its `QueryContext`.
17
17
  */
@@ -77,7 +77,7 @@ export function jsonTypeMode(type) {
77
77
  * Whether the operator reads the JSON *value* instead of its text form. The array operators always
78
78
  * do. Equality joins them for boolean operands, because extracting JSON as text loses the type in
79
79
  * a way no cast recovers portably: PostgreSQL raises `operator does not exist: text = boolean`,
80
- * MySQL compares `'true'` to `1` and silently matches nothing, and SQLite's `json_extract` yields
80
+ * MySQL compares `'true'` to `1` and silently matches nothing, and SQLite's `JSON_EXTRACT` yields
81
81
  * `1`. Comparing the JSON value against a JSON-encoded parameter is exact on every dialect.
82
82
  *
83
83
  * Numbers stay on the text accessor with a numeric cast, which keeps `1` equal to `1.0` - JSON
@@ -3,7 +3,7 @@ import { type ParentPartition } from '../util/relationQuery.util.js';
3
3
  import { AbstractSqlDialect } from './abstractSqlDialect.js';
4
4
  /**
5
5
  * Shared AST/quoting/JSONB/full-text-search/vector-search implementation between Postgres and
6
- * CockroachDB (wire- and SQL-compatible for everything below, including `to_tsvector`/`to_tsquery`
6
+ * CockroachDB (wire- and SQL-compatible for everything below, including `TO_TSVECTOR`/`TO_TSQUERY`
7
7
  * and pgvector's `<=>`/`<->`/`<#>` distance operators, which CockroachDB implements natively).
8
8
  * `xmax`-based upsert `created` detection is Postgres-only (CockroachDB has no `xmax`/`ctid`) and
9
9
  * stays in {@link PostgresDialect}, along with the `vectorExtension`/`vectorIndexStyle` values that
@@ -64,8 +64,8 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
64
64
  normalizeValue(value: unknown): unknown;
65
65
  placeholder(index: number): string;
66
66
  /**
67
- * `to_tsvector(...) @@ websearch_to_tsquery(...)`. `websearch_to_tsquery` takes free-form user input
68
- * (quoted phrases, `or`, `-negation`) and never raises a syntax error, unlike `to_tsquery`, which
67
+ * `TO_TSVECTOR(...) @@ WEBSEARCH_TO_TSQUERY(...)`. `WEBSEARCH_TO_TSQUERY` takes free-form user input
68
+ * (quoted phrases, `or`, `-negation`) and never raises a syntax error, unlike `TO_TSQUERY`, which
69
69
  * rejects anything unparseable - including a plain two-word search.
70
70
  */
71
71
  protected appendTextSearch<E>(ctx: QueryContext, _entity: Type<E>, meta: EntityMeta<E>, search: QueryTextSearchOptions<E>): void;
@@ -10,7 +10,7 @@ import { jsonSetTarget } from './jsonSql.js';
10
10
  import { resolveVectorCast, toSparsevecLiteral } from './vectorCast.js';
11
11
  /**
12
12
  * Shared AST/quoting/JSONB/full-text-search/vector-search implementation between Postgres and
13
- * CockroachDB (wire- and SQL-compatible for everything below, including `to_tsvector`/`to_tsquery`
13
+ * CockroachDB (wire- and SQL-compatible for everything below, including `TO_TSVECTOR`/`TO_TSQUERY`
14
14
  * and pgvector's `<=>`/`<->`/`<#>` distance operators, which CockroachDB implements natively).
15
15
  * `xmax`-based upsert `created` detection is Postgres-only (CockroachDB has no `xmax`/`ctid`) and
16
16
  * stays in {@link PostgresDialect}, along with the `vectorExtension`/`vectorIndexStyle` values that
@@ -66,7 +66,7 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
66
66
  const keys = this.escapeId(ctx.nextAlias(PER_PARENT_KEYS_ALIAS));
67
67
  const branch = this.escapeId(ctx.nextAlias(PER_PARENT_BRANCH_ALIAS));
68
68
  const column = (index) => `${keys}.k${index}`;
69
- // `unnest` resolves an uncast parameter to `unknown` and refuses it ("function unnest(unknown) is
69
+ // `UNNEST` resolves an uncast parameter to `unknown` and refuses it ("function unnest(unknown) is
70
70
  // not unique"), so the array says its type. It comes from the parent's key column, which always
71
71
  // declares one, rather than the child's foreign key, which would have to be resolved through the
72
72
  // reference it takes its own type from.
@@ -78,7 +78,7 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
78
78
  const values = parents.map((it) => it[parent]);
79
79
  return `${this.addValue(ctx.values, values)}::${canonicalToSql(fieldOptionsToCanonical(field), this)}[]`;
80
80
  });
81
- const rowSource = `unnest(${sources.join(', ')}) AS ${keys}(${joins.map((_, index) => `k${index}`).join(', ')})`;
81
+ const rowSource = `UNNEST(${sources.join(', ')}) AS ${keys}(${joins.map((_, index) => `k${index}`).join(', ')})`;
82
82
  // The keys come from the row source rather than as values, which is the whole point of correlating:
83
83
  // one branch, planned once, instead of one per parent.
84
84
  const correlated = Object.fromEntries(joins.map(({ joined }, index) => [joined, raw(({ ctx: inner }) => inner.append(column(index)))]));
@@ -141,8 +141,8 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
141
141
  return `$${index}`;
142
142
  }
143
143
  /**
144
- * `to_tsvector(...) @@ websearch_to_tsquery(...)`. `websearch_to_tsquery` takes free-form user input
145
- * (quoted phrases, `or`, `-negation`) and never raises a syntax error, unlike `to_tsquery`, which
144
+ * `TO_TSVECTOR(...) @@ WEBSEARCH_TO_TSQUERY(...)`. `WEBSEARCH_TO_TSQUERY` takes free-form user input
145
+ * (quoted phrases, `or`, `-negation`) and never raises a syntax error, unlike `TO_TSQUERY`, which
146
146
  * rejects anything unparseable - including a plain two-word search.
147
147
  */
148
148
  appendTextSearch(ctx, _entity, meta, search) {
@@ -151,7 +151,7 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
151
151
  .join(` || ' ' || `);
152
152
  // The config is bound once and its numbered placeholder reused by both calls.
153
153
  const config = search.$config ? `${this.addValue(ctx.values, search.$config)}::regconfig, ` : '';
154
- ctx.append(`to_tsvector(${config}${fields}) @@ websearch_to_tsquery(${config}`);
154
+ ctx.append(`TO_TSVECTOR(${config}${fields}) @@ WEBSEARCH_TO_TSQUERY(${config}`);
155
155
  ctx.addValue(search.$value);
156
156
  ctx.append(')');
157
157
  }
@@ -159,14 +159,14 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
159
159
  return `${jsonField} @> ${this.jsonVal(ctx, value)}`;
160
160
  }
161
161
  jsonSize(ctx, jsonField, value) {
162
- return this.buildFragment(ctx, (fragmentCtx) => this.buildSizeComparison(fragmentCtx, () => fragmentCtx.append(`jsonb_array_length(${jsonField})`), value));
162
+ return this.buildFragment(ctx, (fragmentCtx) => this.buildSizeComparison(fragmentCtx, () => fragmentCtx.append(`JSONB_ARRAY_LENGTH(${jsonField})`), value));
163
163
  }
164
164
  /**
165
165
  * Object elements stay `jsonb` so each field can pick `->` or `->>`. Scalar elements are exploded
166
166
  * as text unless they are compared as JSON, where `_text` would yield `text = jsonb`.
167
167
  */
168
168
  jsonElemFrom(jsonField, fields, alias, asJson = false) {
169
- const fn = fields.length || asJson ? 'jsonb_array_elements' : 'jsonb_array_elements_text';
169
+ const fn = fields.length || asJson ? 'JSONB_ARRAY_ELEMENTS' : 'JSONB_ARRAY_ELEMENTS_TEXT';
170
170
  return `${fn}(${jsonField}) AS ${alias}`;
171
171
  }
172
172
  jsonElemRef(alias, field, asJson = false) {
@@ -209,8 +209,8 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
209
209
  */
210
210
  jsonPullKey(ctx, expr, escapedCol, key, value) {
211
211
  const escapedKey = escapeSingleQuotes(key);
212
- const kept = `SELECT jsonb_agg(${JSON_PULL_ALIAS}.val ORDER BY ${JSON_PULL_ALIAS}.ord) FROM jsonb_array_elements(${escapedCol}->'${escapedKey}') WITH ORDINALITY AS ${JSON_PULL_ALIAS}(val, ord) WHERE ${JSON_PULL_ALIAS}.val <> ${this.jsonVal(ctx, value)}`;
213
- return `jsonb_set(${expr}, '{${escapedKey}}', COALESCE((${kept}), '[]'::jsonb), false)`;
212
+ const kept = `SELECT JSONB_AGG(${JSON_PULL_ALIAS}.val ORDER BY ${JSON_PULL_ALIAS}.ord) FROM JSONB_ARRAY_ELEMENTS(${escapedCol}->'${escapedKey}') WITH ORDINALITY AS ${JSON_PULL_ALIAS}(val, ord) WHERE ${JSON_PULL_ALIAS}.val <> ${this.jsonVal(ctx, value)}`;
213
+ return `JSONB_SET(${expr}, '{${escapedKey}}', COALESCE((${kept}), '[]'::jsonb), false)`;
214
214
  }
215
215
  jsonSet(ctx, expr, set, field) {
216
216
  return `${jsonSetTarget(expr, field, `'{}'::jsonb`)} || ${this.jsonVal(ctx, set)}`;
@@ -220,7 +220,7 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
220
220
  return Object.entries(push).reduce((acc, [key, value]) => {
221
221
  const escapedKey = escapeSingleQuotes(key);
222
222
  const ph = this.jsonVal(ctx, value);
223
- return `jsonb_set(${acc}, '{${escapedKey}}', COALESCE((${acc})->'${escapedKey}', '[]'::jsonb) || jsonb_build_array(${ph}))`;
223
+ return `JSONB_SET(${acc}, '{${escapedKey}}', COALESCE((${acc})->'${escapedKey}', '[]'::jsonb) || JSONB_BUILD_ARRAY(${ph}))`;
224
224
  }, expr);
225
225
  }
226
226
  jsonUnset(ctx, expr, unset) {
@@ -1,4 +1,4 @@
1
- import type { EntityData, EntityId, EntityIndexInput, EntityMembers, EntityMeta, EntityOptions, FieldKey, FieldOptions, FilterOptions, HookEvent, IdKey, RelationOptions, Type } from '../../type/index.js';
1
+ import type { EntityData, EntityIndexInput, EntityMembers, EntityMeta, EntityOptions, FieldKey, FieldOptions, FilterOptions, HookEvent, IdKey, RelationOptions, Type, WrittenId } from '../../type/index.js';
2
2
  export declare function defineField<E>(entity: Type<E>, key: string, opts?: FieldOptions): EntityMeta<E>;
3
3
  export declare function defineId<E>(entity: Type<E>, key: string, opts: FieldOptions): EntityMeta<E>;
4
4
  export declare function defineRelation<E>(entity: Type<E>, key: string, opts: RelationOptions): EntityMeta<E>;
@@ -40,13 +40,16 @@ export declare function soleIdOf<E>(meta: EntityMeta<E>, what: string): IdKey<E>
40
40
  */
41
41
  export declare function namesKey<E>(meta: EntityMeta<E>, row: EntityData<E>): boolean;
42
42
  /**
43
- * A row's primary key in the shape a `$where` takes: the value itself for a single key, an object
44
- * carrying every key for a composite - which is exactly {@link EntityId}.
43
+ * A row's primary key: the value itself for a single key, an object carrying every key for a
44
+ * composite - which is {@link WrittenId}, and reads as the {@link EntityId} a `$where` takes.
45
45
  *
46
- * What a settled write names its rows by, and what an insert hands back. Naming a composite row by
47
- * one of its columns would address every row agreeing on that one.
46
+ * What a settled write names its rows by, and what a write hands back. Naming a composite row by one
47
+ * of its columns would address every row agreeing on that one.
48
+ *
49
+ * `WrittenId` does not reduce for an unresolved `E`, so which branch this entity is in cannot be
50
+ * proven here, only checked - which is what `ids.length` does.
48
51
  */
49
- export declare function idOf<E>(meta: EntityMeta<E>, row: EntityData<E>): EntityId<E>;
52
+ export declare function idOf<E>(meta: EntityMeta<E>, row: EntityData<E>): WrittenId<E>;
50
53
  /**
51
54
  * Forgets an entity, and reports whether there was one - for a registry that grows at runtime, where a
52
55
  * deleted content type would otherwise keep its metadata for the life of the process. Nothing rewrites
@@ -235,18 +235,19 @@ export function namesKey(meta, row) {
235
235
  return meta.ids.every((key) => row[key] != null);
236
236
  }
237
237
  /**
238
- * A row's primary key in the shape a `$where` takes: the value itself for a single key, an object
239
- * carrying every key for a composite - which is exactly {@link EntityId}.
238
+ * A row's primary key: the value itself for a single key, an object carrying every key for a
239
+ * composite - which is {@link WrittenId}, and reads as the {@link EntityId} a `$where` takes.
240
240
  *
241
- * What a settled write names its rows by, and what an insert hands back. Naming a composite row by
242
- * one of its columns would address every row agreeing on that one.
241
+ * What a settled write names its rows by, and what a write hands back. Naming a composite row by one
242
+ * of its columns would address every row agreeing on that one.
243
+ *
244
+ * `WrittenId` does not reduce for an unresolved `E`, so which branch this entity is in cannot be
245
+ * proven here, only checked - which is what `ids.length` does.
243
246
  */
244
247
  export function idOf(meta, row) {
245
248
  const { ids } = meta;
246
- if (ids.length === 1) {
247
- return row[ids[0]];
248
- }
249
- return Object.fromEntries(ids.map((key) => [key, row[key]]));
249
+ const id = ids.length === 1 ? row[ids[0]] : Object.fromEntries(ids.map((key) => [key, row[key]]));
250
+ return id;
250
251
  }
251
252
  /**
252
253
  * Forgets an entity, and reports whether there was one - for a registry that grows at runtime, where a
@@ -109,7 +109,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
109
109
  JOIN pg_class i ON i.oid = ix.indexrelid
110
110
  JOIN pg_am am ON am.oid = i.relam
111
111
  JOIN pg_namespace n ON n.oid = t.relnamespace
112
- CROSS JOIN LATERAL unnest(ix.indkey) WITH ORDINALITY AS k(attnum, n)
112
+ CROSS JOIN LATERAL UNNEST(ix.indkey) WITH ORDINALITY AS k(attnum, n)
113
113
  LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum AND k.attnum > 0
114
114
  LEFT JOIN pg_opclass op ON op.oid = ix.indclass[k.n - 1]
115
115
  WHERE t.relname = $1
@@ -136,9 +136,9 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
136
136
  return /*sql*/ `
137
137
  SELECT
138
138
  tc.constraint_name,
139
- array_to_json(array_agg(kcu.column_name ORDER BY kcu.ordinal_position)) AS columns,
139
+ ARRAY_TO_JSON(ARRAY_AGG(kcu.column_name ORDER BY kcu.ordinal_position)) AS columns,
140
140
  ccu.table_name AS referenced_table,
141
- array_to_json(array_agg(ccu.column_name ORDER BY kcu.ordinal_position)) AS referenced_columns,
141
+ ARRAY_TO_JSON(ARRAY_AGG(ccu.column_name ORDER BY kcu.ordinal_position)) AS referenced_columns,
142
142
  rc.delete_rule,
143
143
  rc.update_rule
144
144
  FROM information_schema.table_constraints tc
@@ -75,8 +75,8 @@ export declare class MongodbQuerier extends AbstractQuerier {
75
75
  firstId?: undefined;
76
76
  } | {
77
77
  changes: number;
78
- ids: PrimaryKey[];
79
- firstId: PrimaryKey;
78
+ ids: (PrimaryKey | undefined)[];
79
+ firstId: PrimaryKey | undefined;
80
80
  }>;
81
81
  protected internalDeleteMany<E extends Document>(entity: Type<E>, qm: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
82
82
  get hasOpenTransaction(): boolean;
@@ -359,12 +359,14 @@ export class MongodbQuerier extends AbstractQuerier {
359
359
  });
360
360
  const res = await this.execute((session) => this.collection(entity).bulkWrite(operations, { session }));
361
361
  const changes = (res.upsertedCount ?? 0) + (res.modifiedCount ?? 0);
362
- // `upsertedIds` only covers newly-inserted documents (keyed by operation index); a matched and
363
- // updated document's `_id` isn't in the response, so it's simply not represented here - same
364
- // "exact where knowable, absent otherwise" convention `RETURNING`-based SQL dialects use for
365
- // rows that hit `DO NOTHING`.
366
- const ids = Object.values(res.upsertedIds).map((id) => this.dialect.fromWireId(id));
367
- return { changes, ids, firstId: ids[0] };
362
+ // `upsertedIds` only covers newly-inserted documents, keyed by operation index: a matched and
363
+ // updated document's `_id` is not in the response. Read by that index rather than flattened,
364
+ // so each id lands on the row it belongs to and the gaps stay gaps.
365
+ const ids = payload.map((_, index) => {
366
+ const id = res.upsertedIds[index];
367
+ return id === undefined ? undefined : this.dialect.fromWireId(id);
368
+ });
369
+ return { changes, ids, firstId: ids.find((id) => id !== undefined) };
368
370
  });
369
371
  }
370
372
  async internalDeleteMany(entity, qm, opts = {}) {
@@ -1,9 +1,5 @@
1
- import type { EntityData, EntityId, ExtraOptions, FieldKey, IdValue, Querier, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryPopulate, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpdateResult, RawRow, RelationKey, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
1
+ import type { EntityData, EntityId, ExtraOptions, FieldKey, IdValue, Querier, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryPopulate, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpdateResult, QueryUpsertOneResult, QueryUpsertManyResult, RawRow, RelationKey, TransactionOptions, Type, UpdatePayload, WrittenId } from '../type/index.js';
2
2
  import { LoggerWrapper, type ParentJoin, type ParentPartition } from '../util/index.js';
3
- /**
4
- * Base class for all database queriers.
5
- * It provides a standardized way to execute tasks serially to prevent race conditions on database connections.
6
- */
7
3
  export declare abstract class AbstractQuerier implements Querier {
8
4
  readonly extra?: ExtraOptions | undefined;
9
5
  /**
@@ -111,8 +107,12 @@ export declare abstract class AbstractQuerier implements Querier {
111
107
  protected abstract internalAggregate<E extends object, G extends QueryGroupMap<E>, A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
112
108
  /** Abstract outright: nothing is shared to do around it. See {@link UniversalQuerier.estimatedCount}. */
113
109
  abstract estimatedCount<E extends object>(entity: Type<E>): Promise<number>;
114
- insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<IdValue<E> | undefined>;
115
- insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(IdValue<E> | undefined)[]>;
110
+ insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<WrittenId<E> | undefined>;
111
+ /**
112
+ * A composite key is named here rather than by the statement: no column holds it, so no database
113
+ * reports one, but the caller wrote every column of it and the payload still carries them.
114
+ */
115
+ insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(WrittenId<E> | undefined)[]>;
116
116
  protected abstract internalInsertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(IdValue<E> | undefined)[]>;
117
117
  updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
118
118
  updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
@@ -125,8 +125,8 @@ export declare abstract class AbstractQuerier implements Querier {
125
125
  * but the upsert itself is a fact known before and after, and a row written with no hook at all
126
126
  * was how an `@Id({ onInsert })` or an audit trail silently skipped this path.
127
127
  */
128
- upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
129
- upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
128
+ upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpsertOneResult<E>>;
129
+ upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpsertManyResult<E>>;
130
130
  protected abstract internalUpsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
131
131
  protected abstract internalUpsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
132
132
  deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts?: QueryOptions): Promise<number>;
@@ -148,7 +148,7 @@ export declare abstract class AbstractQuerier implements Querier {
148
148
  */
149
149
  private findDoomed;
150
150
  protected abstract internalDeleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
151
- saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<EntityId<E> | undefined>;
151
+ saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<WrittenId<E> | undefined>;
152
152
  /**
153
153
  * Insert or update, as the name has always promised - and now as one statement per kind rather
154
154
  * than a guess.
@@ -165,7 +165,7 @@ export declare abstract class AbstractQuerier implements Querier {
165
165
  * The hooks follow the statement: a named row fires `beforeUpsert`/`afterUpsert`, never the
166
166
  * update pair, because the database picks the branch as the statement runs.
167
167
  */
168
- saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(EntityId<E> | undefined)[]>;
168
+ saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(WrittenId<E> | undefined)[]>;
169
169
  protected fillToManyRelations<E>(entity: Type<E>, payload: E[], populate?: QueryPopulate<E>): Promise<void>;
170
170
  private fillToManyThroughRelation;
171
171
  private fillToManyOneToMany;
@@ -43,6 +43,19 @@ function soleParentColumn(relOpts) {
43
43
  * Base class for all database queriers.
44
44
  * It provides a standardized way to execute tasks serially to prevent race conditions on database connections.
45
45
  */
46
+ /**
47
+ * The ids an upsert reports, payload-aligned so the result zips with the rows that were passed.
48
+ *
49
+ * A composite is named from the payload, as an insert's is: no column holds that key, so no
50
+ * statement reports one. A sole key takes what the statement reported when it spoke for every row,
51
+ * and otherwise falls back to the key the caller supplied - a `firstId` dialect reports nothing for
52
+ * a batch, which is not the same as those rows having no id.
53
+ */
54
+ function upsertIds(meta, payload, reported) {
55
+ return meta.ids.length === 1 && reported?.length === payload.length
56
+ ? reported
57
+ : payload.map((it) => (namesKey(meta, it) ? idOf(meta, it) : undefined));
58
+ }
46
59
  export class AbstractQuerier {
47
60
  extra;
48
61
  /**
@@ -167,8 +180,17 @@ export class AbstractQuerier {
167
180
  const [id] = await this.insertMany(entity, [payload]);
168
181
  return id;
169
182
  }
183
+ /**
184
+ * A composite key is named here rather than by the statement: no column holds it, so no database
185
+ * reports one, but the caller wrote every column of it and the payload still carries them.
186
+ */
170
187
  async insertMany(entity, payload) {
171
- return this.hooked(entity, 'Insert', payload, () => this.internalInsertMany(entity, payload));
188
+ const meta = getMeta(entity);
189
+ return this.hooked(entity, 'Insert', payload, async () => {
190
+ const reported = await this.internalInsertMany(entity, payload);
191
+ // Neither branch can be shown to be `WrittenId` for an unresolved `E`; `idOf` narrows likewise.
192
+ return meta.ids.length === 1 ? reported : payload.map((it) => idOf(meta, it));
193
+ });
172
194
  }
173
195
  async updateOneById(entity, id, payload, opts) {
174
196
  assertIdValue(entity, id);
@@ -198,10 +220,17 @@ export class AbstractQuerier {
198
220
  * was how an `@Id({ onInsert })` or an audit trail silently skipped this path.
199
221
  */
200
222
  async upsertOne(entity, conflictPaths, payload) {
201
- return this.hooked(entity, 'Upsert', [payload], () => this.internalUpsertOne(entity, conflictPaths, payload));
223
+ return this.hooked(entity, 'Upsert', [payload], async () => {
224
+ const { ids, changes, created } = await this.internalUpsertOne(entity, conflictPaths, payload);
225
+ const [id] = upsertIds(getMeta(entity), [payload], ids);
226
+ return { id, changes, created };
227
+ });
202
228
  }
203
229
  async upsertMany(entity, conflictPaths, payload) {
204
- return this.hooked(entity, 'Upsert', payload, () => this.internalUpsertMany(entity, conflictPaths, payload));
230
+ return this.hooked(entity, 'Upsert', payload, async () => {
231
+ const { ids, changes } = await this.internalUpsertMany(entity, conflictPaths, payload);
232
+ return { ids: upsertIds(getMeta(entity), payload, ids), changes };
233
+ });
205
234
  }
206
235
  async deleteOneById(entity, id, opts) {
207
236
  assertIdValue(entity, id);
@@ -1,5 +1,5 @@
1
1
  import type { AbstractDialect } from '../dialect/index.js';
2
- import type { EntityData, EntityId, ExtraOptions, FieldKey, IdValue, PoolRunOptions, Querier, QuerierPool, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpdateResult, RelationKey, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
2
+ import type { EntityData, EntityId, ExtraOptions, FieldKey, PoolRunOptions, Querier, QuerierPool, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpsertOneResult, QueryUpsertManyResult, RelationKey, TransactionOptions, Type, UpdatePayload, WrittenId } from '../type/index.js';
3
3
  /**
4
4
  * Base pool: dialect id and behavior come only from the `dialect` instance (see {@link QuerierPool}).
5
5
  */
@@ -37,14 +37,14 @@ export declare abstract class AbstractQuerierPool<Q extends Querier, D extends A
37
37
  exists<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: QueryOptions): Promise<boolean>;
38
38
  aggregate<E extends object, const G extends QueryGroupMap<E>, const A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
39
39
  estimatedCount<E extends object>(entity: Type<E>): Promise<number>;
40
- insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<IdValue<E> | undefined>;
41
- insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(IdValue<E> | undefined)[]>;
40
+ insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<WrittenId<E> | undefined>;
41
+ insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(WrittenId<E> | undefined)[]>;
42
42
  updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
43
43
  updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
44
- upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
45
- upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
46
- saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<EntityId<E> | undefined>;
47
- saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(EntityId<E> | undefined)[]>;
44
+ upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpsertOneResult<E>>;
45
+ upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpsertManyResult<E>>;
46
+ saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<WrittenId<E> | undefined>;
47
+ saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(WrittenId<E> | undefined)[]>;
48
48
  deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts?: QueryOptions): Promise<number>;
49
49
  deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
50
50
  restoreOneById<E extends object>(entity: Type<E>, id: EntityId<E>): Promise<number>;
@@ -365,9 +365,9 @@ export class AbstractSqlQuerier extends AbstractQuerier {
365
365
  }
366
366
  payload = clone(payload);
367
367
  const meta = getMeta(entity);
368
- // A composite key is supplied whole by the caller, so nothing is generated and nothing comes
369
- // back: the rows insert, and their ids are `undefined` for the reason MySQL's are below - there
370
- // is no id to report. `idOf` names such a row for the caller that wants one.
368
+ // What comes back is one column's value, so a composite reports nothing here and `insertMany`
369
+ // names those rows from the payload instead. `sole` is what keeps every id path off a key that
370
+ // is several columns.
371
371
  const [idKey] = meta.ids;
372
372
  const sole = meta.ids.length === 1;
373
373
  const idField = sole ? meta.fields[idKey] : undefined;
@@ -46,24 +46,24 @@ export declare class SqliteDialect extends AbstractSqlDialect {
46
46
  * element includes these keys" - `$elemMatch` always expands to per-field conditions.
47
47
  */
48
48
  protected readonly jsonContainmentIsPartial = false;
49
- /** `json_each` exposes a JSON boolean as `0`/`1` and a number as a number - already comparable. */
49
+ /** `JSON_EACH` exposes a JSON boolean as `0`/`1` and a number as a number - already comparable. */
50
50
  protected readonly jsonScalarElemKeepsType = true;
51
51
  /**
52
52
  * Each element is read back as JSON text through `->` at its own `fullkey`, so it compares
53
- * correctly whatever its type. `json_each`'s `value` column would not: it unquotes strings (`a`
53
+ * correctly whatever its type. `JSON_EACH`'s `value` column would not: it unquotes strings (`a`
54
54
  * vs `"a"`), flattens booleans to 0/1, and stringifies objects.
55
55
  */
56
56
  protected jsonAll(ctx: QueryContext, jsonField: string, value: unknown): string;
57
57
  protected jsonSize(ctx: QueryContext, jsonField: string, value: number | QuerySizeComparisonOps): string;
58
- /** `json_each` yields both scalar and object elements, so one form covers each case. */
58
+ /** `JSON_EACH` yields both scalar and object elements, so one form covers each case. */
59
59
  protected jsonElemFrom(jsonField: string, _fields: readonly string[], alias: string): string;
60
60
  protected jsonElemRef(alias: string, field?: string, asJson?: boolean): string;
61
61
  protected getJsonPathScalarExpr(escapedColumn: string, jsonPathStr: string): string;
62
62
  protected numericCast(expr: string): string;
63
63
  protected jsonCast(operand: string): string;
64
64
  /**
65
- * `json_replace` leaves an absent key (and a NULL column) untouched. Elements are read back
66
- * through `->` at their own `fullkey` so each keeps its JSON type - `json_each`'s `value` would
65
+ * `JSON_REPLACE` leaves an absent key (and a NULL column) untouched. Elements are read back
66
+ * through `->` at their own `fullkey` so each keeps its JSON type - `JSON_EACH`'s `value` would
67
67
  * flatten booleans to 0/1 and stringify objects.
68
68
  */
69
69
  protected jsonPullKey(ctx: QueryContext, expr: string, escapedCol: string, key: string, value: unknown): string;
@@ -71,8 +71,8 @@ export declare class SqliteDialect extends AbstractSqlDialect {
71
71
  /**
72
72
  * `[#]` appends, creating the array when the key is absent.
73
73
  *
74
- * @remarks `json_set` rather than `json_insert`: the two are equivalent here because `[#]` always
75
- * resolves past the end of the array, and Turso's engine implements `json_insert` as create-only,
74
+ * @remarks `JSON_SET` rather than `JSON_INSERT`: the two are equivalent here because `[#]` always
75
+ * resolves past the end of the array, and Turso's engine implements `JSON_INSERT` as create-only,
76
76
  * so it silently drops the element when the array already exists.
77
77
  */
78
78
  protected jsonPush(ctx: QueryContext, expr: string, push: Record<string, unknown>): string;
@@ -94,11 +94,11 @@ export class SqliteDialect extends AbstractSqlDialect {
94
94
  * element includes these keys" - `$elemMatch` always expands to per-field conditions.
95
95
  */
96
96
  jsonContainmentIsPartial = false;
97
- /** `json_each` exposes a JSON boolean as `0`/`1` and a number as a number - already comparable. */
97
+ /** `JSON_EACH` exposes a JSON boolean as `0`/`1` and a number as a number - already comparable. */
98
98
  jsonScalarElemKeepsType = true;
99
99
  /**
100
100
  * Each element is read back as JSON text through `->` at its own `fullkey`, so it compares
101
- * correctly whatever its type. `json_each`'s `value` column would not: it unquotes strings (`a`
101
+ * correctly whatever its type. `JSON_EACH`'s `value` column would not: it unquotes strings (`a`
102
102
  * vs `"a"`), flattens booleans to 0/1, and stringifies objects.
103
103
  */
104
104
  jsonAll(ctx, jsonField, value) {
@@ -108,52 +108,52 @@ export class SqliteDialect extends AbstractSqlDialect {
108
108
  return `(${conditions.join(' AND ')})`;
109
109
  }
110
110
  jsonSize(ctx, jsonField, value) {
111
- return this.buildFragment(ctx, (fragmentCtx) => this.buildSizeComparison(fragmentCtx, () => fragmentCtx.append(`json_array_length(${jsonField})`), value));
111
+ return this.buildFragment(ctx, (fragmentCtx) => this.buildSizeComparison(fragmentCtx, () => fragmentCtx.append(`JSON_ARRAY_LENGTH(${jsonField})`), value));
112
112
  }
113
- /** `json_each` yields both scalar and object elements, so one form covers each case. */
113
+ /** `JSON_EACH` yields both scalar and object elements, so one form covers each case. */
114
114
  jsonElemFrom(jsonField, _fields, alias) {
115
- return `json_each(${jsonField}) ${alias}`;
115
+ return `JSON_EACH(${jsonField}) ${alias}`;
116
116
  }
117
117
  jsonElemRef(alias, field, asJson = false) {
118
118
  if (field === undefined) {
119
119
  return `${alias}.value`;
120
120
  }
121
- return asJson ? `${alias}.value -> ${jsonPath(field)}` : `json_extract(${alias}.value, ${jsonPath(field)})`;
121
+ return asJson ? `${alias}.value -> ${jsonPath(field)}` : `JSON_EXTRACT(${alias}.value, ${jsonPath(field)})`;
122
122
  }
123
123
  getJsonPathScalarExpr(escapedColumn, jsonPathStr) {
124
- return `json_extract(${escapedColumn}, ${jsonPath(jsonPathStr)})`;
124
+ return `JSON_EXTRACT(${escapedColumn}, ${jsonPath(jsonPathStr)})`;
125
125
  }
126
126
  numericCast(expr) {
127
127
  return `CAST(${expr} AS REAL)`;
128
128
  }
129
129
  jsonCast(operand) {
130
- return `json(${operand})`;
130
+ return `JSON(${operand})`;
131
131
  }
132
132
  /**
133
- * `json_replace` leaves an absent key (and a NULL column) untouched. Elements are read back
134
- * through `->` at their own `fullkey` so each keeps its JSON type - `json_each`'s `value` would
133
+ * `JSON_REPLACE` leaves an absent key (and a NULL column) untouched. Elements are read back
134
+ * through `->` at their own `fullkey` so each keeps its JSON type - `JSON_EACH`'s `value` would
135
135
  * flatten booleans to 0/1 and stringify objects.
136
136
  */
137
137
  jsonPullKey(ctx, expr, escapedCol, key, value) {
138
138
  const path = jsonPath(key);
139
139
  const elem = `${escapedCol} -> ${JSON_PULL_ALIAS}.fullkey`;
140
- const kept = `SELECT json_group_array(json(${elem})) FROM json_each(${escapedCol}, ${path}) ${JSON_PULL_ALIAS} WHERE ${elem} <> ${this.jsonScalarParam(ctx, value)}`;
141
- return `json_replace(${expr}, ${path}, (${kept}))`;
140
+ const kept = `SELECT JSON_GROUP_ARRAY(JSON(${elem})) FROM JSON_EACH(${escapedCol}, ${path}) ${JSON_PULL_ALIAS} WHERE ${elem} <> ${this.jsonScalarParam(ctx, value)}`;
141
+ return `JSON_REPLACE(${expr}, ${path}, (${kept}))`;
142
142
  }
143
143
  jsonSet(ctx, expr, set, field) {
144
- return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), 'json_set', jsonSetTarget(expr, field, `'{}'`), set);
144
+ return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), 'JSON_SET', jsonSetTarget(expr, field, `'{}'`), set);
145
145
  }
146
146
  /**
147
147
  * `[#]` appends, creating the array when the key is absent.
148
148
  *
149
- * @remarks `json_set` rather than `json_insert`: the two are equivalent here because `[#]` always
150
- * resolves past the end of the array, and Turso's engine implements `json_insert` as create-only,
149
+ * @remarks `JSON_SET` rather than `JSON_INSERT`: the two are equivalent here because `[#]` always
150
+ * resolves past the end of the array, and Turso's engine implements `JSON_INSERT` as create-only,
151
151
  * so it silently drops the element when the array already exists.
152
152
  */
153
153
  jsonPush(ctx, expr, push) {
154
- return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), 'json_set', expr, push, '[#]');
154
+ return jsonAssignCall((value) => this.jsonScalarParam(ctx, value), 'JSON_SET', expr, push, '[#]');
155
155
  }
156
156
  jsonUnset(_ctx, expr, unset) {
157
- return jsonRemoveCall('json_remove', expr, unset);
157
+ return jsonRemoveCall('JSON_REMOVE', expr, unset);
158
158
  }
159
159
  }
@@ -211,25 +211,35 @@ export type IdKey<E> = ([NamedIdKey<E>] extends [never] ? FieldKey<E> : NamedIdK
211
211
  * filter"; `assertIdValue` is what rejects it.
212
212
  */
213
213
  export type IdValue<E> = E[IdKey<E>];
214
+ /** Every column of a key, which is how a composite row is named and what a `$where` reduces to. */
215
+ type IdMap<E> = {
216
+ [K in IdKey<E>]?: E[K];
217
+ };
214
218
  /**
215
219
  * How a row is addressed by its primary key: the value for a single key, an object carrying every
216
220
  * key for a composite - which is also the `$where` map it reduces to, so both spellings are one type.
217
221
  *
218
- * Distinct from {@link IdValue}, the *column's* value, which stays a scalar: an id column holds a
219
- * number, never an object. The two read alike on a single-key entity and are not the same thing -
220
- * `findOneById` takes an `EntityId`, while the inserts return `IdValue | undefined`, which is why a
221
- * composite insert reports no id rather than the map.
222
+ * A union rather than a choice between the two, because a caller holding one column's value has to
223
+ * reach the same parameter as one holding a map. {@link WrittenId} is where a shape is committed to.
222
224
  *
223
- * The keys stay optional, and completeness is checked at run time by `assertIdValue`. Requiring them
224
- * needs `IdKey` to be precise, which it is not: with no `id`/`_id`/`uuid` and no `idKey` brand it
225
- * falls back to every field, so `IdKey<Membership>` accepts `'role'` and requiring the map would
226
- * demand fields that are not keys. Making it conditional on the brand was tried and reverted - a
227
- * conditional type does not reduce for an unresolved `E`, which left `QueryWhere<E>` opaque and broke
228
- * assignability across the dialects.
225
+ * The keys stay optional, and completeness is checked at run time by `assertIdValue`: requiring them
226
+ * would refuse a `$where` map that names one row while it is still being built up.
229
227
  */
230
- export type EntityId<E> = IdValue<E> | {
231
- [K in IdKey<E>]?: E[K];
232
- };
228
+ export type EntityId<E> = IdValue<E> | IdMap<E>;
229
+ /** Whether `T` is a union of more than one member, which for a key means the entity's is composite. */
230
+ type IsUnion<T, U = T> = T extends unknown ? ([U] extends [T] ? false : true) : never;
231
+ /**
232
+ * The id a write reports: the column's value for a single key, the key map for a composite.
233
+ *
234
+ * Exact where {@link EntityId} is a union, and that is the difference between them - a by-id method
235
+ * *accepts* either spelling, a write *commits* to one.
236
+ *
237
+ * Falls back to `EntityId` where {@link NamedIdKey} names nothing, because there `IdKey` is every
238
+ * field and a composite cannot be told from a single key: reporting a map for a scalar would be a
239
+ * lie. `@Id` refuses an unnamed key, so a decorated entity is always exact, and `defineEntity` is
240
+ * the path where this fallback is still reachable.
241
+ */
242
+ export type WrittenId<E> = [NamedIdKey<E>] extends [never] ? EntityId<E> : IsUnion<IdKey<E>> extends true ? IdMap<E> : IdValue<E>;
233
243
  /**
234
244
  * Infers the values of the relations on an entity
235
245
  */
@@ -1,4 +1,4 @@
1
- import type { FieldKey, IdKey, JsonFieldPaths, RelationKey, RelationTarget } from './entity.js';
1
+ import type { FieldKey, IdKey, JsonFieldPaths, RelationKey, RelationTarget, WrittenId } from './entity.js';
2
2
  import type { QueryLock } from './queryLock.js';
3
3
  import type { QueryRaw } from './queryRaw.js';
4
4
  import type { QueryWhere } from './queryWhere.js';
@@ -407,7 +407,29 @@ export type QueryStringified = {
407
407
  [K in keyof Query<unknown>]?: string;
408
408
  };
409
409
  /**
410
- * result of an update operation.
410
+ * What upserting one row reports, against the entity rather than the driver.
411
+ *
412
+ * `created` is here and not on {@link QueryUpsertManyResult} because it is only ever knowable for a
413
+ * single statement: a batch's `affectedRows` is a weighted sum on the dialects that report one at
414
+ * all, and a batch of mixed shapes is several statements.
415
+ */
416
+ export type QueryUpsertOneResult<E> = {
417
+ readonly id?: WrittenId<E>;
418
+ readonly changes?: number;
419
+ /** Whether the record was created (`true`) or updated (`false`), where the dialect can tell. */
420
+ readonly created?: boolean;
421
+ };
422
+ /**
423
+ * What upserting many rows reports. `ids` is payload-aligned like an insert's, so it zips with the
424
+ * rows that were passed, and carries a composite key as the map naming it.
425
+ */
426
+ export type QueryUpsertManyResult<E> = {
427
+ readonly ids: (WrittenId<E> | undefined)[];
428
+ readonly changes?: number;
429
+ };
430
+ /**
431
+ * result of an update operation, as the driver reports it - which is what `run` hands back, where
432
+ * there is no entity to name the ids against. The `QueryUpsert*Result` pair is the entity-level shape.
411
433
  */
412
434
  export type QueryUpdateResult = {
413
435
  /**
@@ -415,11 +437,12 @@ export type QueryUpdateResult = {
415
437
  */
416
438
  changes?: number;
417
439
  /**
418
- * the inserted IDs, in insertion order. Exact on `'returning'` dialects; inferred from the
419
- * driver header on the others (see {@link InsertIdSource}), and empty when the header
420
- * reports no generated ID.
440
+ * the IDs the statement reported, in payload order, `undefined` where it reported none for that
441
+ * row - a MongoDB upsert names only the documents it inserted. Exact on `'returning'` dialects;
442
+ * inferred from the driver header on the others (see {@link InsertIdSource}), and absent
443
+ * altogether when the header reports nothing.
421
444
  */
422
- ids?: PrimaryKey[];
445
+ ids?: (PrimaryKey | undefined)[];
423
446
  /**
424
447
  * first inserted ID.
425
448
  */
@@ -1,5 +1,5 @@
1
- import type { EntityData, EntityId, FieldKey, IdValue, RelationKey, UpdatePayload } from './entity.js';
2
- import type { QueryConflictPaths, QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpdateResult } from './query.js';
1
+ import type { EntityData, EntityId, FieldKey, RelationKey, UpdatePayload, WrittenId } from './entity.js';
2
+ import type { QueryConflictPaths, QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpsertOneResult, QueryUpsertManyResult } from './query.js';
3
3
  import type { QueryAggMap, QueryAggregate, QueryAggregateResult, QueryGroupMap } from './queryAggregate.js';
4
4
  import type { Type } from './utility.js';
5
5
  import type { QuerierCountedResult, QuerierResult, QuerierTransport } from './wire.js';
@@ -116,7 +116,7 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
116
116
  * @param payload the data to be persisted
117
117
  * @return the ID
118
118
  */
119
- insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<IdValue<E> | undefined>;
119
+ insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<WrittenId<E> | undefined>;
120
120
  /**
121
121
  * Insert multiple records in a single statement (auto-chunked when the batch exceeds the
122
122
  * dialect's bind-parameter limit) and return their IDs in payload order.
@@ -125,12 +125,13 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
125
125
  * Database-generated IDs are exact on `'returning'` dialects (Postgres, MariaDB, MongoDB);
126
126
  * on MySQL/SQLite they are inferred from the driver header, which is only reliable for
127
127
  * auto-increment keys in batches without explicit IDs - otherwise those entries are
128
- * `undefined` rather than potentially wrong values.
128
+ * `undefined` rather than potentially wrong values. A composite key is never one the statement
129
+ * reports, so those rows are named from the payload instead.
129
130
  * @param entity the entity to persist on
130
131
  * @param payload the data to be persisted
131
132
  * @return the IDs
132
133
  */
133
- insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(IdValue<E> | undefined)[]>;
134
+ insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(WrittenId<E> | undefined)[]>;
134
135
  /**
135
136
  * Insert or update a record based on the conflict paths.
136
137
  * @param entity the entity to persist on
@@ -138,7 +139,7 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
138
139
  * @param payload the data to be persisted
139
140
  * @return operation metadata; see {@link QueryUpdateResult}
140
141
  */
141
- upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
142
+ upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpsertOneResult<E>>;
142
143
  /**
143
144
  * Insert or update many records based on the conflict paths.
144
145
  * @param entity the entity to persist on
@@ -146,21 +147,21 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
146
147
  * @param payload the data to be persisted
147
148
  * @return operation metadata; see {@link QueryUpdateResult}
148
149
  */
149
- upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
150
+ upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpsertManyResult<E>>;
150
151
  /**
151
152
  * insert or update a record.
152
153
  * @param entity the entity to persist on
153
154
  * @param payload the data to be persisted
154
155
  * @return the ID
155
156
  */
156
- saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<EntityId<E> | undefined>;
157
+ saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<WrittenId<E> | undefined>;
157
158
  /**
158
159
  * Insert or update records.
159
160
  * @param entity the entity to persist on
160
161
  * @param payload the data to be persisted
161
162
  * @return the IDs
162
163
  */
163
- saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(EntityId<E> | undefined)[]>;
164
+ saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(WrittenId<E> | undefined)[]>;
164
165
  /**
165
166
  * Restore soft-deleted records (sets the soft-delete field back to `null`). Throws if the
166
167
  * entity has no soft-delete field.
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, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
5
5
  "license": "MIT",
6
- "version": "0.49.0",
6
+ "version": "0.51.0",
7
7
  "type": "module",
8
8
  "engines": {
9
9
  "node": ">=24"