uql-orm 0.68.0 → 0.68.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/http/http.js +5 -8
- package/dist/browser/querier/httpQuerier.d.ts +9 -9
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +8 -7
- package/dist/d1/d1Querier.d.ts +5 -5
- package/dist/d1/d1QuerierPool.d.ts +3 -3
- package/dist/http/query.d.ts +10 -2
- package/dist/http/query.js +26 -1
- package/dist/type/entity.d.ts +4 -4
- package/dist/type/query.d.ts +29 -24
- package/dist/type/queryWhere.d.ts +20 -20
- package/dist/type/universalQuerier.d.ts +10 -10
- package/dist/type/wire.d.ts +9 -0
- package/package.json +2 -2
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { wireJson } from '../../http/query.js';
|
|
1
2
|
import { notify } from './bus.js';
|
|
2
3
|
/**
|
|
3
4
|
* Error thrown for non-2xx responses. Carries the HTTP status so callers can key
|
|
@@ -15,16 +16,13 @@ export function get(url, opts) {
|
|
|
15
16
|
return request(url, { method: 'get' }, opts);
|
|
16
17
|
}
|
|
17
18
|
export function post(url, payload, opts) {
|
|
18
|
-
|
|
19
|
-
return request(url, { method: 'post', body }, opts);
|
|
19
|
+
return request(url, { method: 'post', body: wireJson(payload) }, opts);
|
|
20
20
|
}
|
|
21
21
|
export function patch(url, payload, opts) {
|
|
22
|
-
|
|
23
|
-
return request(url, { method: 'patch', body }, opts);
|
|
22
|
+
return request(url, { method: 'patch', body: wireJson(payload) }, opts);
|
|
24
23
|
}
|
|
25
24
|
export function put(url, payload, opts) {
|
|
26
|
-
|
|
27
|
-
return request(url, { method: 'put', body }, opts);
|
|
25
|
+
return request(url, { method: 'put', body: wireJson(payload) }, opts);
|
|
28
26
|
}
|
|
29
27
|
export function remove(url, opts) {
|
|
30
28
|
return request(url, { method: 'delete' }, opts);
|
|
@@ -35,8 +33,7 @@ export function remove(url, opts) {
|
|
|
35
33
|
* (fetch only normalizes the classic verbs).
|
|
36
34
|
*/
|
|
37
35
|
export function query(url, payload, opts) {
|
|
38
|
-
|
|
39
|
-
return request(url, { method: 'QUERY', body }, opts);
|
|
36
|
+
return request(url, { method: 'QUERY', body: wireJson(payload) }, opts);
|
|
40
37
|
}
|
|
41
38
|
function request(url, init, opts) {
|
|
42
39
|
notify({ phase: 'start', opts });
|
|
@@ -24,24 +24,24 @@ export declare class HttpQuerier implements ClientQuerier {
|
|
|
24
24
|
readonly basePath: string;
|
|
25
25
|
readonly defaults: HttpQuerierDefaults;
|
|
26
26
|
constructor(basePath: string, defaults?: HttpQuerierDefaults);
|
|
27
|
-
findOneById<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, id: EntityId<E>, q?: QueryOneProjected<E, S, V, X, P, C>, opts?: RequestOptions): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C> | undefined>>;
|
|
28
|
-
findOne<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryOneProjected<E, S, V, X, P, C>, opts?: RequestOptions): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C> | undefined>>;
|
|
29
|
-
findMany<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C>, opts?: RequestFindOptions): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>>;
|
|
30
|
-
findManyAndCount<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C>, opts?: RequestFindOptions): Promise<RequestCountedSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>>;
|
|
31
|
-
count<E extends object>(entity: Type<E>, q?: QueryPage<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<number>>;
|
|
27
|
+
findOneById<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, id: EntityId<E>, q?: QueryOneProjected<E, S, V, X, P, C, never>, opts?: RequestOptions): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C> | undefined>>;
|
|
28
|
+
findOne<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryOneProjected<E, S, V, X, P, C, never>, opts?: RequestOptions): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C> | undefined>>;
|
|
29
|
+
findMany<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C, never>, opts?: RequestFindOptions): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>>;
|
|
30
|
+
findManyAndCount<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C, never>, opts?: RequestFindOptions): Promise<RequestCountedSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>>;
|
|
31
|
+
count<E extends object>(entity: Type<E>, q?: QueryPage<E, never>, opts?: RequestOptions): Promise<RequestSuccessResponse<number>>;
|
|
32
32
|
/** The `count` route capped at one row, so existence needs no endpoint of its own. */
|
|
33
|
-
exists<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: RequestOptions): Promise<{
|
|
33
|
+
exists<E extends object>(entity: Type<E>, q?: QueryFilter<E, never>, opts?: RequestOptions): Promise<{
|
|
34
34
|
count?: number;
|
|
35
35
|
data: boolean;
|
|
36
36
|
}>;
|
|
37
37
|
insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<WrittenId<E> | undefined>>;
|
|
38
38
|
insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions): Promise<RequestSuccessResponse<(WrittenId<E> | undefined)[]>>;
|
|
39
|
-
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdatePayload<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<number>>;
|
|
40
|
-
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<number>>;
|
|
39
|
+
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdatePayload<E, never>, opts?: RequestOptions): Promise<RequestSuccessResponse<number>>;
|
|
40
|
+
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E, never>, payload: UpdatePayload<E, never>, opts?: RequestOptions): Promise<RequestSuccessResponse<number>>;
|
|
41
41
|
saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions): Promise<RequestSuccessResponse<WrittenId<E> | undefined>>;
|
|
42
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
|
-
deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions & RequestOptions): Promise<RequestSuccessResponse<number>>;
|
|
44
|
+
deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E, never>, opts?: QueryOptions & RequestOptions): Promise<RequestSuccessResponse<number>>;
|
|
45
45
|
getBasePath<E>(entity: Type<E>): string;
|
|
46
46
|
protected read<T>(path: string, q: Record<string, unknown> | undefined, opts?: RequestOptions): Promise<RequestSuccessResponse<T>>;
|
|
47
47
|
protected buildOptions(opts?: RequestOptions): RequestOptions | undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var
|
|
1
|
+
var p=[];function y(e){for(let n of p)n(e)}function M(e){p.push(e);let n=p.length-1;return()=>{p.splice(n,1)}}var S=["$select","$populate","$exclude","$where","$sort"],E=["$count"],O=["$skip","$limit"],f=["$candidates"],T=["$distinct"],v=["$lock",...E,...f];var b=Symbol("rawValue"),L=Symbol("rawAlias");function c(e){return e?Object.keys(e):[]}function C(e){if(typeof e!=="object"||e===null)return!0;if(Array.isArray(e))return!1;let n=Object.getPrototypeOf(e);return n!==Object.prototype&&n!==null}var Y=new Set([...S,...E,...O,...f,...T,"hardDelete","count"]);function i(e){if(!e)return"";let n=new URLSearchParams;for(let r of c(e)){let o=e[r];if(o===void 0)continue;n.append(r,typeof o==="object"&&o!==null?u(o):String(o))}let t=n.toString();return t?`?${t}`:""}function u(e){return JSON.stringify(e,(n,t)=>{if(typeof t!=="object"||t===null)return t;if(b in t)throw TypeError("raw SQL cannot travel over HTTP: what leaves the browser is JSON");if(t instanceof ArrayBuffer||ArrayBuffer.isView(t))throw TypeError("binary cannot travel over HTTP: what leaves the browser is JSON");return t})}class K extends Error{status;constructor(e,n){super(e);this.status=n;this.name="RequestError"}}function R(e,n){return a(e,{method:"get"},n)}function h(e,n,t){return a(e,{method:"post",body:u(n)},t)}function x(e,n,t){return a(e,{method:"patch",body:u(n)},t)}function Q(e,n,t){return a(e,{method:"put",body:u(n)},t)}function w(e,n){return a(e,{method:"delete"},n)}function k(e,n,t){return a(e,{method:"QUERY",body:u(n)},t)}function a(e,n,t){if(y({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((o)=>{if(r.status>=200&&r.status<300)return y({phase:"success",opts:t}),o;let P=o,l={message:P?.error?.message??r.statusText,code:P?.error?.code??r.status};throw y({phase:"error",error:l,opts:t}),new K(l.message,l.code)})).finally(()=>{y({phase:"complete",opts:t})})}function U(e){let 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 s={findMany:{method:"GET",path:""},findOne:{method:"GET",path:"/one"},count:{method:"GET",path:"/count"},findOneById:{method:"GET",path:"/:id"},insertOne:{method:"POST",path:""},insertMany:{method:"POST",path:"/many"},saveOne:{method:"PUT",path:""},saveMany:{method:"PUT",path:"/many"},updateMany:{method:"PATCH",path:""},updateOneById:{method:"PATCH",path:"/:id"},deleteOneById:{method:"DELETE",path:"/:id"},deleteMany:{method:"DELETE",path:""}},F=c(s),ne=new Map(F.filter((e)=>s[e].method==="GET"&&s[e].path!=="/:id").map((e)=>[s[e].path,e]));function j(e){return U(e.name)}function g(e,n){if(!C(n))throw TypeError(`'${e.name}' was addressed by an id object, which the HTTP route cannot carry.`);return String(n)}class m{basePath;defaults;constructor(e,n={}){this.basePath=e;this.defaults=n}async findOneById(e,n,t,r){let o=this.getBasePath(e),d=i(t);return R(`${o}/${g(e,n)}${d}`,this.buildOptions(r))}findOne(e,n,t){return this.read(`${this.getBasePath(e)}${s.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)}${s.count.path}`,n,t)}async exists(e,n,t){let r=await this.count(e,{...n,$limit:1},t);return{...r,data:r.data>0}}insertOne(e,n,t){let r=this.getBasePath(e);return h(r,n,this.buildOptions(t))}insertMany(e,n,t){let r=this.getBasePath(e);return h(`${r}${s.insertMany.path}`,n,this.buildOptions(t))}async updateOneById(e,n,t,r){let o=this.getBasePath(e);return x(`${o}/${g(e,n)}`,t,this.buildOptions(r))}updateMany(e,n,t,r){let o=this.getBasePath(e),d=i(n);return x(`${o}${d}`,t,this.buildOptions(r))}saveOne(e,n,t){let r=this.getBasePath(e);return Q(r,n,this.buildOptions(t))}saveMany(e,n,t){let r=this.getBasePath(e);return Q(`${r}${s.saveMany.path}`,n,this.buildOptions(t))}async deleteOneById(e,n,t={}){let r=this.getBasePath(e),o=t.hardDelete?i({hardDelete:t.hardDelete}):"";return w(`${r}/${g(e,n)}${o}`,this.buildOptions(t))}deleteMany(e,n,t={}){let r=this.getBasePath(e),o=i(t.hardDelete?{...n,hardDelete:t.hardDelete}:n);return w(`${r}${o}`,this.buildOptions(t))}getBasePath(e){return`${this.basePath}/${(this.defaults.entityPath??j)(e)}`}read(e,n,t){if(this.defaults.readMethod==="QUERY")return k(e,n??{},this.buildOptions(t));return R(`${e}${i(n)}`,this.buildOptions(t))}buildOptions(e){if(!this.defaults.headers&&!e?.headers)return e;return{...e,headers:{...this.defaults.headers,...e?.headers}}}}var q={getQuerier:()=>new m("/api")};function de(e){q=e}function V(){return q}function pe(){return V().getQuerier()}export{m as HttpQuerier,K as RequestError,R as get,pe as getQuerier,V as getQuerierPool,y as notify,M as on,x as patch,h as post,Q as put,k as query,w as remove,de as setQuerierPool};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=489BD92466B1F42A64756E2164756E21
|
|
4
4
|
//# sourceMappingURL=uql-browser.min.js.map
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../src/browser/http/bus.ts", "../../src/
|
|
3
|
+
"sources": ["../../src/browser/http/bus.ts", "../../src/type/query.ts", "../../src/type/queryRaw.ts", "../../src/util/object.util.ts", "../../src/http/query.ts", "../../src/browser/http/http.ts", "../../src/util/string.util.ts", "../../src/http/contract.ts", "../../src/browser/querier/httpQuerier.ts", "../../src/browser/options.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import type { RequestCallback, RequestNotification } from '../type/index.js';\n\nconst subscriptors: RequestCallback[] = [];\n\nexport function notify(notification: RequestNotification): void {\n for (const subscriptor of subscriptors) {\n subscriptor(notification);\n }\n}\n\nexport function on(cb: RequestCallback): () => void {\n subscriptors.push(cb);\n const index = subscriptors.length - 1;\n return (): void => {\n subscriptors.splice(index, 1);\n };\n}\n",
|
|
6
|
-
"import type { RequestErrorResponse } from '../../http/contract.js';\nimport type { RequestSuccessResponse } from '../../type/index.js';\nimport type { RequestOptions } from '../type/index.js';\nimport { notify } from './bus.js';\n\n/**\n * Error thrown for non-2xx responses. Carries the HTTP status so callers can key\n * behavior on it (401 redirects, 402 payment flows, error-boundary routing).\n */\nexport class RequestError extends Error {\n constructor(\n message: string,\n readonly status: number,\n ) {\n super(message);\n this.name = 'RequestError';\n }\n}\n\nexport function get<T>(url: string, opts?: RequestOptions) {\n return request<T>(url, { method: 'get' }, opts);\n}\n\nexport function post<T>(url: string, payload: unknown, opts?: RequestOptions) {\n const body = JSON.stringify(payload);\n return request<T>(url, { method: 'post', body }, opts);\n}\n\nexport function patch<T>(url: string, payload: unknown, opts?: RequestOptions) {\n const body = JSON.stringify(payload);\n return request<T>(url, { method: 'patch', body }, opts);\n}\n\nexport function put<T>(url: string, payload: unknown, opts?: RequestOptions) {\n const body = JSON.stringify(payload);\n return request<T>(url, { method: 'put', body }, opts);\n}\n\nexport function remove<T>(url: string, opts?: RequestOptions) {\n return request<T>(url, { method: 'delete' }, opts);\n}\n\n/**\n * HTTP QUERY (RFC 10008): a safe, idempotent read whose JSON query travels in the\n * request body, avoiding URL-length limits. Method name must stay uppercase\n * (fetch only normalizes the classic verbs).\n */\nexport function query<T>(url: string, payload: unknown, opts?: RequestOptions) {\n const body = JSON.stringify(payload);\n return request<T>(url, { method: 'QUERY', body }, opts);\n}\n\nfunction request<T>(url: string, init: RequestInit, opts?: RequestOptions) {\n notify({ phase: 'start', opts });\n\n init.headers = {\n accept: 'application/json',\n 'content-type': 'application/json',\n ...opts?.headers,\n };\n if (opts?.signal) {\n init.signal = opts.signal;\n }\n\n return fetch(url, init)\n .then((rawResp) =>\n rawResp.json().then((resp: unknown) => {\n const isSuccess = rawResp.status >= 200 && rawResp.status < 300;\n if (isSuccess) {\n notify({ phase: 'success', opts });\n return resp as RequestSuccessResponse<T>;\n }\n const errorResp = resp as Partial<RequestErrorResponse> | undefined;\n const error = {\n message: errorResp?.error?.message ?? rawResp.statusText,\n code: errorResp?.error?.code ?? rawResp.status,\n };\n notify({ phase: 'error', error, opts });\n throw new RequestError(error.message, error.code);\n }),\n )\n .finally(() => {\n notify({ phase: 'complete', opts });\n });\n}\n",
|
|
6
|
+
"import type { FieldKey, 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\n/**\n * Field selection - `{ name: true }` whitelists fields; relations go in `$populate`. Declared over\n * `F extends keyof E`, like every map keyed by an entity's members, so each key stays linked to its\n * property and an editor rename reaches it. `F` is also how a projection passes its captured key set.\n */\nexport type QuerySelect<E, F extends keyof E = FieldKey<E>, V = BooleanLike> = {\n [K in F]?: V;\n};\n\n/**\n * Accepted `$select` value: a field map, or raw SQL projections built with `raw()`\n * (e.g. ``[raw`*`, raw`LOG10(points)`.as('score')]``). The raw form is SQL-only.\n */\nexport type QuerySelectValue<E, Raw = QueryRaw> = QuerySelect<E> | readonly Raw[];\n\n/**\n * Fields to exclude from the query result - `{ name: true }` blacklists fields.\n * Mutually exclusive with positive field selections in `$select`.\n */\nexport type QueryExclude<E> = QuerySelect<E>;\n\n/**\n * relation population map.\n */\nexport type QueryPopulate<E, Raw = QueryRaw, R extends keyof E = RelationKey<E>> = {\n [K in R]?: BooleanLike | QueryPopulateRelationOptions<E[K], Raw>;\n};\n\n/**\n * The key a read carries its relation tallies under. One spelling for the type and the runtime that\n * fills it: they sit in different modules, so a drift would type-check and answer `undefined`.\n */\nexport const COUNT_RESULT_KEY = '_count';\n\n/**\n * How many rows each named relation holds per parent, `true` for all of them or a filter to narrow\n * which ones count: a correlated count in the read's own statement, so no related row is loaded. Comes\n * back under `_count`, which keeps it clear of a relation of the same name `$populate` filled.\n */\nexport type QueryCount<E, Raw = QueryRaw, R extends keyof E = ToManyRelationKey<E>> = {\n [K in R]?: BooleanLike | QueryFilter<RelationTarget<E[K]>, Raw>;\n};\n\n/**\n * query conflict paths - subset of field keys used to detect upsert conflicts.\n */\nexport type QueryConflictPaths<E> = QuerySelect<E, FieldKey<E>, true>;\n\n/**\n * Options to populate a relation declared as `V`, by its cardinality.\n */\nexport type QueryPopulateRelationOptions<V, Raw = QueryRaw> =\n IsMany<V> extends true\n ? RelationQuery<RelationTarget<V>, Raw>\n : QueryUnique<RelationTarget<V>, Raw> & { $required?: boolean };\n\n/**\n * The per-request context parameterized filters read, set with `withContext(ctx, cb)`. An interface,\n * so its keys can be typed once: `declare module 'uql-orm' { interface UqlContext { tenantId: number } }`.\n */\nexport interface UqlContext {\n [key: string]: unknown;\n}\n\n/**\n * A filter's `$where` fragment: a plain fragment, or a function of the ambient {@link UqlContext}.\n * Return `undefined` when the condition can't resolve (see {@link FilterOptions.onMissing}).\n */\nexport type FilterWhere<E> = QueryWhere<E> | ((context: UqlContext | undefined) => QueryWhere<E> | undefined);\n\n/**\n * What to do when a filter's condition returns `undefined`. `skip` omits it (convenience filters);\n * `throw` fails closed (the default for `security` filters).\n */\nexport type FilterOnMissing = 'skip' | 'throw';\n\n/**\n * Authoring shape for `@Entity({ filters })` / `@Filter` / `defineFilter`.\n */\nexport type FilterOptions<E = unknown> = {\n readonly where: FilterWhere<E>;\n /** Applied to every query unless bypassed via `QueryOptions.filters`. Defaults to `true`. */\n readonly default?: boolean;\n} & (\n | {\n readonly security?: false;\n /** What to do when {@link FilterOptions.where} returns `undefined`. Defaults to `skip`. */\n readonly onMissing?: FilterOnMissing;\n }\n | {\n /**\n * Row-level-security filter: always applied (ignores `QueryOptions.filters` bypass) and\n * AND-merged so a client `$where` on the same field can't override it. It fails closed.\n */\n readonly security: true;\n readonly onMissing?: 'throw';\n }\n);\n\n/**\n * direction for the sort.\n */\nexport type QuerySortDirection = -1 | 1 | 'asc' | 'desc';\n\n/**\n * Accepted value for a field in `$sort` - either a direction or a vector similarity search.\n */\nexport type QuerySortValue = QuerySortDirection | QueryVectorSearch;\n\n/**\n * 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 * Ordering parents by how many rows a to-many relation holds - \"the ten users with the most posts\".\n * The tally is computed per parent as a correlated count, never by loading the rows.\n */\nexport type QuerySortByCount = {\n $count: QuerySortDirection;\n};\n\n/**\n * A sort by fields, JSON paths, a to-one relation's fields, a to-many's `$count`, or a vector distance,\n * which `Vector` confines to the queried entity. One mapped type over the key sets: an intersection is\n * checked once per member, which made this the costliest type to check.\n */\nexport type QuerySortMap<E, Vector extends boolean = true, K extends keyof E = FieldKey<E> | RelationKey<E>> = {\n [P in K]?: P extends RelationKey<E>\n ? // A to-many has no single value to order by, so what it offers instead is its own size.\n IsMany<E[P]> extends true\n ? QuerySortByCount\n : QuerySortMap<RelationTarget<E[P]>, false>\n : Vector extends true\n ? NonNullable<E[P]> extends readonly number[]\n ? QuerySortValue\n : QuerySortDirection\n : QuerySortDirection;\n} & ([JsonFieldPaths<E>] extends [never] ? unknown : { [P in JsonFieldPaths<E>]?: QuerySortDirection });\n\n/**\n * pager options.\n */\nexport type QueryPager = {\n /**\n * Index from where start the search\n */\n $skip?: number;\n\n /**\n * Max number of records to retrieve\n */\n $limit?: number;\n};\n\n/**\n * Which rows a statement addresses.\n */\nexport type QueryFilter<E, Raw = QueryRaw> = {\n /**\n * filtering options.\n */\n $where?: QueryWhere<E, Raw>;\n};\n\n/**\n * A filter plus the page `count` takes. No `$sort`: ordering picks *which* rows a page holds, never\n * how many, so a count that accepted one would promise an influence it cannot have.\n */\nexport type QueryPage<E, Raw = QueryRaw> = QueryFilter<E, Raw> & QueryPager;\n\n/**\n * A filter plus the ordering and page `updateMany`/`deleteMany` take. Both settle the\n * rows they address with a SELECT first, so the page is portable rather than MySQL-only, and a\n * vector `$sort` is as valid here as on a read: it ranks the settle query's rows, which has the\n * projection list to hold the distance. `$lock` stays off these, declared on {@link Query} instead.\n */\nexport type QuerySearch<E, Raw = QueryRaw> = QueryPage<E, Raw> & {\n /**\n * sorting options.\n */\n $sort?: QuerySortMap<E>;\n};\n\n/**\n * query options.\n */\nexport type Query<E, Raw = QueryRaw> = {\n /**\n * field selection - `{ name: true }` whitelists fields, or raw SQL projections\n * (``[raw`LOG10(points)`.as('score')]``, SQL dialects only - MongoDB rejects the raw-array form).\n * Mutually exclusive with `$exclude`.\n */\n $select?: QuerySelectValue<E, Raw>;\n\n /**\n * relation population options.\n */\n $populate?: QueryPopulate<E, Raw>;\n\n /**\n * how many rows each named relation holds, under `_count` on every row. See {@link QueryCount}.\n */\n $count?: QueryCount<E, Raw>;\n\n /**\n * field exclusion - `{ name: true }` blacklists fields. Mutually exclusive with positive `$select`.\n * Keys a relation is assembled from (a joined row's primary key, a to-many's foreign key) are kept\n * regardless, since subtracting them would leave the relation unfilled.\n */\n $exclude?: QueryExclude<E>;\n\n /**\n * sorting options, vector similarity search included: a SELECT is the one statement with a\n * projection list to hold the distance such a search computes.\n */\n $sort?: QuerySortMap<E>;\n\n /**\n * whether to return only distinct rows.\n */\n $distinct?: boolean;\n\n /**\n * Lock the rows this query returns, `SELECT ... FOR UPDATE`, inside an open transaction: outside one\n * it is refused, since the lock would drop before the rows are used. SQL only, and not the SQLite family.\n */\n $lock?: QueryLock;\n\n /**\n * How many candidates an ANN index explores before ranking a vector search, in that index's own units\n * (`hnsw.ef_search`, `numCandidates`...); ignored where the search is exact. Postgres needs a transaction.\n */\n $candidates?: number;\n\n // `$where`, `$skip` and `$limit` are declared here rather than intersected in from\n // {@link QueryFilter} and {@link QueryPager}: an assignability check against an intersection is\n // repeated per constituent, and every query in a consuming codebase pays that. The two shapes are\n // pinned together in `queryStatementClauses.test-d.ts` so the copies cannot drift.\n\n /**\n * filtering options.\n */\n $where?: QueryWhere<E, Raw>;\n\n /**\n * Index from where start the search\n */\n $skip?: number;\n\n /**\n * Max number of records to retrieve\n */\n $limit?: number;\n};\n\n/**\n * A {@link Query} as it travels as JSON, which a `raw` SQL fragment cannot: what the browser client takes,\n * and what an RPC contract (tRPC, oRPC, TanStack Start) declares as its input.\n */\nexport type WireQuery<E> = Query<E, never>;\n\n/**\n * `Query`'s clauses grouped by the shape of their value, for the wire parser and the relation query\n * check alike; `satisfies` keeps them in step with `Query`.\n */\nexport const QUERY_OBJECT_CLAUSES = [\n '$select',\n '$populate',\n '$exclude',\n '$where',\n '$sort',\n] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Object clauses only the statement itself takes: a populated relation's rows keep their declared type,\n * so a `$count` inside one would have no `_count` to land in.\n */\nexport const QUERY_ROOT_OBJECT_CLAUSES = ['$count'] as const satisfies readonly (keyof Query<unknown>)[];\n\nexport const QUERY_NUMBER_CLAUSES = ['$skip', '$limit'] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Number clauses only the statement itself takes - the numeric mirror of {@link QUERY_ROOT_OBJECT_CLAUSES}.\n * `$candidates` tunes the index behind a vector search, and a vector search only ever ranks the rows\n * the statement returns, so a relation's own query has nothing to tune.\n */\nexport const QUERY_ROOT_NUMBER_CLAUSES = ['$candidates'] as const satisfies readonly (keyof Query<unknown>)[];\n\nexport const QUERY_BOOLEAN_CLAUSES = ['$distinct'] as const satisfies readonly (keyof Query<unknown>)[];\n\n/** The clauses that describe the statement, which a populated relation's own query refuses by name. */\nexport const QUERY_STATEMENT_CLAUSES = [\n '$lock',\n ...QUERY_ROOT_OBJECT_CLAUSES,\n ...QUERY_ROOT_NUMBER_CLAUSES,\n] as const satisfies readonly (keyof Query<unknown>)[];\n\ntype RelationClause = (\n | typeof QUERY_OBJECT_CLAUSES\n | typeof QUERY_NUMBER_CLAUSES\n | typeof QUERY_BOOLEAN_CLAUSES\n)[number];\n\n/**\n * A populated relation's own query: the clause groups its runtime check accepts, so the two cannot\n * drift, and a clause added to {@link Query} stays off it until it joins one of them.\n */\nexport type RelationQuery<E = object, Raw = QueryRaw> = Pick<Query<E, Raw>, RelationClause> & {\n $required?: boolean;\n};\n\n/**\n * options to get a single record.\n */\nexport type QueryOne<E, Raw = QueryRaw> = Except<Query<E, Raw>, '$limit'>;\n\n/**\n * options to get an unique record.\n */\nexport type QueryUnique<E, Raw = QueryRaw> = Pick<QueryOne<E, Raw>, '$select' | '$exclude' | '$populate' | '$where'>;\n\n/**\n * The clauses that shape a row, captured as key sets rather than maps: a naked type parameter skips\n * excess-property checks, while a key set fails its own constraint on a typo.\n * @internal\n */\ntype QueryProjection<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n Raw = QueryRaw,\n> = {\n $select?: QuerySelect<E, S, V> | readonly Raw[];\n $exclude?: QuerySelect<E, X, V>;\n $populate?: QueryPopulate<E, Raw, P>;\n // Narrowing the captured names to the to-many ones leaves a to-one relation no key here at all,\n // so counting one is an excess property rather than a value to check.\n $count?: QueryCount<E, Raw, C & ToManyRelationKey<E>>;\n};\n\n/**\n * A {@link Query} whose projection is captured, so {@link QueryFindResult} can shape the row.\n */\nexport type QueryProjected<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E> = never,\n Raw = QueryRaw,\n> = Query<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;\n\n/**\n * A {@link QueryOne} whose projection is captured, so {@link QueryFindResult} can shape the row.\n */\nexport type QueryOneProjected<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E> = never,\n Raw = QueryRaw,\n> = QueryOne<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;\n\n/**\n * The keys a query comes back with, as the runtime projects them: a positive `$select`'s, or every\n * field minus what `$select` or `$exclude` subtracts, plus the populated relations.\n * @internal\n */\ntype ProjectedKeys<E, S, V, X, P> =\n | ([V] extends [false | 0] ? Exclude<FieldKey<E>, S> : [S] extends [never] ? Exclude<FieldKey<E>, X> : S)\n | P;\n\n/**\n * Whether every entry of the captured map says the same thing: all selected, or all subtracted.\n * @internal\n */\ntype IsUniform<V> = [V] extends [true | 1] ? true : [V] extends [false | 0] ? true : false;\n\n/**\n * A find's row: the entity narrowed to what the query projected and populated, so reading anything\n * else does not compile. The entity itself where the projection is raw, absent or not uniform.\n * @example `QueryFindResult<User, 'id' | 'name'>`\n */\nexport type QueryFindResult<\n E,\n S extends FieldKey<E> = never,\n // A whitelist by default, so the hand-written form reads `QueryFindResult<User, 'id' | 'name'>`.\n V = true,\n X extends FieldKey<E> = never,\n P extends RelationKey<E> = never,\n C extends RelationKey<E> = never,\n> = QueryProjectedRow<E, S, V, X, P, C> & CountedRelations<C>;\n\n/**\n * The `_count` a query asked for, or an inert intersection member when it asked for none - so a read\n * without `$count` keeps exactly the row type it had.\n */\ntype CountedRelations<C extends PropertyKey> = [C] extends [never]\n ? unknown\n : { [K in typeof COUNT_RESULT_KEY]: { [R in C]: number } };\n\n/** @internal */\ntype QueryProjectedRow<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n> = [S | X] extends [never]\n ? E\n : IsUniform<V> extends true\n ? [PopulatedToMany<E, P>] extends [never]\n ? // `Pick`, not a key remap: an entity keyed by an index signature - a content type defined at\n // runtime - has `string` for its keys, and a remap keeps no literal one, so every projection\n // over one came back as `{}`.\n Pick<E, ProjectedKeys<E, S, V, X, P> & keyof E>\n : // A populated to-many is always a list, empty where the parent has no children, so it maps\n // and counts without a guard. Only that promotion needs a second member, and only a query\n // that populates one pays for it; every other key keeps the modifier the entity declared,\n // a to-one relation included, since a join that finds no row leaves it absent.\n Pick<E, Exclude<ProjectedKeys<E, S, V, X, P>, PopulatedToMany<E, P>> & keyof E> & {\n [K in PopulatedToMany<E, P>]-?: NonNullable<E[K]>;\n }\n : E;\n\n/** The to-many relations a query populated, which come back as lists rather than as optional ones. */\ntype PopulatedToMany<E, P> = Extract<P, ToManyRelationKey<E>>;\n\n/**\n * stringified query.\n */\nexport type QueryStringified = {\n [K in keyof Query<unknown>]?: string;\n};\n\n/** What upserting one row reports. `created` is only knowable for a single statement, so a batch has none. */\nexport type QueryUpsertOneResult<E> = {\n readonly id?: WrittenId<E>;\n readonly changes?: number;\n /** Whether the record was created (`true`) or updated (`false`), where the dialect can tell. */\n readonly created?: boolean;\n};\n\n/**\n * What upserting many rows reports. `ids` is payload-aligned like an insert's, so it zips with the\n * rows that were passed, and carries a composite key as the map naming it.\n */\nexport type QueryUpsertManyResult<E> = {\n readonly ids: (WrittenId<E> | undefined)[];\n readonly changes?: number;\n};\n\n/**\n * result of an update operation, as the driver reports it - which is what `run` hands back, where\n * there is no entity to name the ids against. The `QueryUpsert*Result` pair is the entity-level shape.\n */\nexport type QueryUpdateResult = {\n /**\n * number of affected records.\n */\n changes?: number;\n /**\n * the IDs the statement reported, in payload order, `undefined` where it reported none for that\n * row - a MongoDB upsert names only the documents it inserted. Exact on `'returning'` dialects;\n * inferred from the driver header on the others (see {@link InsertIdSource}), and absent\n * altogether when the header reports nothing.\n */\n ids?: (PrimaryKey | undefined)[];\n /**\n * first inserted ID.\n */\n firstId?: PrimaryKey;\n /**\n * whether the record was created (`true`) or updated (`false`).\n * `undefined` when the dialect cannot determine this (e.g. SQLite).\n */\n created?: boolean;\n};\n",
|
|
7
|
+
"import type { QueryContext, SqlQueryDialect } from './dialect.js';\nimport type { Type } from './utility.js';\n\n/** What a `raw` callback receives. See {@link QueryRawFn}. */\nexport type QueryRawRenderOptions = {\n /** The dialect rendering the SQL. */\n dialect: SqlQueryDialect;\n /** The alias of the table in scope, unescaped; empty where there is none. */\n prefix: string;\n /** {@link prefix} escaped, with its trailing dot. */\n escapedPrefix: string;\n /** The query context the SQL is written into. */\n ctx: QueryContext;\n /**\n * The entity being rendered, which a ref read off a definition's map resolves its column against: a\n * computed field's own, or the one whose schema is built. Absent where a statement renders SQL.\n */\n entity?: Type<unknown>;\n};\n\n/** {@link QueryRawRenderOptions} as the callers along the way fill them in, every one still optional. */\nexport type QueryRawFnOptions = Partial<QueryRawRenderOptions>;\n\n/**\n * A `raw` callback: write into `ctx`, or return a string or number to have it appended. Anything else\n * it returns is ignored, which is why the return type is `unknown` rather than `void | Scalar` - the\n * latter rejected `({ ctx }) => ctx.append(...)`, the form every computed field is written in, because\n * TypeScript's \"returning a value where void is expected\" allowance does not apply to a union.\n */\nexport type QueryRawFn = (opts: QueryRawRenderOptions) => unknown;\n\nexport const RAW_VALUE: unique symbol = Symbol('rawValue');\nexport const RAW_ALIAS: unique symbol = Symbol('rawAlias');\n\nexport class QueryRaw {\n readonly [RAW_VALUE]: QueryRawFn;\n readonly [RAW_ALIAS]?: string;\n\n constructor(value: QueryRawFn, alias?: string) {\n this[RAW_VALUE] = value;\n this[RAW_ALIAS] = alias;\n }\n\n /** The same expression under an alias, for a `$select` projection. */\n as(alias: string): QueryRaw {\n return new QueryRaw(this[RAW_VALUE], alias);\n }\n\n /** Writes the expression into `opts.ctx`. The alias is the projection's to write, after the term. */\n render(opts: QueryRawRenderOptions): void {\n const emitted = this[RAW_VALUE](opts);\n if (typeof emitted === 'string' || (typeof emitted === 'number' && !Number.isNaN(emitted))) {\n opts.ctx.append(String(emitted));\n }\n }\n}\n\n/**\n * A field of an entity as SQL, read off `refs(Entity)` or a definition's refs: interpolated into `raw`, it\n * renders as the field's column. Its `key` is how an index tells a column from an expression.\n */\nexport class ColumnRef<K extends string = string> extends QueryRaw {\n constructor(\n readonly key: K,\n value: QueryRawFn,\n ) {\n super(value);\n }\n}\n",
|
|
7
8
|
"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 `key` names an operator (`$eq`, `$push`...) rather than a field. */\nexport function isOperatorKey(key: string): boolean {\n return key.startsWith('$');\n}\n\n/** Whether `value` is a non-empty object with an operator key (`$eq`, `$push`...): the one test every dialect classifies with. */\nexport function isOperatorObject(value: unknown): value is Record<string, unknown> {\n return isRecord(value) && someKey(value, isOperatorKey);\n}\n\n/** Whether `value` is an object that is not an array, whose keys can be read. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nexport function getKeys<T extends object>(obj: T | null | undefined): (keyof T & string)[] {\n return obj ? (Object.keys(obj) as (keyof T & string)[]) : [];\n}\n\n/** The entries of `record` holding a value: a key declared but left `undefined` is no entry at all. */\nexport function definedEntries<K extends string, V>(record: Partial<Record<K, V>>): [K, V][] {\n return (Object.entries(record) as [K, V | undefined][]).filter((entry): entry is [K, V] => entry[1] !== undefined);\n}\n\n/**\n * The entity's own name, declared or its class's. `meta.name` holds only what the author wrote, so\n * the fallback is what an entity that named no table is called - which is why the sites spelling this\n * out reached for three different fallbacks, `?? ''` among them, and named nothing at all.\n */\nexport function entityName<E>(meta: EntityMeta<E>): string {\n return meta.name ?? meta.entity.name;\n}\n\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\n/** Whether `value` is a plain object naming columns, the one shape a `$where` takes. */\nexport function isWhereMap(value: unknown): value is Record<string, unknown> {\n return !Array.isArray(value) && !isScalarId(value);\n}\n",
|
|
9
|
+
"import type { QueryOptions, WireQuery } from '../type/index.js';\n// the clause lists themselves, not the barrel: this module is in the browser bundle's graph\nimport {\n QUERY_BOOLEAN_CLAUSES,\n QUERY_NUMBER_CLAUSES,\n QUERY_OBJECT_CLAUSES,\n QUERY_ROOT_NUMBER_CLAUSES,\n QUERY_ROOT_OBJECT_CLAUSES,\n} from '../type/query.js';\n// the brand alone, not the class: importing `QueryRaw` for an `instanceof` kept it, and `ColumnRef`\n// with it, in the browser bundle, which is on a size budget\nimport { RAW_VALUE } from '../type/queryRaw.js';\n// the specific util module, not the barrel, so the browser bundle does not pull in entity metadata\nimport { getKeys, isWhereMap } from '../util/object.util.js';\n\n/**\n * Keys accepted from the wire - query structure ({@link Query}) plus the `hardDelete`/`count` scalar\n * flags. Anything else (e.g. `filters`, `context`, `$entity`) is dropped so a remote client can't\n * bypass a security filter or inject ambient context - those are server-only. The `satisfies` ties\n * every entry to a real query/option key, so a typo or a renamed option fails to compile.\n */\nconst ALLOWED_QUERY_KEYS = new Set<string>([\n ...QUERY_OBJECT_CLAUSES,\n ...QUERY_ROOT_OBJECT_CLAUSES,\n ...QUERY_NUMBER_CLAUSES,\n ...QUERY_ROOT_NUMBER_CLAUSES,\n ...QUERY_BOOLEAN_CLAUSES,\n 'hardDelete',\n 'count',\n] satisfies (keyof WireQuery<unknown> | keyof Pick<QueryOptions, 'hardDelete'> | 'count')[]);\n\n/**\n * Keys that mean something locally but that this transport can never honor, so they are rejected\n * rather than dropped like the rest. Each request runs on its own auto-committing connection, so a\n * row lock taken here is released before the response is written: honoring `$lock` is impossible,\n * and ignoring it would hand the caller a read they believe is serialized and is not.\n */\nconst REJECTED_QUERY_KEYS = new Set<string>(['$lock'] satisfies (keyof WireQuery<unknown>)[]);\n\n/**\n * Parse raw query-string entries (with JSON-stringified values) into a UQL query object.\n * Symmetric counterpart of {@link stringifyQuery}. Only {@link ALLOWED_QUERY_KEYS} are honored.\n */\nexport function parseQueryParams(params: Record<string, unknown> = {}): WireQuery<unknown> {\n const query: Record<string, unknown> = {};\n for (const key of getKeys(params)) {\n if (REJECTED_QUERY_KEYS.has(key)) {\n throw Object.assign(new TypeError(`'${key}' is not supported over HTTP`), { status: 400 });\n }\n if (ALLOWED_QUERY_KEYS.has(key)) {\n query[key] = params[key];\n }\n }\n\n for (const key of [...QUERY_OBJECT_CLAUSES, ...QUERY_ROOT_OBJECT_CLAUSES]) {\n const value = query[key];\n if (typeof value === 'string') {\n try {\n query[key] = JSON.parse(value);\n } catch {\n throw Object.assign(new SyntaxError(`invalid JSON in '${key}'`), { status: 400 });\n }\n }\n }\n\n query['$where'] ??= {};\n if (!isWhereMap(query['$where'])) {\n throw Object.assign(new TypeError(\"'$where' must be a JSON object\"), { status: 400 });\n }\n\n // A query string carries every value as text, so what decodes a clause is the shape its group\n // declares. `'false'` is the reason the boolean pass exists rather than the raw value being taken:\n // it is a non-empty string, so a `$distinct=false` would otherwise read as asking for one.\n for (const key of [...QUERY_NUMBER_CLAUSES, ...QUERY_ROOT_NUMBER_CLAUSES]) {\n if (query[key] !== undefined) {\n query[key] = Number(query[key]);\n }\n }\n for (const key of QUERY_BOOLEAN_CLAUSES) {\n if (query[key] !== undefined) {\n query[key] = query[key] === true || query[key] === 'true';\n }\n }\n\n return query as WireQuery<unknown>;\n}\n\n/**\n * Serialize a UQL query object into a percent-encoded query string where object values\n * are JSON-stringified. Symmetric counterpart of {@link parseQueryParams}.\n */\nexport function stringifyQuery(query?: Record<string, unknown>): string {\n if (!query) {\n return '';\n }\n const params = new URLSearchParams();\n for (const key of getKeys(query)) {\n const value = query[key];\n if (value === undefined) {\n continue;\n }\n params.append(key, typeof value === 'object' && value !== null ? wireJson(value) : String(value));\n }\n const qs = params.toString();\n return qs ? `?${qs}` : '';\n}\n\n/**\n * What leaves the browser, as JSON, refusing what JSON keeps nothing of rather than letting the server\n * build a statement around the remains. A `raw` fragment renders SQL against a dialect the client does not\n * have and arrives as `{}`; binary arrives as an object keyed by index. A `Date` is not among them - it\n * serializes to ISO 8601, which is what a date column reads. This is what a cast, or a JavaScript caller,\n * hits where the client's types already refuse a fragment.\n */\nexport function wireJson(value: unknown): string {\n return JSON.stringify(value, (_key: string, held: unknown) => {\n if (typeof held !== 'object' || held === null) {\n return held;\n }\n if (RAW_VALUE in held) {\n throw new TypeError('raw SQL cannot travel over HTTP: what leaves the browser is JSON');\n }\n // A blob is a field value, so no type parameter reaches it: this is the only place it is caught.\n if (held instanceof ArrayBuffer || ArrayBuffer.isView(held)) {\n throw new TypeError('binary cannot travel over HTTP: what leaves the browser is JSON');\n }\n return held;\n });\n}\n",
|
|
10
|
+
"import type { RequestErrorResponse } from '../../http/contract.js';\nimport { wireJson } from '../../http/query.js';\nimport type { RequestSuccessResponse } from '../../type/index.js';\nimport type { RequestOptions } from '../type/index.js';\nimport { notify } from './bus.js';\n\n/**\n * Error thrown for non-2xx responses. Carries the HTTP status so callers can key\n * behavior on it (401 redirects, 402 payment flows, error-boundary routing).\n */\nexport class RequestError extends Error {\n constructor(\n message: string,\n readonly status: number,\n ) {\n super(message);\n this.name = 'RequestError';\n }\n}\n\nexport function get<T>(url: string, opts?: RequestOptions) {\n return request<T>(url, { method: 'get' }, opts);\n}\n\nexport function post<T>(url: string, payload: unknown, opts?: RequestOptions) {\n return request<T>(url, { method: 'post', body: wireJson(payload) }, opts);\n}\n\nexport function patch<T>(url: string, payload: unknown, opts?: RequestOptions) {\n return request<T>(url, { method: 'patch', body: wireJson(payload) }, opts);\n}\n\nexport function put<T>(url: string, payload: unknown, opts?: RequestOptions) {\n return request<T>(url, { method: 'put', body: wireJson(payload) }, opts);\n}\n\nexport function remove<T>(url: string, opts?: RequestOptions) {\n return request<T>(url, { method: 'delete' }, opts);\n}\n\n/**\n * HTTP QUERY (RFC 10008): a safe, idempotent read whose JSON query travels in the\n * request body, avoiding URL-length limits. Method name must stay uppercase\n * (fetch only normalizes the classic verbs).\n */\nexport function query<T>(url: string, payload: unknown, opts?: RequestOptions) {\n return request<T>(url, { method: 'QUERY', body: wireJson(payload) }, opts);\n}\n\nfunction request<T>(url: string, init: RequestInit, opts?: RequestOptions) {\n notify({ phase: 'start', opts });\n\n init.headers = {\n accept: 'application/json',\n 'content-type': 'application/json',\n ...opts?.headers,\n };\n if (opts?.signal) {\n init.signal = opts.signal;\n }\n\n return fetch(url, init)\n .then((rawResp) =>\n rawResp.json().then((resp: unknown) => {\n const isSuccess = rawResp.status >= 200 && rawResp.status < 300;\n if (isSuccess) {\n notify({ phase: 'success', opts });\n return resp as RequestSuccessResponse<T>;\n }\n const errorResp = resp as Partial<RequestErrorResponse> | undefined;\n const error = {\n message: errorResp?.error?.message ?? rawResp.statusText,\n code: errorResp?.error?.code ?? rawResp.status,\n };\n notify({ phase: 'error', error, opts });\n throw new RequestError(error.message, error.code);\n }),\n )\n .finally(() => {\n notify({ phase: 'complete', opts });\n });\n}\n",
|
|
8
11
|
"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 return text.charAt(0).toUpperCase() + text.slice(1);\n}\n\nexport function lowerFirst(text: string): string {\n return text.charAt(0).toLowerCase() + text.slice(1);\n}\n\nexport function snakeCase(val: string): 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
12
|
"import { type QueryErrorKind, queryErrorKind } from '../querier/queryError.js';\nimport 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/** Generic on purpose: a driver's constraint message names tables and constraints, and Postgres echoes the value. */\nconst CONSTRAINT_ERRORS: ReadonlyMap<QueryErrorKind | undefined, RequestErrorResponse['error']> = new Map([\n ['uniqueViolation', { message: 'Conflict', code: 409 }],\n ['foreignKeyViolation', { message: 'Conflict', code: 409 }],\n ['notNullViolation', { message: 'Bad Request', code: 400 }],\n ['checkViolation', { message: 'Bad Request', code: 400 }],\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), then a constraint violation (409/400), defaults to 500; `code` mirrors the HTTP status.\n */\nexport function toErrorResponse(err: unknown): { status: number; body: RequestErrorResponse } {\n const error =\n err instanceof Error && 'status' in err && typeof err.status === 'number'\n ? { message: err.message, code: err.status }\n : (CONSTRAINT_ERRORS.get(queryErrorKind(err)) ?? {\n message: err instanceof Error ? err.message : 'Internal Server Error',\n code: 500,\n });\n return { status: error.code, body: { error } };\n}\n",
|
|
10
|
-
"import type { FieldKey, 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\n/**\n * Field selection - `{ name: true }` whitelists fields; relations go in `$populate`. Declared over\n * `F extends keyof E`, like every map keyed by an entity's members, so each key stays linked to its\n * property and an editor rename reaches it. `F` is also how a projection passes its captured key set.\n */\nexport type QuerySelect<E, F extends keyof E = FieldKey<E>, V = BooleanLike> = {\n [K in F]?: V;\n};\n\n/**\n * Accepted `$select` value: a field map, or raw SQL projections built with `raw()`\n * (e.g. ``[raw`*`, raw`LOG10(points)`.as('score')]``). The raw form is SQL-only.\n */\nexport type QuerySelectValue<E> = 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, R extends keyof E = RelationKey<E>> = {\n [K in R]?: BooleanLike | QueryPopulateRelationOptions<E[K]>;\n};\n\n/**\n * The key a read carries its relation tallies under. One spelling for the type and the runtime that\n * fills it: they sit in different modules, so a drift would type-check and answer `undefined`.\n */\nexport const COUNT_RESULT_KEY = '_count';\n\n/**\n * How many rows each named relation holds per parent, `true` for all of them or a filter to narrow\n * which ones count: a correlated count in the read's own statement, so no related row is loaded. Comes\n * back under `_count`, which keeps it clear of a relation of the same name `$populate` filled.\n */\nexport type QueryCount<E, R extends keyof E = ToManyRelationKey<E>> = {\n [K in R]?: 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> = QuerySelect<E, FieldKey<E>, true>;\n\n/**\n * Options to populate a relation declared as `V`, by its cardinality.\n */\nexport type QueryPopulateRelationOptions<V> =\n IsMany<V> extends true ? RelationQuery<RelationTarget<V>> : QueryUnique<RelationTarget<V>> & { $required?: boolean };\n\n/**\n * The per-request context parameterized filters read, set with `withContext(ctx, cb)`. An interface,\n * so its keys can be typed once: `declare module 'uql-orm' { interface UqlContext { tenantId: number } }`.\n */\nexport interface UqlContext {\n [key: string]: unknown;\n}\n\n/**\n * A filter's `$where` fragment: a plain fragment, or a function of the ambient {@link UqlContext}.\n * Return `undefined` when the condition can't resolve (see {@link FilterOptions.onMissing}).\n */\nexport type FilterWhere<E> = QueryWhere<E> | ((context: UqlContext | undefined) => QueryWhere<E> | undefined);\n\n/**\n * What to do when a filter's condition returns `undefined`. `skip` omits it (convenience filters);\n * `throw` fails closed (the default for `security` filters).\n */\nexport type FilterOnMissing = 'skip' | 'throw';\n\n/**\n * Authoring shape for `@Entity({ filters })` / `@Filter` / `defineFilter`.\n */\nexport type FilterOptions<E = unknown> = {\n readonly where: FilterWhere<E>;\n /** Applied to every query unless bypassed via `QueryOptions.filters`. Defaults to `true`. */\n readonly default?: boolean;\n} & (\n | {\n readonly security?: false;\n /** What to do when {@link FilterOptions.where} returns `undefined`. Defaults to `skip`. */\n readonly onMissing?: FilterOnMissing;\n }\n | {\n /**\n * Row-level-security filter: always applied (ignores `QueryOptions.filters` bypass) and\n * AND-merged so a client `$where` on the same field can't override it. It fails closed.\n */\n readonly security: true;\n readonly onMissing?: 'throw';\n }\n);\n\n/**\n * direction for the sort.\n */\nexport type QuerySortDirection = -1 | 1 | 'asc' | 'desc';\n\n/**\n * Accepted value for a field in `$sort` - either a direction or a vector similarity search.\n */\nexport type QuerySortValue = QuerySortDirection | QueryVectorSearch;\n\n/**\n * 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 * Ordering parents by how many rows a to-many relation holds - \"the ten users with the most posts\".\n * The tally is computed per parent as a correlated count, never by loading the rows.\n */\nexport type QuerySortByCount = {\n $count: QuerySortDirection;\n};\n\n/**\n * A sort by fields, JSON paths, a to-one relation's fields, a to-many's `$count`, or a vector distance,\n * which `Vector` confines to the queried entity. One mapped type over the key sets: an intersection is\n * checked once per member, which made this the costliest type to check.\n */\nexport type QuerySortMap<E, Vector extends boolean = true, K extends keyof E = FieldKey<E> | RelationKey<E>> = {\n [P in K]?: P extends RelationKey<E>\n ? // A to-many has no single value to order by, so what it offers instead is its own size.\n IsMany<E[P]> extends true\n ? QuerySortByCount\n : QuerySortMap<RelationTarget<E[P]>, false>\n : Vector extends true\n ? NonNullable<E[P]> extends readonly number[]\n ? QuerySortValue\n : QuerySortDirection\n : QuerySortDirection;\n} & ([JsonFieldPaths<E>] extends [never] ? unknown : { [P in JsonFieldPaths<E>]?: QuerySortDirection });\n\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 * Lock the rows this query returns, `SELECT ... FOR UPDATE`, inside an open transaction: outside one\n * it is refused, since the lock would drop before the rows are used. SQL only, and not the SQLite family.\n */\n $lock?: QueryLock;\n\n /**\n * How many candidates an ANN index explores before ranking a vector search, in that index's own units\n * (`hnsw.ef_search`, `numCandidates`...); ignored where the search is exact. Postgres needs a transaction.\n */\n $candidates?: number;\n\n // `$where`, `$skip` and `$limit` are declared here rather than intersected in from\n // {@link QueryFilter} and {@link QueryPager}: an assignability check against an intersection is\n // repeated per constituent, and every query in a consuming codebase pays that. The two shapes are\n // pinned together in `queryStatementClauses.test-d.ts` so the copies cannot drift.\n\n /**\n * filtering options.\n */\n $where?: QueryWhere<E>;\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, for the wire parser and the relation query\n * check alike; `satisfies` keeps them in step with `Query`.\n */\nexport const QUERY_OBJECT_CLAUSES = [\n '$select',\n '$populate',\n '$exclude',\n '$where',\n '$sort',\n] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Object clauses only the statement itself takes: a populated relation's rows keep their declared type,\n * so a `$count` inside one would have no `_count` to land in.\n */\nexport const QUERY_ROOT_OBJECT_CLAUSES = ['$count'] as const satisfies readonly (keyof Query<unknown>)[];\n\nexport const QUERY_NUMBER_CLAUSES = ['$skip', '$limit'] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Number clauses only the statement itself takes - the numeric mirror of {@link QUERY_ROOT_OBJECT_CLAUSES}.\n * `$candidates` tunes the index behind a vector search, and a vector search only ever ranks the rows\n * the statement returns, so a relation's own query has nothing to tune.\n */\nexport const QUERY_ROOT_NUMBER_CLAUSES = ['$candidates'] as const satisfies readonly (keyof Query<unknown>)[];\n\nexport const QUERY_BOOLEAN_CLAUSES = ['$distinct'] as const satisfies readonly (keyof Query<unknown>)[];\n\n/** The clauses that describe the statement, which a populated relation's own query refuses by name. */\nexport const QUERY_STATEMENT_CLAUSES = [\n '$lock',\n ...QUERY_ROOT_OBJECT_CLAUSES,\n ...QUERY_ROOT_NUMBER_CLAUSES,\n] as const satisfies readonly (keyof Query<unknown>)[];\n\ntype RelationClause = (\n | typeof QUERY_OBJECT_CLAUSES\n | typeof QUERY_NUMBER_CLAUSES\n | typeof QUERY_BOOLEAN_CLAUSES\n)[number];\n\n/**\n * A populated relation's own query: the clause groups its runtime check accepts, so the two cannot\n * drift, and a clause added to {@link Query} stays off it until it joins one of them.\n */\nexport type RelationQuery<E = object> = Pick<Query<E>, RelationClause> & {\n $required?: boolean;\n};\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 shape a row, captured as key sets rather than maps: a naked type parameter skips\n * excess-property checks, while a key set fails its own constraint on a typo.\n * @internal\n */\ntype QueryProjection<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n> = {\n $select?: QuerySelect<E, S, V> | readonly QueryRaw[];\n $exclude?: QuerySelect<E, X, V>;\n $populate?: QueryPopulate<E, P>;\n // Narrowing the captured names to the to-many ones leaves a to-one relation no key here at all,\n // so counting one is an excess property rather than a value to check.\n $count?: QueryCount<E, C & ToManyRelationKey<E>>;\n};\n\n/**\n * A {@link Query} whose projection is captured, so {@link QueryFindResult} can shape the row.\n */\nexport type QueryProjected<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E> = never,\n> = 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, as the runtime projects them: a positive `$select`'s, or every\n * field minus what `$select` or `$exclude` subtracts, plus the populated relations.\n * @internal\n */\ntype ProjectedKeys<E, S, V, X, P> =\n | ([V] extends [false | 0] ? Exclude<FieldKey<E>, S> : [S] extends [never] ? Exclude<FieldKey<E>, X> : S)\n | P;\n\n/**\n * Whether every entry of the captured map says the same thing: all selected, or all subtracted.\n * @internal\n */\ntype IsUniform<V> = [V] extends [true | 1] ? true : [V] extends [false | 0] ? true : false;\n\n/**\n * A find's row: the entity narrowed to what the query projected and populated, so reading anything\n * else does not compile. The entity itself where the projection is raw, absent or not uniform.\n * @example `QueryFindResult<User, 'id' | 'name'>`\n */\nexport type QueryFindResult<\n E,\n S extends FieldKey<E> = never,\n // A whitelist by default, so the hand-written form reads `QueryFindResult<User, 'id' | 'name'>`.\n V = true,\n X extends FieldKey<E> = never,\n P extends RelationKey<E> = never,\n C extends RelationKey<E> = never,\n> = QueryProjectedRow<E, S, V, X, P, C> & CountedRelations<C>;\n\n/**\n * The `_count` a query asked for, or an inert intersection member when it asked for none - so a read\n * without `$count` keeps exactly the row type it had.\n */\ntype CountedRelations<C extends PropertyKey> = [C] extends [never]\n ? unknown\n : { [K in typeof COUNT_RESULT_KEY]: { [R in C]: number } };\n\n/** @internal */\ntype QueryProjectedRow<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n> = [S | X] extends [never]\n ? E\n : IsUniform<V> extends true\n ? [PopulatedToMany<E, P>] extends [never]\n ? // `Pick`, not a key remap: an entity keyed by an index signature - a content type defined at\n // runtime - has `string` for its keys, and a remap keeps no literal one, so every projection\n // over one came back as `{}`.\n Pick<E, ProjectedKeys<E, S, V, X, P> & keyof E>\n : // A populated to-many is always a list, empty where the parent has no children, so it maps\n // and counts without a guard. Only that promotion needs a second member, and only a query\n // that populates one pays for it; every other key keeps the modifier the entity declared,\n // a to-one relation included, since a join that finds no row leaves it absent.\n Pick<E, Exclude<ProjectedKeys<E, S, V, X, P>, PopulatedToMany<E, P>> & keyof E> & {\n [K in PopulatedToMany<E, P>]-?: NonNullable<E[K]>;\n }\n : E;\n\n/** The to-many relations a query populated, which come back as lists rather than as optional ones. */\ntype PopulatedToMany<E, P> = Extract<P, ToManyRelationKey<E>>;\n\n/**\n * stringified query.\n */\nexport type QueryStringified = {\n [K in keyof Query<unknown>]?: string;\n};\n\n/** What upserting one row reports. `created` is only knowable for a single statement, so a batch has none. */\nexport type QueryUpsertOneResult<E> = {\n readonly id?: WrittenId<E>;\n readonly changes?: number;\n /** Whether the record was created (`true`) or updated (`false`), where the dialect can tell. */\n readonly created?: boolean;\n};\n\n/**\n * What upserting many rows reports. `ids` is payload-aligned like an insert's, so it zips with the\n * rows that were passed, and carries a composite key as the map naming it.\n */\nexport type QueryUpsertManyResult<E> = {\n readonly ids: (WrittenId<E> | undefined)[];\n readonly changes?: number;\n};\n\n/**\n * result of an update operation, as the driver reports it - which is what `run` hands back, where\n * there is no entity to name the ids against. The `QueryUpsert*Result` pair is the entity-level shape.\n */\nexport type QueryUpdateResult = {\n /**\n * number of affected records.\n */\n changes?: number;\n /**\n * the IDs the statement reported, in payload order, `undefined` where it reported none for that\n * row - a MongoDB upsert names only the documents it inserted. Exact on `'returning'` dialects;\n * inferred from the driver header on the others (see {@link InsertIdSource}), and absent\n * altogether when the header reports nothing.\n */\n ids?: (PrimaryKey | undefined)[];\n /**\n * first inserted ID.\n */\n firstId?: PrimaryKey;\n /**\n * whether the record was created (`true`) or updated (`false`).\n * `undefined` when the dialect cannot determine this (e.g. SQLite).\n */\n created?: boolean;\n};\n",
|
|
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, isWhereMap } from '../util/object.util.js';\n\n/**\n * Keys accepted from the wire - query structure ({@link Query}) plus the `hardDelete`/`count` scalar\n * flags. Anything else (e.g. `filters`, `context`, `$entity`) is dropped so a remote client can't\n * bypass a security filter or inject ambient context - those are server-only. The `satisfies` ties\n * every entry to a real query/option key, so a typo or a renamed option fails to compile.\n */\nconst ALLOWED_QUERY_KEYS = new Set<string>([\n ...QUERY_OBJECT_CLAUSES,\n ...QUERY_ROOT_OBJECT_CLAUSES,\n ...QUERY_NUMBER_CLAUSES,\n ...QUERY_ROOT_NUMBER_CLAUSES,\n ...QUERY_BOOLEAN_CLAUSES,\n 'hardDelete',\n 'count',\n] satisfies (keyof 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 if (!isWhereMap(query['$where'])) {\n throw Object.assign(new TypeError(\"'$where' must be a JSON object\"), { status: 400 });\n }\n\n // A query string carries every value as text, so what decodes a clause is the shape its group\n // declares. `'false'` is the reason the boolean pass exists rather than the raw value being taken:\n // it is a non-empty string, so a `$distinct=false` would otherwise read as asking for one.\n for (const key of [...QUERY_NUMBER_CLAUSES, ...QUERY_ROOT_NUMBER_CLAUSES]) {\n if (query[key] !== undefined) {\n query[key] = Number(query[key]);\n }\n }\n for (const key of QUERY_BOOLEAN_CLAUSES) {\n if (query[key] !== undefined) {\n query[key] = query[key] === true || query[key] === 'true';\n }\n }\n\n return query as 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 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/** The id as one path segment, refusing a composite key, which has no spelling in `/:id` yet. Callers are `async`. */\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
|
+
"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 QueryFilter,\n QueryFindResult,\n QueryOneProjected,\n QueryOptions,\n QueryPage,\n QueryProjected,\n QuerySearch,\n RelationKey,\n RequestCountedSuccessResponse,\n RequestSuccessResponse,\n Type,\n UpdatePayload,\n WireQuery,\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/** The id as one path segment, refusing a composite key, which has no spelling in `/:id` yet. Callers are `async`. */\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, never>,\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, never>,\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, never>,\n opts?: RequestFindOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>> {\n const data: WireQuery<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, never>,\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, never>, 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, never>, 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, never>,\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>(\n entity: Type<E>,\n q: QuerySearch<E, never>,\n payload: UpdatePayload<E, never>,\n opts?: RequestOptions,\n ) {\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, never>, 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
14
|
"import { HttpQuerier } from './querier/httpQuerier.js';\nimport type { ClientQuerier, ClientQuerierPool } from './type/index.js';\n\nlet defaultPool: ClientQuerierPool = {\n getQuerier: () => new HttpQuerier('/api'),\n};\n\nexport function setQuerierPool<T extends ClientQuerierPool>(pool: T) {\n defaultPool = pool;\n}\n\nexport function getQuerierPool(): ClientQuerierPool {\n return defaultPool;\n}\n\nexport function getQuerier(): ClientQuerier {\n return getQuerierPool().getQuerier();\n}\n"
|
|
14
15
|
],
|
|
15
|
-
"mappings": "AAEA,IAAM,EAAkC,CAAC,EAElC,SAAS,CAAM,CAAC,EAAyC,CAC9D,QAAW,KAAe,EACxB,EAAY,CAAY,EAIrB,SAAS,CAAE,CAAC,EAAiC,CAClD,EAAa,KAAK,CAAE,EACpB,IAAM,EAAQ,EAAa,OAAS,EACpC,MAAO,IAAY,CACjB,EAAa,OAAO,EAAO,CAAC,
|
|
16
|
-
"debugId": "
|
|
16
|
+
"mappings": "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",
|
|
17
|
+
"debugId": "489BD92466B1F42A64756E2164756E21",
|
|
17
18
|
"names": []
|
|
18
19
|
}
|
package/dist/d1/d1Querier.d.ts
CHANGED
|
@@ -14,16 +14,16 @@ export interface D1PreparedStatement {
|
|
|
14
14
|
all<T = unknown>(): Promise<D1Result<T>>;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
* database is read -
|
|
17
|
+
* What uql calls on D1: a binding (`env.DB`) and a session from `env.DB.withSession()` - how a
|
|
18
|
+
* read-replicated database is read - answer it alike. Both are typed by `@cloudflare/workers-types`.
|
|
19
19
|
*/
|
|
20
|
-
export interface
|
|
20
|
+
export interface D1Queryable {
|
|
21
21
|
prepare(query: string): D1PreparedStatement;
|
|
22
22
|
}
|
|
23
23
|
export declare class D1Querier extends AbstractSqliteQuerier {
|
|
24
|
-
readonly db:
|
|
24
|
+
readonly db: D1Queryable;
|
|
25
25
|
readonly extra?: ExtraOptions | undefined;
|
|
26
|
-
constructor(db:
|
|
26
|
+
constructor(db: D1Queryable, dialect: SqliteDialect, extra?: ExtraOptions | undefined);
|
|
27
27
|
protected execute(query: string, values: SqliteBindValue[]): Promise<{
|
|
28
28
|
rows: RawRow[];
|
|
29
29
|
changes: number;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { AbstractSqlQuerierPool } from '../querier/index.js';
|
|
2
2
|
import type { ExtraOptions } from '../type/index.js';
|
|
3
|
-
import { type
|
|
3
|
+
import { D1Querier, type D1Queryable } from './d1Querier.js';
|
|
4
4
|
import { D1SqliteDialect } from './d1SqliteDialect.js';
|
|
5
5
|
/**
|
|
6
6
|
* Pool for Cloudflare D1. It holds nothing: every querier runs on what it was given, `env.DB` or a
|
|
7
7
|
* session from `env.DB.withSession()`, the way a read-replicated database is read consistently.
|
|
8
8
|
*/
|
|
9
9
|
export declare class D1QuerierPool extends AbstractSqlQuerierPool<D1Querier, D1SqliteDialect> {
|
|
10
|
-
readonly db:
|
|
11
|
-
constructor(db:
|
|
10
|
+
readonly db: D1Queryable;
|
|
11
|
+
constructor(db: D1Queryable, extra?: ExtraOptions);
|
|
12
12
|
getQuerier(): Promise<D1Querier>;
|
|
13
13
|
end(): Promise<void>;
|
|
14
14
|
}
|
package/dist/http/query.d.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { WireQuery } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Parse raw query-string entries (with JSON-stringified values) into a UQL query object.
|
|
4
4
|
* Symmetric counterpart of {@link stringifyQuery}. Only {@link ALLOWED_QUERY_KEYS} are honored.
|
|
5
5
|
*/
|
|
6
|
-
export declare function parseQueryParams(params?: Record<string, unknown>):
|
|
6
|
+
export declare function parseQueryParams(params?: Record<string, unknown>): WireQuery<unknown>;
|
|
7
7
|
/**
|
|
8
8
|
* Serialize a UQL query object into a percent-encoded query string where object values
|
|
9
9
|
* are JSON-stringified. Symmetric counterpart of {@link parseQueryParams}.
|
|
10
10
|
*/
|
|
11
11
|
export declare function stringifyQuery(query?: Record<string, unknown>): string;
|
|
12
|
+
/**
|
|
13
|
+
* What leaves the browser, as JSON, refusing what JSON keeps nothing of rather than letting the server
|
|
14
|
+
* build a statement around the remains. A `raw` fragment renders SQL against a dialect the client does not
|
|
15
|
+
* have and arrives as `{}`; binary arrives as an object keyed by index. A `Date` is not among them - it
|
|
16
|
+
* serializes to ISO 8601, which is what a date column reads. This is what a cast, or a JavaScript caller,
|
|
17
|
+
* hits where the client's types already refuse a fragment.
|
|
18
|
+
*/
|
|
19
|
+
export declare function wireJson(value: unknown): string;
|
package/dist/http/query.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// the clause lists themselves, not the barrel: this module is in the browser bundle's graph
|
|
2
2
|
import { QUERY_BOOLEAN_CLAUSES, QUERY_NUMBER_CLAUSES, QUERY_OBJECT_CLAUSES, QUERY_ROOT_NUMBER_CLAUSES, QUERY_ROOT_OBJECT_CLAUSES, } from '../type/query.js';
|
|
3
|
+
// the brand alone, not the class: importing `QueryRaw` for an `instanceof` kept it, and `ColumnRef`
|
|
4
|
+
// with it, in the browser bundle, which is on a size budget
|
|
5
|
+
import { RAW_VALUE } from '../type/queryRaw.js';
|
|
3
6
|
// the specific util module, not the barrel, so the browser bundle does not pull in entity metadata
|
|
4
7
|
import { getKeys, isWhereMap } from '../util/object.util.js';
|
|
5
8
|
/**
|
|
@@ -82,8 +85,30 @@ export function stringifyQuery(query) {
|
|
|
82
85
|
if (value === undefined) {
|
|
83
86
|
continue;
|
|
84
87
|
}
|
|
85
|
-
params.append(key, typeof value === 'object' && value !== null ?
|
|
88
|
+
params.append(key, typeof value === 'object' && value !== null ? wireJson(value) : String(value));
|
|
86
89
|
}
|
|
87
90
|
const qs = params.toString();
|
|
88
91
|
return qs ? `?${qs}` : '';
|
|
89
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* What leaves the browser, as JSON, refusing what JSON keeps nothing of rather than letting the server
|
|
95
|
+
* build a statement around the remains. A `raw` fragment renders SQL against a dialect the client does not
|
|
96
|
+
* have and arrives as `{}`; binary arrives as an object keyed by index. A `Date` is not among them - it
|
|
97
|
+
* serializes to ISO 8601, which is what a date column reads. This is what a cast, or a JavaScript caller,
|
|
98
|
+
* hits where the client's types already refuse a fragment.
|
|
99
|
+
*/
|
|
100
|
+
export function wireJson(value) {
|
|
101
|
+
return JSON.stringify(value, (_key, held) => {
|
|
102
|
+
if (typeof held !== 'object' || held === null) {
|
|
103
|
+
return held;
|
|
104
|
+
}
|
|
105
|
+
if (RAW_VALUE in held) {
|
|
106
|
+
throw new TypeError('raw SQL cannot travel over HTTP: what leaves the browser is JSON');
|
|
107
|
+
}
|
|
108
|
+
// A blob is a field value, so no type parameter reaches it: this is the only place it is caught.
|
|
109
|
+
if (held instanceof ArrayBuffer || ArrayBuffer.isView(held)) {
|
|
110
|
+
throw new TypeError('binary cannot travel over HTTP: what leaves the browser is JSON');
|
|
111
|
+
}
|
|
112
|
+
return held;
|
|
113
|
+
});
|
|
114
|
+
}
|
package/dist/type/entity.d.ts
CHANGED
|
@@ -84,7 +84,7 @@ export type JsonUpdateOp<T = unknown> = {
|
|
|
84
84
|
*/
|
|
85
85
|
type JsonUpdateOpFor<V, T = UnwrapJson<NonNullable<V>>> = [T] extends [never] ? never : IsMany<T> extends true ? never : JsonUpdateOp<T>;
|
|
86
86
|
/** What an update takes beyond the value: `null` to clear an optional member, `raw` SQL, and JSON operators. */
|
|
87
|
-
type UpdateExtra<V> = (undefined extends V ? null : never) |
|
|
87
|
+
type UpdateExtra<V, Raw> = (undefined extends V ? null : never) | Raw | JsonUpdateOpFor<V>;
|
|
88
88
|
/**
|
|
89
89
|
* What a whole-record write persists: the fields and relations with their declared optionality, a
|
|
90
90
|
* related row's alike, and no methods. Two mapped types, since asking each key costs a conditional.
|
|
@@ -97,10 +97,10 @@ export type EntityData<E, F extends keyof E = FieldKey<E>, R extends keyof E = R
|
|
|
97
97
|
/** A relation's value as its rows' {@link EntityData}. */
|
|
98
98
|
type RelationData<V> = V extends readonly (infer T)[] ? EntityData<T>[] : V extends object ? EntityData<V> : never;
|
|
99
99
|
/** {@link EntityData} made partial, each member also taking its {@link UpdateExtra}. */
|
|
100
|
-
export type UpdatePayload<E, F extends keyof E = FieldKey<E>, R extends keyof E = RelationKey<E>> = {
|
|
101
|
-
[P in F]?: E[P] | UpdateExtra<E[P]>;
|
|
100
|
+
export type UpdatePayload<E, Raw = QueryRaw, F extends keyof E = FieldKey<E>, R extends keyof E = RelationKey<E>> = {
|
|
101
|
+
[P in F]?: E[P] | UpdateExtra<E[P], Raw>;
|
|
102
102
|
} & {
|
|
103
|
-
[P in R]?: E[P] | RelationData<E[P]> | UpdateExtra<E[P]>;
|
|
103
|
+
[P in R]?: E[P] | RelationData<E[P]> | UpdateExtra<E[P], Raw>;
|
|
104
104
|
};
|
|
105
105
|
/** The key's name where the entity states it, by the `idKey` brand or a conventional name; `never` otherwise. */
|
|
106
106
|
export type NamedIdKey<E> = E extends {
|
package/dist/type/query.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export type QuerySelect<E, F extends keyof E = FieldKey<E>, V = BooleanLike> = {
|
|
|
37
37
|
* Accepted `$select` value: a field map, or raw SQL projections built with `raw()`
|
|
38
38
|
* (e.g. ``[raw`*`, raw`LOG10(points)`.as('score')]``). The raw form is SQL-only.
|
|
39
39
|
*/
|
|
40
|
-
export type QuerySelectValue<E> = QuerySelect<E> | readonly
|
|
40
|
+
export type QuerySelectValue<E, Raw = QueryRaw> = QuerySelect<E> | readonly Raw[];
|
|
41
41
|
/**
|
|
42
42
|
* Fields to exclude from the query result - `{ name: true }` blacklists fields.
|
|
43
43
|
* Mutually exclusive with positive field selections in `$select`.
|
|
@@ -46,8 +46,8 @@ export type QueryExclude<E> = QuerySelect<E>;
|
|
|
46
46
|
/**
|
|
47
47
|
* relation population map.
|
|
48
48
|
*/
|
|
49
|
-
export type QueryPopulate<E, R extends keyof E = RelationKey<E>> = {
|
|
50
|
-
[K in R]?: BooleanLike | QueryPopulateRelationOptions<E[K]>;
|
|
49
|
+
export type QueryPopulate<E, Raw = QueryRaw, R extends keyof E = RelationKey<E>> = {
|
|
50
|
+
[K in R]?: BooleanLike | QueryPopulateRelationOptions<E[K], Raw>;
|
|
51
51
|
};
|
|
52
52
|
/**
|
|
53
53
|
* The key a read carries its relation tallies under. One spelling for the type and the runtime that
|
|
@@ -59,8 +59,8 @@ export declare const COUNT_RESULT_KEY = "_count";
|
|
|
59
59
|
* which ones count: a correlated count in the read's own statement, so no related row is loaded. Comes
|
|
60
60
|
* back under `_count`, which keeps it clear of a relation of the same name `$populate` filled.
|
|
61
61
|
*/
|
|
62
|
-
export type QueryCount<E, R extends keyof E = ToManyRelationKey<E>> = {
|
|
63
|
-
[K in R]?: BooleanLike | QueryFilter<RelationTarget<E[K]
|
|
62
|
+
export type QueryCount<E, Raw = QueryRaw, R extends keyof E = ToManyRelationKey<E>> = {
|
|
63
|
+
[K in R]?: BooleanLike | QueryFilter<RelationTarget<E[K]>, Raw>;
|
|
64
64
|
};
|
|
65
65
|
/**
|
|
66
66
|
* query conflict paths - subset of field keys used to detect upsert conflicts.
|
|
@@ -69,7 +69,7 @@ export type QueryConflictPaths<E> = QuerySelect<E, FieldKey<E>, true>;
|
|
|
69
69
|
/**
|
|
70
70
|
* Options to populate a relation declared as `V`, by its cardinality.
|
|
71
71
|
*/
|
|
72
|
-
export type QueryPopulateRelationOptions<V> = IsMany<V> extends true ? RelationQuery<RelationTarget<V
|
|
72
|
+
export type QueryPopulateRelationOptions<V, Raw = QueryRaw> = IsMany<V> extends true ? RelationQuery<RelationTarget<V>, Raw> : QueryUnique<RelationTarget<V>, Raw> & {
|
|
73
73
|
$required?: boolean;
|
|
74
74
|
};
|
|
75
75
|
/**
|
|
@@ -158,24 +158,24 @@ export type QueryPager = {
|
|
|
158
158
|
/**
|
|
159
159
|
* Which rows a statement addresses.
|
|
160
160
|
*/
|
|
161
|
-
export type QueryFilter<E> = {
|
|
161
|
+
export type QueryFilter<E, Raw = QueryRaw> = {
|
|
162
162
|
/**
|
|
163
163
|
* filtering options.
|
|
164
164
|
*/
|
|
165
|
-
$where?: QueryWhere<E>;
|
|
165
|
+
$where?: QueryWhere<E, Raw>;
|
|
166
166
|
};
|
|
167
167
|
/**
|
|
168
168
|
* A filter plus the page `count` takes. No `$sort`: ordering picks *which* rows a page holds, never
|
|
169
169
|
* how many, so a count that accepted one would promise an influence it cannot have.
|
|
170
170
|
*/
|
|
171
|
-
export type QueryPage<E> = QueryFilter<E> & QueryPager;
|
|
171
|
+
export type QueryPage<E, Raw = QueryRaw> = QueryFilter<E, Raw> & QueryPager;
|
|
172
172
|
/**
|
|
173
173
|
* A filter plus the ordering and page `updateMany`/`deleteMany` take. Both settle the
|
|
174
174
|
* rows they address with a SELECT first, so the page is portable rather than MySQL-only, and a
|
|
175
175
|
* vector `$sort` is as valid here as on a read: it ranks the settle query's rows, which has the
|
|
176
176
|
* projection list to hold the distance. `$lock` stays off these, declared on {@link Query} instead.
|
|
177
177
|
*/
|
|
178
|
-
export type QuerySearch<E> = QueryPage<E> & {
|
|
178
|
+
export type QuerySearch<E, Raw = QueryRaw> = QueryPage<E, Raw> & {
|
|
179
179
|
/**
|
|
180
180
|
* sorting options.
|
|
181
181
|
*/
|
|
@@ -184,21 +184,21 @@ export type QuerySearch<E> = QueryPage<E> & {
|
|
|
184
184
|
/**
|
|
185
185
|
* query options.
|
|
186
186
|
*/
|
|
187
|
-
export type Query<E> = {
|
|
187
|
+
export type Query<E, Raw = QueryRaw> = {
|
|
188
188
|
/**
|
|
189
189
|
* field selection - `{ name: true }` whitelists fields, or raw SQL projections
|
|
190
190
|
* (``[raw`LOG10(points)`.as('score')]``, SQL dialects only - MongoDB rejects the raw-array form).
|
|
191
191
|
* Mutually exclusive with `$exclude`.
|
|
192
192
|
*/
|
|
193
|
-
$select?: QuerySelectValue<E>;
|
|
193
|
+
$select?: QuerySelectValue<E, Raw>;
|
|
194
194
|
/**
|
|
195
195
|
* relation population options.
|
|
196
196
|
*/
|
|
197
|
-
$populate?: QueryPopulate<E>;
|
|
197
|
+
$populate?: QueryPopulate<E, Raw>;
|
|
198
198
|
/**
|
|
199
199
|
* how many rows each named relation holds, under `_count` on every row. See {@link QueryCount}.
|
|
200
200
|
*/
|
|
201
|
-
$count?: QueryCount<E>;
|
|
201
|
+
$count?: QueryCount<E, Raw>;
|
|
202
202
|
/**
|
|
203
203
|
* field exclusion - `{ name: true }` blacklists fields. Mutually exclusive with positive `$select`.
|
|
204
204
|
* Keys a relation is assembled from (a joined row's primary key, a to-many's foreign key) are kept
|
|
@@ -227,7 +227,7 @@ export type Query<E> = {
|
|
|
227
227
|
/**
|
|
228
228
|
* filtering options.
|
|
229
229
|
*/
|
|
230
|
-
$where?: QueryWhere<E>;
|
|
230
|
+
$where?: QueryWhere<E, Raw>;
|
|
231
231
|
/**
|
|
232
232
|
* Index from where start the search
|
|
233
233
|
*/
|
|
@@ -237,6 +237,11 @@ export type Query<E> = {
|
|
|
237
237
|
*/
|
|
238
238
|
$limit?: number;
|
|
239
239
|
};
|
|
240
|
+
/**
|
|
241
|
+
* A {@link Query} as it travels as JSON, which a `raw` SQL fragment cannot: what the browser client takes,
|
|
242
|
+
* and what an RPC contract (tRPC, oRPC, TanStack Start) declares as its input.
|
|
243
|
+
*/
|
|
244
|
+
export type WireQuery<E> = Query<E, never>;
|
|
240
245
|
/**
|
|
241
246
|
* `Query`'s clauses grouped by the shape of their value, for the wire parser and the relation query
|
|
242
247
|
* check alike; `satisfies` keeps them in step with `Query`.
|
|
@@ -262,36 +267,36 @@ type RelationClause = (typeof QUERY_OBJECT_CLAUSES | typeof QUERY_NUMBER_CLAUSES
|
|
|
262
267
|
* A populated relation's own query: the clause groups its runtime check accepts, so the two cannot
|
|
263
268
|
* drift, and a clause added to {@link Query} stays off it until it joins one of them.
|
|
264
269
|
*/
|
|
265
|
-
export type RelationQuery<E = object> = Pick<Query<E>, RelationClause> & {
|
|
270
|
+
export type RelationQuery<E = object, Raw = QueryRaw> = Pick<Query<E, Raw>, RelationClause> & {
|
|
266
271
|
$required?: boolean;
|
|
267
272
|
};
|
|
268
273
|
/**
|
|
269
274
|
* options to get a single record.
|
|
270
275
|
*/
|
|
271
|
-
export type QueryOne<E> = Except<Query<E>, '$limit'>;
|
|
276
|
+
export type QueryOne<E, Raw = QueryRaw> = Except<Query<E, Raw>, '$limit'>;
|
|
272
277
|
/**
|
|
273
278
|
* options to get an unique record.
|
|
274
279
|
*/
|
|
275
|
-
export type QueryUnique<E> = Pick<QueryOne<E>, '$select' | '$exclude' | '$populate' | '$where'>;
|
|
280
|
+
export type QueryUnique<E, Raw = QueryRaw> = Pick<QueryOne<E, Raw>, '$select' | '$exclude' | '$populate' | '$where'>;
|
|
276
281
|
/**
|
|
277
282
|
* The clauses that shape a row, captured as key sets rather than maps: a naked type parameter skips
|
|
278
283
|
* excess-property checks, while a key set fails its own constraint on a typo.
|
|
279
284
|
* @internal
|
|
280
285
|
*/
|
|
281
|
-
type QueryProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E
|
|
282
|
-
$select?: QuerySelect<E, S, V> | readonly
|
|
286
|
+
type QueryProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E>, Raw = QueryRaw> = {
|
|
287
|
+
$select?: QuerySelect<E, S, V> | readonly Raw[];
|
|
283
288
|
$exclude?: QuerySelect<E, X, V>;
|
|
284
|
-
$populate?: QueryPopulate<E, P>;
|
|
285
|
-
$count?: QueryCount<E, C & ToManyRelationKey<E>>;
|
|
289
|
+
$populate?: QueryPopulate<E, Raw, P>;
|
|
290
|
+
$count?: QueryCount<E, Raw, C & ToManyRelationKey<E>>;
|
|
286
291
|
};
|
|
287
292
|
/**
|
|
288
293
|
* A {@link Query} whose projection is captured, so {@link QueryFindResult} can shape the row.
|
|
289
294
|
*/
|
|
290
|
-
export type QueryProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E> = never> = Query<E> & QueryProjection<E, S, V, X, P, C>;
|
|
295
|
+
export type QueryProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E> = never, Raw = QueryRaw> = Query<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;
|
|
291
296
|
/**
|
|
292
297
|
* A {@link QueryOne} whose projection is captured, so {@link QueryFindResult} can shape the row.
|
|
293
298
|
*/
|
|
294
|
-
export type QueryOneProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E> = never> = QueryOne<E> & QueryProjection<E, S, V, X, P, C>;
|
|
299
|
+
export type QueryOneProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E> = never, Raw = QueryRaw> = QueryOne<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;
|
|
295
300
|
/**
|
|
296
301
|
* The keys a query comes back with, as the runtime projects them: a positive `$select`'s, or every
|
|
297
302
|
* field minus what `$select` or `$exclude` subtracts, plus the populated relations.
|
|
@@ -26,10 +26,10 @@ export type QueryTextSearchOptions<E> = {
|
|
|
26
26
|
* entity's keys so each stays linked for rename. An object and nothing else, so a wrong value is
|
|
27
27
|
* reported on its key: ids go through `{ id: 1 }` or the by-id methods.
|
|
28
28
|
*/
|
|
29
|
-
export type QueryWhere<E, K extends keyof E = FieldKey<E> | RelationKey<E>> = QueryWhereRootOperator<E> & {
|
|
30
|
-
[P in K]?: P extends FieldKey<E> ? QueryWhereFieldValue<E[P]> : QueryWhere<RelationTarget<E[P]
|
|
29
|
+
export type QueryWhere<E, Raw = QueryRaw, K extends keyof E = FieldKey<E> | RelationKey<E>> = QueryWhereRootOperator<E, Raw> & {
|
|
30
|
+
[P in K]?: P extends FieldKey<E> ? QueryWhereFieldValue<E[P], Raw> : QueryWhere<RelationTarget<E[P]>, Raw> | QueryRelationSizeFilter;
|
|
31
31
|
} & ([JsonFieldPaths<E>] extends [never] ? unknown : {
|
|
32
|
-
[P in JsonFieldPaths<E>]?: QueryWhereFieldValue<JsonFieldPathValue<E, P
|
|
32
|
+
[P in JsonFieldPaths<E>]?: QueryWhereFieldValue<JsonFieldPathValue<E, P>, Raw>;
|
|
33
33
|
});
|
|
34
34
|
/**
|
|
35
35
|
* Filter a to-many relation by its row count.
|
|
@@ -39,24 +39,24 @@ export type QueryWhere<E, K extends keyof E = FieldKey<E> | RelationKey<E>> = Qu
|
|
|
39
39
|
export type QueryRelationSizeFilter = {
|
|
40
40
|
readonly $size: number | QuerySizeComparisonOps;
|
|
41
41
|
};
|
|
42
|
-
export type QueryWhereRootOperator<E> = {
|
|
42
|
+
export type QueryWhereRootOperator<E, Raw = QueryRaw> = {
|
|
43
43
|
/**
|
|
44
44
|
* joins query clauses with a logical `AND`, returns records that match all the clauses.
|
|
45
45
|
*/
|
|
46
|
-
$and?: QueryWhereArray<E>;
|
|
46
|
+
$and?: QueryWhereArray<E, Raw>;
|
|
47
47
|
/**
|
|
48
48
|
* joins query clauses with a logical `OR`, returns records that match any of the clauses.
|
|
49
49
|
*/
|
|
50
|
-
$or?: QueryWhereArray<E>;
|
|
50
|
+
$or?: QueryWhereArray<E, Raw>;
|
|
51
51
|
/**
|
|
52
52
|
* joins query clauses with a logical `AND`, returns records that do not match all the clauses.
|
|
53
53
|
* @see {@link QueryWhereFieldOperatorMap.$not} for per-field negation.
|
|
54
54
|
*/
|
|
55
|
-
$not?: QueryWhereArray<E>;
|
|
55
|
+
$not?: QueryWhereArray<E, Raw>;
|
|
56
56
|
/**
|
|
57
57
|
* joins query clauses with a logical `OR`, returns records that do not match any of the clauses.
|
|
58
58
|
*/
|
|
59
|
-
$nor?: QueryWhereArray<E>;
|
|
59
|
+
$nor?: QueryWhereArray<E, Raw>;
|
|
60
60
|
/**
|
|
61
61
|
* whether the specified fields match against a full-text search of the given string.
|
|
62
62
|
*/
|
|
@@ -64,11 +64,11 @@ export type QueryWhereRootOperator<E> = {
|
|
|
64
64
|
/**
|
|
65
65
|
* whether the record exists in the given sub-query.
|
|
66
66
|
*/
|
|
67
|
-
$exists?:
|
|
67
|
+
$exists?: Raw;
|
|
68
68
|
/**
|
|
69
69
|
* whether the record does not exists in the given sub-query.
|
|
70
70
|
*/
|
|
71
|
-
$nexists?:
|
|
71
|
+
$nexists?: Raw;
|
|
72
72
|
};
|
|
73
73
|
/**
|
|
74
74
|
* Per-field negation operators. `Pick`'s constraint ties this back to
|
|
@@ -100,7 +100,7 @@ export type QuerySizeComparisonOps = {
|
|
|
100
100
|
export type QueryVectorNear = QueryVectorQuery & {
|
|
101
101
|
[K in QueryOrderedOp]?: NonNullable<QueryWhereFieldOperatorMap<number>[K]>;
|
|
102
102
|
};
|
|
103
|
-
export type QueryWhereFieldOperatorMap<T> = {
|
|
103
|
+
export type QueryWhereFieldOperatorMap<T, Raw = QueryRaw> = {
|
|
104
104
|
/**
|
|
105
105
|
* whether a value is equal to the given value.
|
|
106
106
|
*/
|
|
@@ -113,7 +113,7 @@ export type QueryWhereFieldOperatorMap<T> = {
|
|
|
113
113
|
* negates the given comparison for a single field.
|
|
114
114
|
* @see {@link QueryWhereRootOperator.$not} for root-level clause negation.
|
|
115
115
|
*/
|
|
116
|
-
$not?: QueryWhereFieldValue<T>;
|
|
116
|
+
$not?: QueryWhereFieldValue<T, Raw>;
|
|
117
117
|
/**
|
|
118
118
|
* whether a value is less than the given value.
|
|
119
119
|
*/
|
|
@@ -202,7 +202,7 @@ export type QueryWhereFieldOperatorMap<T> = {
|
|
|
202
202
|
* @example { addresses: { $elemMatch: { city: 'NYC', zip: '10001' } } }
|
|
203
203
|
* @example { addresses: { $elemMatch: { city: { $like: 'New%' } } } }
|
|
204
204
|
*/
|
|
205
|
-
$elemMatch?: unknown extends T ? QueryWhereElemMatch<unknown> : NonNullable<T> extends readonly (infer U)[] ? QueryWhereElemMatch<U> : never;
|
|
205
|
+
$elemMatch?: unknown extends T ? QueryWhereElemMatch<unknown, Raw> : NonNullable<T> extends readonly (infer U)[] ? QueryWhereElemMatch<U, Raw> : never;
|
|
206
206
|
/**
|
|
207
207
|
* whether a vector is within a given distance of the query vector. `$sort` ranks by distance;
|
|
208
208
|
* this filters by it, so "the closest ten" and "everything closer than 0.35" are separate asks.
|
|
@@ -216,10 +216,10 @@ export type QueryWhereFieldOperatorMap<T> = {
|
|
|
216
216
|
* field comparison. An untyped element (`unknown`) accepts any keys but still requires the
|
|
217
217
|
* object-of-conditions shape (a bare scalar is rejected).
|
|
218
218
|
*/
|
|
219
|
-
export type QueryWhereElemMatch<U> = unknown extends U ? {
|
|
220
|
-
[key: string]: QueryWhereFieldValue<unknown> | undefined;
|
|
221
|
-
} : NonNullable<U> extends Scalar ? QueryWhereFieldOperators<NonNullable<U
|
|
222
|
-
[K in keyof NonNullable<U>]?: QueryWhereFieldValue<NonNullable<U>[K]>;
|
|
219
|
+
export type QueryWhereElemMatch<U, Raw = QueryRaw> = unknown extends U ? {
|
|
220
|
+
[key: string]: QueryWhereFieldValue<unknown, Raw> | undefined;
|
|
221
|
+
} : NonNullable<U> extends Scalar ? QueryWhereFieldOperators<NonNullable<U>, Raw> : {
|
|
222
|
+
[K in keyof NonNullable<U>]?: QueryWhereFieldValue<NonNullable<U>[K], Raw>;
|
|
223
223
|
};
|
|
224
224
|
/**
|
|
225
225
|
* Simple relational comparison operators. `Pick`'s constraint ties this back to
|
|
@@ -269,7 +269,7 @@ type QueryAllowedOp<T> = QueryCommonOp | ([NonNullable<T>] extends [QueryCompara
|
|
|
269
269
|
* The operators a field of type `T` takes. `unknown`, and a column typed as every scalar at once (a
|
|
270
270
|
* runtime-defined entity), take all of them, since nothing narrows what they hold.
|
|
271
271
|
*/
|
|
272
|
-
export type QueryWhereFieldOperators<T> = unknown extends T ? QueryWhereFieldOperatorMap<T> : IsUntypedColumn<T> extends true ? QueryWhereFieldOperatorMap<T> : Pick<QueryWhereFieldOperatorMap<T>, QueryAllowedOp<T>>;
|
|
272
|
+
export type QueryWhereFieldOperators<T, Raw = QueryRaw> = unknown extends T ? QueryWhereFieldOperatorMap<T, Raw> : IsUntypedColumn<T> extends true ? QueryWhereFieldOperatorMap<T, Raw> : Pick<QueryWhereFieldOperatorMap<T, Raw>, QueryAllowedOp<T>>;
|
|
273
273
|
/**
|
|
274
274
|
* Whether a column admits every scalar at once, which is what an entity keyed by an index signature
|
|
275
275
|
* says about all of its columns. `Scalar` is the yardstick rather than a parameter: the question is
|
|
@@ -280,9 +280,9 @@ type IsUntypedColumn<T> = [Scalar] extends [NonNullable<T>] ? true : false;
|
|
|
280
280
|
* A field's filter value: the value, `null` where it is optional, a list as an implicit `$in` (not on
|
|
281
281
|
* an array field, where it would be ambiguous), or an operator map.
|
|
282
282
|
*/
|
|
283
|
-
export type QueryWhereFieldValue<T> = T | (undefined extends T ? null : never) | (IsMany<T> extends true ? never : T[]) | QueryWhereFieldOperators<T> |
|
|
283
|
+
export type QueryWhereFieldValue<T, Raw = QueryRaw> = T | (undefined extends T ? null : never) | (IsMany<T> extends true ? never : T[]) | QueryWhereFieldOperators<T, Raw> | Raw;
|
|
284
284
|
/**
|
|
285
285
|
* query filter array - the value every {@link QueryGroupOp} takes.
|
|
286
286
|
*/
|
|
287
|
-
export type QueryWhereArray<E> = (QueryWhere<E> |
|
|
287
|
+
export type QueryWhereArray<E, Raw = QueryRaw> = (QueryWhere<E, Raw> | Raw)[];
|
|
288
288
|
export {};
|
|
@@ -2,38 +2,38 @@ import type { EntityData, EntityId, FieldKey, RelationKey, UpdatePayload, Writte
|
|
|
2
2
|
import type { QueryConflictPaths, QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, 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
|
-
import type { QuerierCountedResult, QuerierResult, QuerierTransport } from './wire.js';
|
|
5
|
+
import type { QuerierCountedResult, QuerierRaw, QuerierResult, QuerierTransport } from './wire.js';
|
|
6
6
|
/**
|
|
7
7
|
* The operations the server and the browser client declare alike, per transport `W`, options `O`,
|
|
8
8
|
* and delete options `DO`, which on the client also carry the {@link QueryOptions} it cannot pass otherwise.
|
|
9
9
|
*/
|
|
10
10
|
export interface SharedQuerier<W extends QuerierTransport, O, DO = O> {
|
|
11
11
|
/** Find the record with the given primary key. */
|
|
12
|
-
findOneById<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, id: EntityId<E>, q?: QueryOneProjected<E, S, V, X, P, C
|
|
12
|
+
findOneById<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, id: EntityId<E>, q?: QueryOneProjected<E, S, V, X, P, C, QuerierRaw<W>>, opts?: O): QuerierResult<W, QueryFindResult<E, S, V, X, P, C> | undefined>;
|
|
13
13
|
/**
|
|
14
14
|
* obtains the first record matching the given search parameters.
|
|
15
15
|
* @param entity the target entity
|
|
16
16
|
* @param q the criteria options
|
|
17
17
|
* @return the record
|
|
18
18
|
*/
|
|
19
|
-
findOne<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryOneProjected<E, S, V, X, P, C
|
|
19
|
+
findOne<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryOneProjected<E, S, V, X, P, C, QuerierRaw<W>>, opts?: O): QuerierResult<W, QueryFindResult<E, S, V, X, P, C> | undefined>;
|
|
20
20
|
/**
|
|
21
21
|
* obtains the records matching the given search parameters.
|
|
22
22
|
* @param entity the target entity
|
|
23
23
|
* @param q the criteria options
|
|
24
24
|
* @return the records
|
|
25
25
|
*/
|
|
26
|
-
findMany<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C
|
|
26
|
+
findMany<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C, QuerierRaw<W>>, opts?: O): QuerierResult<W, QueryFindResult<E, S, V, X, P, C>[]>;
|
|
27
27
|
/** Find the records matching the query, and count every match past its page. */
|
|
28
|
-
findManyAndCount<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C
|
|
28
|
+
findManyAndCount<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C, QuerierRaw<W>>, opts?: O): QuerierCountedResult<W, QueryFindResult<E, S, V, X, P, C>>;
|
|
29
29
|
/** Count the records matching the filter, or those a page of them takes. */
|
|
30
|
-
count<E extends object>(entity: Type<E>, q?: QueryPage<E
|
|
30
|
+
count<E extends object>(entity: Type<E>, q?: QueryPage<E, QuerierRaw<W>>, opts?: O): QuerierResult<W, number>;
|
|
31
31
|
/** Whether any record matches: a count capped at one row, so the engine stops at the first match. */
|
|
32
|
-
exists<E extends object>(entity: Type<E>, q?: QueryFilter<E
|
|
32
|
+
exists<E extends object>(entity: Type<E>, q?: QueryFilter<E, QuerierRaw<W>>, opts?: O): QuerierResult<W, boolean>;
|
|
33
33
|
/** Update the record with the given primary key; resolves to the number of affected rows. */
|
|
34
|
-
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdatePayload<E
|
|
34
|
+
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdatePayload<E, QuerierRaw<W>>, opts?: O): QuerierResult<W, number>;
|
|
35
35
|
/** Update the records matching the query; resolves to the number of affected rows. */
|
|
36
|
-
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E
|
|
36
|
+
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E, QuerierRaw<W>>, payload: UpdatePayload<E, QuerierRaw<W>>, opts?: O): QuerierResult<W, number>;
|
|
37
37
|
/**
|
|
38
38
|
* delete or SoftDelete a record.
|
|
39
39
|
* @param entity the entity to persist on
|
|
@@ -47,7 +47,7 @@ export interface SharedQuerier<W extends QuerierTransport, O, DO = O> {
|
|
|
47
47
|
* @param q the criteria to look for the records
|
|
48
48
|
* @return the number of affected records
|
|
49
49
|
*/
|
|
50
|
-
deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E
|
|
50
|
+
deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E, QuerierRaw<W>>, opts?: DO): QuerierResult<W, number>;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
53
|
* A `querier` allows to interact with the datasource to perform persistence operations on any entity.
|
package/dist/type/wire.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { QueryRaw } from './queryRaw.js';
|
|
1
2
|
/**
|
|
2
3
|
* The envelope a wire response wraps its result in.
|
|
3
4
|
*/
|
|
@@ -16,6 +17,14 @@ export type RequestCountedSuccessResponse<E> = RequestSuccessResponse<E> & {
|
|
|
16
17
|
* client one hands back the envelope its transport wrapped it in.
|
|
17
18
|
*/
|
|
18
19
|
export type QuerierTransport = 'server' | 'client';
|
|
20
|
+
/**
|
|
21
|
+
* The `raw` SQL a transport carries. A client's query and payload travel as JSON, which a `raw` fragment
|
|
22
|
+
* is not: it would arrive as `{}`, so the client's types refuse one rather than let it leave.
|
|
23
|
+
*/
|
|
24
|
+
export type QuerierRaw<W extends QuerierTransport> = {
|
|
25
|
+
server: QueryRaw;
|
|
26
|
+
client: never;
|
|
27
|
+
}[W];
|
|
19
28
|
/**
|
|
20
29
|
* A querier method's result on a transport: `Promise<User[]>` on the server, the response envelope on
|
|
21
30
|
* the client. A map indexed by the transport, which resolves away in hovers.
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
4
|
"description": "The JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.68.
|
|
6
|
+
"version": "0.68.1",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
"repository": {
|
|
162
162
|
"type": "git",
|
|
163
163
|
"url": "git+https://github.com/rogerpadilla/uql.git",
|
|
164
|
-
"directory": "packages/
|
|
164
|
+
"directory": "packages/orm"
|
|
165
165
|
},
|
|
166
166
|
"bugs": {
|
|
167
167
|
"url": "https://github.com/rogerpadilla/uql/issues"
|