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