uql-orm 0.65.0 → 0.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,12 +7,12 @@
7
7
  "import type { EntityMeta, FieldKey, FieldOptions } from '../type/index.js';\n\nexport function throwPendingTransaction(): never {\n throw TypeError('pending transaction');\n}\n\nexport function throwNoPendingTransaction(): never {\n throw TypeError('not a pending transaction');\n}\n\nexport function clone<T>(value: T): T {\n if (typeof value !== 'object' || value === null) {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map((it) => clone(it)) as T;\n }\n return { ...value };\n}\n\n/** Whether `obj` has at least one enumerable key. Narrows away `undefined`/`null` for callers. */\nexport function hasKeys<T>(obj: T): obj is NonNullable<T> {\n if (typeof obj !== 'object' || obj === null) return false;\n for (const _ in obj) return true;\n return false;\n}\n\n/**\n * Whether any enumerable key of `obj` satisfies `pred`, short-circuiting on the first match\n * without materializing a key array (unlike `Object.keys(obj).some(pred)`).\n */\nexport function someKey<T extends object>(obj: T, pred: (key: keyof T & string) => boolean): boolean {\n for (const key in obj) {\n if (pred(key)) return true;\n }\n return false;\n}\n\n/** Whether any enumerable value of `obj` satisfies `pred`, short-circuiting like {@link someKey}. */\nexport function someValue(obj: object, pred: (value: unknown) => boolean): boolean {\n return someKey(obj, (key) => pred((obj as Record<string, unknown>)[key]));\n}\n\nconst isOperatorKey = (key: string) => key.startsWith('$');\n\n/**\n * Whether `value` is a non-empty object whose keys are query/update operators (`$eq`, `$push`, ...).\n * The single source of this test: the SQL dialects, the MongoDB dialect and the `$elemMatch` walker\n * all classify operator objects with it, and they used to disagree about `{}`.\n */\nexport function isOperatorObject(value: unknown): value is Record<string, unknown> {\n return hasKeys(value) && !Array.isArray(value) && someKey(value, isOperatorKey);\n}\n\n/** Whether every key of the non-empty object `value` is an operator (no plain field names mixed in). */\nexport function isOperatorOnlyObject(value: unknown): value is Record<string, unknown> {\n return hasKeys(value) && !Array.isArray(value) && !someKey(value, (key) => !isOperatorKey(key));\n}\n\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): (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",
8
8
  "export function kebabCase(val: string): string {\n let resp = val.charAt(0).toLowerCase();\n for (let i = 1; i < val.length; ++i) {\n resp += val[i] === val[i].toUpperCase() ? '-' + val[i].toLowerCase() : val[i];\n }\n return resp;\n}\n\nexport function upperFirst(text: string): string {\n 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 === null || val === undefined) return val as string;\n if (!val) return '';\n let resp = val.charAt(0).toLowerCase();\n for (let i = 1; i < val.length; ++i) {\n const char = val[i];\n const charLower = char.toLowerCase();\n if (char !== charLower && char === char.toUpperCase()) {\n resp += '_' + charLower;\n } else {\n resp += char;\n }\n }\n return resp;\n}\n\n/**\n * Convert a string to PascalCase (UpperCamelCase).\n * @example 'user_profile' -> 'UserProfile'\n * @example 'some-text' -> 'SomeText'\n */\nexport function pascalCase(str: string): string {\n if (!str) return '';\n return str\n .split(/[_\\s-]+/)\n .map((word) => {\n // Lower-casing the rest is only right for a word that carries no case of its own: it turns\n // `USER_ID` into `UserId`, but it also turns `tenantId` into `Tenantid`.\n const rest = word === word.toUpperCase() ? word.slice(1).toLowerCase() : word.slice(1);\n return word.charAt(0).toUpperCase() + rest;\n })\n .join('');\n}\n\n/**\n * Convert a string to camelCase.\n * @example 'user_profile' -> 'userProfile'\n * @example 'SomeText' -> 'someText'\n */\nexport function camelCase(str: string): string {\n const pascal = pascalCase(str);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\n/**\n * Simple singularize function for English words.\n * @example 'users' -> 'user'\n * @example 'categories' -> 'category'\n */\nexport function singularize(name: string): string {\n if (!name) return '';\n if (name.endsWith('ies')) {\n return name.slice(0, -3) + 'y';\n }\n if (name.endsWith('ses') || name.endsWith('xes') || name.endsWith('zes')) {\n return name.slice(0, -2);\n }\n if (name.endsWith('s') && !name.endsWith('ss')) {\n return name.slice(0, -1);\n }\n return name;\n}\n\n/**\n * Simple pluralize function for English words.\n * @example 'user' -> 'users'\n * @example 'category' -> 'categories'\n */\nexport function pluralize(name: string): string {\n if (!name) return '';\n if (name.endsWith('y') && name.length > 1 && !/[aeiou]/.test(name[name.length - 2])) {\n return name.slice(0, -1) + 'ies';\n }\n if (name.endsWith('s') || name.endsWith('x') || name.endsWith('z') || name.endsWith('ch') || name.endsWith('sh')) {\n return name + 'es';\n }\n return name + 's';\n}\n",
9
9
  "import { type 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, IdKey, JsonFieldPaths, RelationKey, RelationTarget, WrittenId } from './entity.js';\nimport type { QueryLock } from './queryLock.js';\nimport type { QueryRaw } from './queryRaw.js';\nimport type { QueryWhere } from './queryWhere.js';\nimport type { BooleanLike, Except, IsMany, PrimaryKey } from './utility.js';\nimport type { QueryVectorSearch } from './vector.js';\n\nexport type QueryOptions = {\n /**\n * Toggle named entity filters for this query. `false` disables all filters;\n * `{ softDelete: false }` disables one; `{ myFilter: true }` force-enables a `default: false` filter.\n * Security filters cannot be disabled here.\n */\n filters?: false | Record<string, boolean>;\n /**\n * Delete only: physically remove rows instead of soft-deleting, ignoring the soft-delete filter so\n * already-deleted rows are removed too. No effect on entities without a soft-delete field.\n */\n hardDelete?: boolean;\n /**\n * prefix the query with this.\n */\n prefix?: string;\n /**\n * automatically infer the prefix for the query.\n */\n autoPrefix?: boolean;\n};\n\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 * Ambient per-request context (e.g. `{ tenantId, userId, roles }`) resolved by parameterized\n * filters. Set with `withContext(ctx, cb)`. It's an `interface` (not a type alias) so you can type\n * your keys once via declaration merging and get them typed wherever context is read:\n *\n * ```ts\n * declare module 'uql-orm' {\n * interface UqlContext { tenantId: number; userId: string }\n * }\n * ```\n */\nexport interface UqlContext {\n [key: string]: unknown;\n}\n\n/**\n * A filter's `$where` fragment: a plain fragment, or a function of the ambient {@link UqlContext}.\n * Return `undefined` when the condition can't resolve (see {@link FilterOptions.onMissing}).\n */\nexport type 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 * Row-level-security filter: always applied (ignores `QueryOptions.filters` bypass) and\n * AND-merged so a client `$where` on the same field can't override it.\n */\n readonly security?: boolean;\n /** What to do when {@link where} returns `undefined`. Defaults to `skip`, or `throw` for `security`. */\n readonly onMissing?: FilterOnMissing;\n};\n\n/**\n * direction for the sort.\n */\nexport type QuerySortDirection = -1 | 1 | 'asc' | 'desc';\n\n/**\n * Accepted value for a field in `$sort` - either a direction or a vector similarity search.\n */\nexport type QuerySortValue = QuerySortDirection | QueryVectorSearch;\n\n/**\n * To-one relations only: a parent holds many rows of a to-many, so there is no single value to order\n * it by, and joining one in would duplicate the parent instead. Order those inside `$populate`.\n */\ntype ToOneRelationKey<E> = { [K in RelationKey<E>]: IsMany<E[K]> extends true ? never : K }[RelationKey<E>];\n\n/** The relation names a parent holds many rows of, which a populated query fills with a list. */\ntype ToManyRelationKey<E> = Exclude<RelationKey<E>, ToOneRelationKey<E>>;\n\n/**\n * 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 * sort by map - supports field keys, JSON dot-notation paths (restricted to real JSON fields,\n * like `QueryWhere`), relation sort via nested objects, and vector similarity search on\n * `number[]` fields. `Vector` is what confines a vector search to the level the statement ranks:\n * the queried entity. A relation of it is joined in one row at a time, so there is nothing to rank\n * there - the SQL dialects throw, and MongoDB would quietly drop it, so this is its only guard.\n *\n * One mapped type over the three key sets rather than three intersected. The sets are disjoint - a\n * JSON path is dotted, and a field key cannot also be a relation key - and an assignability check\n * against an intersection is repeated per constituent, which made this the single most expensive\n * type in the package to check.\n */\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 * take a row-level lock on the rows this query returns (`SELECT ... FOR UPDATE`). Needs an open\n * transaction: outside one the statement commits and drops the lock before the caller can act on\n * the rows, so it is rejected rather than emitted. Locks only the queried entity, never anything\n * reached through `$populate`. SQL only; MongoDB and the SQLite family reject it.\n *\n * Declared here rather than on {@link QuerySearch}, which `update`/`delete` take: that placement\n * is what keeps the clause off those statements at the type level.\n */\n $lock?: QueryLock;\n\n /**\n * how many candidates an approximate-nearest-neighbour index explores before ranking, for a vector\n * search. Higher trades speed for recall; the default is whatever the engine's own is, which is\n * tuned for speed. Ignored where the search is exact (SQLite, libSQL and Turso scan every row) and\n * where the field carries no ANN index, since there is nothing to widen.\n *\n * The units are the index's, not UQL's, so the number is not comparable across index types: it\n * becomes `hnsw.ef_search` or `ivfflat.probes` on Postgres, `mhnsw_ef_search` on MariaDB, and\n * `numCandidates` on MongoDB Atlas. On Postgres it needs an open transaction, since a `SET LOCAL`\n * outside one applies to nothing.\n */\n $candidates?: number;\n\n // `$where`, `$skip` and `$limit` are declared here rather than intersected in from\n // {@link QueryFilter} and {@link QueryPager}: an assignability check against an intersection is\n // repeated per constituent, and every query in a consuming codebase pays that. The two shapes are\n // pinned together in `queryStatementClauses.test-d.ts` so the copies cannot drift.\n\n /**\n * filtering options.\n */\n $where?: QueryWhere<E>;\n\n /**\n * Index from where start the search\n */\n $skip?: number;\n\n /**\n * Max number of records to retrieve\n */\n $limit?: number;\n};\n\n/**\n * `Query`'s clauses grouped by the shape of their value - what a parser reading one off the wire and\n * a validator checking a relation's own query both need, and what each used to enumerate for itself.\n * Declared beside the type they describe so the two cannot drift, and `satisfies` fails the build\n * rather than the runtime if a clause is ever renamed.\n *\n * `$lock` is only in {@link QUERY_STATEMENT_CLAUSES}: neither a wire query nor a relation's query\n * accepts it.\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 decide a row's shape, captured from the query as written: the field names\n * `$select` and `$exclude` list, the value those maps carry (a falsy one subtracts instead of\n * selecting, as it does at runtime, and a widened map is how a projection that is not statically\n * known announces itself), and the relation names `$populate` lists.\n *\n * Each is captured as a *key set* rather than as the map itself, which is what keeps the checks\n * intact: TypeScript skips excess-property checking on a naked type parameter, so a captured map\n * would take a typo'd key without a word, while a captured key set makes that typo fail its own\n * `FieldKey<E>` / `RelationKey<E>` constraint. Every other clause - `$where`, `$sort`, and each\n * populated relation's own query - stays the concrete {@link Query} it is today.\n * @internal\n */\ntype QueryProjection<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n> = {\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, mirroring what the runtime projects: the fields a positive\n * `$select` names, or every field minus what a falsy `$select` entry or a truthy `$exclude` entry\n * subtracts, plus the relations `$populate` asked for. A positive `$select` wins outright, which is\n * why `$exclude` is only read on the branch where there is none.\n * @internal\n */\ntype ProjectedKeys<E, S, V, X, P> =\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 row of a find result: the entity narrowed to the fields the query projected, plus the relations\n * it populated - reading anything the query left out is a compile error rather than a silent\n * `undefined`. Modifiers are preserved, so an optional field stays optional. Name a projected row\n * with it where a helper has to take one: `QueryFindResult<User, 'id' | 'name'>`.\n *\n * The entity itself when the query projects nothing, when it uses a raw-projection array (columns,\n * not fields), and when the projection is not uniform - a `Query<E>` built elsewhere, or a map\n * mixing selected and subtracted entries, whose positive keys inference cannot recover. Relations\n * keep their declared type: narrowing them means capturing their queries as maps, which costs those\n * queries their own checks.\n */\nexport type QueryFindResult<\n E,\n S extends FieldKey<E> = never,\n // A whitelist by default, so the hand-written form reads `QueryFindResult<User, 'id' | 'name'>`.\n V = true,\n X extends FieldKey<E> = never,\n P extends RelationKey<E> = never,\n C extends RelationKey<E> = never,\n> = QueryProjectedRow<E, S, V, X, P, C> & CountedRelations<C>;\n\n/**\n * The `_count` a query asked for, or an inert intersection member when it asked for none - so a read\n * without `$count` keeps exactly the row type it had.\n */\ntype CountedRelations<C extends PropertyKey> = [C] extends [never]\n ? unknown\n : { [K in typeof COUNT_RESULT_KEY]: { [R in C]: number } };\n\n/** @internal */\ntype QueryProjectedRow<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n> = [S | X] extends [never]\n ? E\n : IsUniform<V> extends true\n ? [PopulatedToMany<E, P>] extends [never]\n ? // `Pick`, not a key remap: an entity keyed by an index signature - a content type defined at\n // runtime - has `string` for its keys, and a remap keeps no literal one, so every projection\n // over one came back as `{}`.\n Pick<E, ProjectedKeys<E, S, V, X, P> & 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/**\n * What upserting one row reports, against the entity rather than the driver.\n *\n * `created` is here and not on {@link QueryUpsertManyResult} because it is only ever knowable for a\n * single statement: a batch's `affectedRows` is a weighted sum on the dialects that report one at\n * all, and a batch of mixed shapes is several statements.\n */\nexport type QueryUpsertOneResult<E> = {\n readonly id?: WrittenId<E>;\n readonly changes?: number;\n /** Whether the record was created (`true`) or updated (`false`), where the dialect can tell. */\n readonly created?: boolean;\n};\n\n/**\n * What upserting many rows reports. `ids` is payload-aligned like an insert's, so it zips with the\n * rows that were passed, and carries a composite key as the map naming it.\n */\nexport type QueryUpsertManyResult<E> = {\n readonly ids: (WrittenId<E> | undefined)[];\n readonly changes?: number;\n};\n\n/**\n * result of an update operation, as the driver reports it - which is what `run` hands back, where\n * there is no entity to name the ids against. The `QueryUpsert*Result` pair is the entity-level shape.\n */\nexport type QueryUpdateResult = {\n /**\n * number of affected records.\n */\n changes?: number;\n /**\n * the IDs the statement reported, in payload order, `undefined` where it reported none for that\n * row - a MongoDB upsert names only the documents it inserted. Exact on `'returning'` dialects;\n * inferred from the driver header on the others (see {@link InsertIdSource}), and absent\n * altogether when the header reports nothing.\n */\n ids?: (PrimaryKey | undefined)[];\n /**\n * first inserted ID.\n */\n firstId?: PrimaryKey;\n /**\n * whether the record was created (`true`) or updated (`false`).\n * `undefined` when the dialect cannot determine this (e.g. SQLite).\n */\n created?: boolean;\n};\n",
10
+ "import type { FieldKey, IdKey, JsonFieldPaths, RelationKey, RelationTarget, WrittenId } from './entity.js';\nimport type { QueryLock } from './queryLock.js';\nimport type { QueryRaw } from './queryRaw.js';\nimport type { QueryWhere } from './queryWhere.js';\nimport type { BooleanLike, Except, IsMany, PrimaryKey } from './utility.js';\nimport type { QueryVectorSearch } from './vector.js';\n\nexport type QueryOptions = {\n /**\n * Toggle named entity filters for this query. `false` disables all filters;\n * `{ softDelete: false }` disables one; `{ myFilter: true }` force-enables a `default: false` filter.\n * Security filters cannot be disabled here.\n */\n filters?: false | Record<string, boolean>;\n /**\n * Delete only: physically remove rows instead of soft-deleting, ignoring the soft-delete filter so\n * already-deleted rows are removed too. No effect on entities without a soft-delete field.\n */\n hardDelete?: boolean;\n /**\n * prefix the query with this.\n */\n prefix?: string;\n /**\n * automatically infer the prefix for the query.\n */\n autoPrefix?: boolean;\n};\n\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 * Ambient per-request context (e.g. `{ tenantId, userId, roles }`) resolved by parameterized\n * filters. Set with `withContext(ctx, cb)`. It's an `interface` (not a type alias) so you can type\n * your keys once via declaration merging and get them typed wherever context is read:\n *\n * ```ts\n * declare module 'uql-orm' {\n * interface UqlContext { tenantId: number; userId: string }\n * }\n * ```\n */\nexport interface UqlContext {\n [key: string]: unknown;\n}\n\n/**\n * A filter's `$where` fragment: a plain fragment, or a function of the ambient {@link UqlContext}.\n * Return `undefined` when the condition can't resolve (see {@link FilterOptions.onMissing}).\n */\nexport type 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 * sort by map - supports field keys, JSON dot-notation paths (restricted to real JSON fields,\n * like `QueryWhere`), relation sort via nested objects, and vector similarity search on\n * `number[]` fields. `Vector` is what confines a vector search to the level the statement ranks:\n * the queried entity. A relation of it is joined in one row at a time, so there is nothing to rank\n * there - the SQL dialects throw, and MongoDB would quietly drop it, so this is its only guard.\n *\n * One mapped type over the three key sets rather than three intersected. The sets are disjoint - a\n * JSON path is dotted, and a field key cannot also be a relation key - and an assignability check\n * against an intersection is repeated per constituent, which made this the single most expensive\n * type in the package to check.\n */\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 * take a row-level lock on the rows this query returns (`SELECT ... FOR UPDATE`). Needs an open\n * transaction: outside one the statement commits and drops the lock before the caller can act on\n * the rows, so it is rejected rather than emitted. Locks only the queried entity, never anything\n * reached through `$populate`. SQL only; MongoDB and the SQLite family reject it.\n *\n * Declared here rather than on {@link QuerySearch}, which `update`/`delete` take: that placement\n * is what keeps the clause off those statements at the type level.\n */\n $lock?: QueryLock;\n\n /**\n * how many candidates an approximate-nearest-neighbour index explores before ranking, for a vector\n * search. Higher trades speed for recall; the default is whatever the engine's own is, which is\n * tuned for speed. Ignored where the search is exact (SQLite, libSQL and Turso scan every row) and\n * where the field carries no ANN index, since there is nothing to widen.\n *\n * The units are the index's, not UQL's, so the number is not comparable across index types: it\n * becomes `hnsw.ef_search` or `ivfflat.probes` on Postgres, `mhnsw_ef_search` on MariaDB, and\n * `numCandidates` on MongoDB Atlas. On Postgres it needs an open transaction, since a `SET LOCAL`\n * outside one applies to nothing.\n */\n $candidates?: number;\n\n // `$where`, `$skip` and `$limit` are declared here rather than intersected in from\n // {@link QueryFilter} and {@link QueryPager}: an assignability check against an intersection is\n // repeated per constituent, and every query in a consuming codebase pays that. The two shapes are\n // pinned together in `queryStatementClauses.test-d.ts` so the copies cannot drift.\n\n /**\n * filtering options.\n */\n $where?: QueryWhere<E>;\n\n /**\n * Index from where start the search\n */\n $skip?: number;\n\n /**\n * Max number of records to retrieve\n */\n $limit?: number;\n};\n\n/**\n * `Query`'s clauses grouped by the shape of their value - what a parser reading one off the wire and\n * a validator checking a relation's own query both need, and what each used to enumerate for itself.\n * Declared beside the type they describe so the two cannot drift, and `satisfies` fails the build\n * rather than the runtime if a clause is ever renamed.\n *\n * `$lock` is only in {@link QUERY_STATEMENT_CLAUSES}: neither a wire query nor a relation's query\n * accepts it.\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 decide a row's shape, captured from the query as written: the field names\n * `$select` and `$exclude` list, the value those maps carry (a falsy one subtracts instead of\n * selecting, as it does at runtime, and a widened map is how a projection that is not statically\n * known announces itself), and the relation names `$populate` lists.\n *\n * Each is captured as a *key set* rather than as the map itself, which is what keeps the checks\n * intact: TypeScript skips excess-property checking on a naked type parameter, so a captured map\n * would take a typo'd key without a word, while a captured key set makes that typo fail its own\n * `FieldKey<E>` / `RelationKey<E>` constraint. Every other clause - `$where`, `$sort`, and each\n * populated relation's own query - stays the concrete {@link Query} it is today.\n * @internal\n */\ntype QueryProjection<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n> = {\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, mirroring what the runtime projects: the fields a positive\n * `$select` names, or every field minus what a falsy `$select` entry or a truthy `$exclude` entry\n * subtracts, plus the relations `$populate` asked for. A positive `$select` wins outright, which is\n * why `$exclude` is only read on the branch where there is none.\n * @internal\n */\ntype ProjectedKeys<E, S, V, X, P> =\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 row of a find result: the entity narrowed to the fields the query projected, plus the relations\n * it populated - reading anything the query left out is a compile error rather than a silent\n * `undefined`. Modifiers are preserved, so an optional field stays optional. Name a projected row\n * with it where a helper has to take one: `QueryFindResult<User, 'id' | 'name'>`.\n *\n * The entity itself when the query projects nothing, when it uses a raw-projection array (columns,\n * not fields), and when the projection is not uniform - a `Query<E>` built elsewhere, or a map\n * mixing selected and subtracted entries, whose positive keys inference cannot recover. Relations\n * keep their declared type: narrowing them means capturing their queries as maps, which costs those\n * queries their own checks.\n */\nexport type QueryFindResult<\n E,\n S extends FieldKey<E> = never,\n // A whitelist by default, so the hand-written form reads `QueryFindResult<User, 'id' | 'name'>`.\n V = true,\n X extends FieldKey<E> = never,\n P extends RelationKey<E> = never,\n C extends RelationKey<E> = never,\n> = QueryProjectedRow<E, S, V, X, P, C> & CountedRelations<C>;\n\n/**\n * The `_count` a query asked for, or an inert intersection member when it asked for none - so a read\n * without `$count` keeps exactly the row type it had.\n */\ntype CountedRelations<C extends PropertyKey> = [C] extends [never]\n ? unknown\n : { [K in typeof COUNT_RESULT_KEY]: { [R in C]: number } };\n\n/** @internal */\ntype QueryProjectedRow<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n> = [S | X] extends [never]\n ? E\n : IsUniform<V> extends true\n ? [PopulatedToMany<E, P>] extends [never]\n ? // `Pick`, not a key remap: an entity keyed by an index signature - a content type defined at\n // runtime - has `string` for its keys, and a remap keeps no literal one, so every projection\n // over one came back as `{}`.\n Pick<E, ProjectedKeys<E, S, V, X, P> & 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/**\n * What upserting one row reports, against the entity rather than the driver.\n *\n * `created` is here and not on {@link QueryUpsertManyResult} because it is only ever knowable for a\n * single statement: a batch's `affectedRows` is a weighted sum on the dialects that report one at\n * all, and a batch of mixed shapes is several statements.\n */\nexport type QueryUpsertOneResult<E> = {\n readonly id?: WrittenId<E>;\n readonly changes?: number;\n /** Whether the record was created (`true`) or updated (`false`), where the dialect can tell. */\n readonly created?: boolean;\n};\n\n/**\n * What upserting many rows reports. `ids` is payload-aligned like an insert's, so it zips with the\n * rows that were passed, and carries a composite key as the map naming it.\n */\nexport type QueryUpsertManyResult<E> = {\n readonly ids: (WrittenId<E> | undefined)[];\n readonly changes?: number;\n};\n\n/**\n * result of an update operation, as the driver reports it - which is what `run` hands back, where\n * there is no entity to name the ids against. The `QueryUpsert*Result` pair is the entity-level shape.\n */\nexport type QueryUpdateResult = {\n /**\n * number of affected records.\n */\n changes?: number;\n /**\n * the IDs the statement reported, in payload order, `undefined` where it reported none for that\n * row - a MongoDB upsert names only the documents it inserted. Exact on `'returning'` dialects;\n * inferred from the driver header on the others (see {@link InsertIdSource}), and absent\n * altogether when the header reports nothing.\n */\n ids?: (PrimaryKey | undefined)[];\n /**\n * first inserted ID.\n */\n firstId?: PrimaryKey;\n /**\n * whether the record was created (`true`) or updated (`false`).\n * `undefined` when the dialect cannot determine this (e.g. SQLite).\n */\n created?: boolean;\n};\n",
11
11
  "import type { Query, QueryOptions } from '../type/index.js';\n// the clause lists themselves, not the barrel: this module is in the browser bundle's graph\nimport {\n QUERY_BOOLEAN_CLAUSES,\n QUERY_NUMBER_CLAUSES,\n QUERY_OBJECT_CLAUSES,\n QUERY_ROOT_NUMBER_CLAUSES,\n QUERY_ROOT_OBJECT_CLAUSES,\n} from '../type/query.js';\n// the specific util module, not the barrel, so the browser bundle does not pull in entity metadata\nimport { getKeys, 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
12
  "import { CRUD_ROUTES, entityPath, type HttpMethod } from '../../http/contract.js';\nimport { stringifyQuery } from '../../http/query.js';\nimport type {\n EntityData,\n EntityId,\n FieldKey,\n Query,\n QueryFilter,\n QueryFindResult,\n QueryOneProjected,\n QueryOptions,\n QueryPage,\n QueryProjected,\n QuerySearch,\n RelationKey,\n RequestCountedSuccessResponse,\n RequestSuccessResponse,\n Type,\n UpdatePayload,\n WrittenId,\n} from '../../type/index.js';\nimport { isScalarId } from '../../util/object.util.js';\nimport { get, query as httpQuery, patch, post, put, remove } from '../http/index.js';\nimport type { ClientQuerier, RequestFindOptions, RequestOptions } from '../type/index.js';\n\nexport type HttpQuerierDefaults = {\n /**\n * headers sent with every request from this instance, merged under per-call headers.\n * Create one instance per request (e.g. during SSR) to scope auth headers safely.\n */\n readonly headers?: Record<string, string>;\n /**\n * transport for read queries (findOne, findMany, count). 'QUERY' (RFC 10008) sends the\n * JSON query in the request body, avoiding URL-length limits for large queries; requires\n * infrastructure (proxies, CDNs) that forwards the QUERY method. Defaults to 'GET'.\n */\n readonly readMethod?: Extract<HttpMethod, 'GET' | 'QUERY'>;\n /**\n * The URL segment an entity is addressed by, defaulting to its kebab-cased class name - the same\n * option the server handler takes, so one map serves both. State it where the default cannot: a\n * build that minifies class names renames every route.\n */\n readonly entityPath?: (entity: Type<unknown>) => string;\n};\n\n/**\n * The id as one path segment.\n *\n * A composite key has no spelling here yet - the route is `/:id`, and how several columns share one\n * segment is a serialization to invent rather than copy. Refused rather than interpolated, which\n * would have sent `[object Object]` for the server to reject. Its callers are `async` so this\n * surfaces as a rejection, like every other failure they can hand back.\n */\nfunction idSegment<E>(entity: Type<E>, id: EntityId<E>): string {\n if (!isScalarId(id)) {\n throw new TypeError(`'${entity.name}' was addressed by an id object, which the HTTP route cannot carry.`);\n }\n return String(id);\n}\n\nexport class HttpQuerier implements ClientQuerier {\n constructor(\n readonly basePath: string,\n readonly defaults: HttpQuerierDefaults = {},\n ) {}\n\n async findOneById<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n id: EntityId<E>,\n q?: QueryOneProjected<E, S, V, X, P, C>,\n opts?: RequestOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C> | undefined>> {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(q);\n return get<QueryFindResult<E, S, V, X, P, C> | undefined>(\n `${basePath}/${idSegment(entity, id)}${qs}`,\n this.buildOptions(opts),\n );\n }\n\n findOne<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n q: QueryOneProjected<E, S, V, X, P, C>,\n opts?: RequestOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C> | undefined>> {\n return this.read<QueryFindResult<E, S, V, X, P, C> | undefined>(\n `${this.getBasePath(entity)}${CRUD_ROUTES.findOne.path}`,\n q,\n opts,\n );\n }\n\n findMany<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n q: QueryProjected<E, S, V, X, P, C>,\n opts?: RequestFindOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>> {\n const data: Query<E> & { count?: boolean } = { ...q };\n if (opts?.count) {\n data.count = true;\n }\n return this.read<QueryFindResult<E, S, V, X, P, C>[]>(this.getBasePath(entity), data, opts);\n }\n\n async findManyAndCount<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n q: QueryProjected<E, S, V, X, P, C>,\n opts?: RequestFindOptions,\n ): Promise<RequestCountedSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>> {\n const response = await this.findMany(entity, q, { ...opts, count: true });\n if (typeof response.count !== 'number') {\n throw new TypeError('findManyAndCount response has an invalid count');\n }\n return { ...response, count: response.count };\n }\n\n count<E extends object>(entity: Type<E>, q?: QueryPage<E>, opts?: RequestOptions) {\n return this.read<number>(`${this.getBasePath(entity)}${CRUD_ROUTES.count.path}`, q, opts);\n }\n\n /** The `count` route capped at one row, so existence needs no endpoint of its own. */\n async exists<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: RequestOptions) {\n const res = await this.count(entity, { ...q, $limit: 1 }, opts);\n return { ...res, data: res.data > 0 };\n }\n\n insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return post<WrittenId<E> | undefined>(basePath, payload, this.buildOptions(opts));\n }\n\n insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return post<(WrittenId<E> | undefined)[]>(\n `${basePath}${CRUD_ROUTES.insertMany.path}`,\n payload,\n this.buildOptions(opts),\n );\n }\n\n async updateOneById<E extends object>(\n entity: Type<E>,\n id: EntityId<E>,\n payload: UpdatePayload<E>,\n opts?: RequestOptions,\n ) {\n const basePath = this.getBasePath(entity);\n return patch<number>(`${basePath}/${idSegment(entity, id)}`, payload, this.buildOptions(opts));\n }\n\n updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(q);\n return patch<number>(`${basePath}${qs}`, payload, this.buildOptions(opts));\n }\n\n saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>, opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return put<WrittenId<E> | undefined>(basePath, payload, this.buildOptions(opts));\n }\n\n saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[], opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return put<(WrittenId<E> | undefined)[]>(\n `${basePath}${CRUD_ROUTES.saveMany.path}`,\n payload,\n this.buildOptions(opts),\n );\n }\n\n async deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts: QueryOptions & RequestOptions = {}) {\n const basePath = this.getBasePath(entity);\n const qs = opts.hardDelete ? stringifyQuery({ hardDelete: opts.hardDelete }) : '';\n return remove<number>(`${basePath}/${idSegment(entity, id)}${qs}`, this.buildOptions(opts));\n }\n\n deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts: QueryOptions & RequestOptions = {}) {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(opts.hardDelete ? { ...q, hardDelete: opts.hardDelete } : q);\n return remove<number>(`${basePath}${qs}`, this.buildOptions(opts));\n }\n\n getBasePath<E>(entity: Type<E>) {\n return `${this.basePath}/${(this.defaults.entityPath ?? entityPath)(entity)}`;\n }\n\n protected read<T>(path: string, q: Record<string, unknown> | undefined, opts?: RequestOptions) {\n if (this.defaults.readMethod === 'QUERY') {\n return httpQuery<T>(path, q ?? {}, this.buildOptions(opts));\n }\n return get<T>(`${path}${stringifyQuery(q)}`, this.buildOptions(opts));\n }\n\n protected buildOptions(opts?: RequestOptions): RequestOptions | undefined {\n if (!this.defaults.headers && !opts?.headers) {\n return opts;\n }\n return { ...opts, headers: { ...this.defaults.headers, ...opts?.headers } };\n }\n}\n",
13
13
  "import { HttpQuerier } from './querier/httpQuerier.js';\nimport type { ClientQuerier, ClientQuerierPool } from './type/index.js';\n\nlet defaultPool: ClientQuerierPool = {\n getQuerier: () => new HttpQuerier('/api'),\n};\n\nexport function setQuerierPool<T extends ClientQuerierPool>(pool: T) {\n defaultPool = pool;\n}\n\nexport function getQuerierPool(): ClientQuerierPool {\n return defaultPool;\n}\n\nexport function getQuerier(): ClientQuerier {\n return getQuerierPool().getQuerier();\n}\n"
14
14
  ],
15
- "mappings": "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",
15
+ "mappings": "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",
16
16
  "debugId": "8A0DBFD4D82E137D64756E2164756E21",
17
17
  "names": []
18
18
  }
@@ -1,4 +1,4 @@
1
- import type { EntityIndexColumnInput, EntityIndexOptions, EntityOptions, FilterOptions, RefMap, Type } from '../../type/index.js';
1
+ import type { EntityIndexColumnInput, EntityIndexOptions, EntityOptions, FilterName, FilterOptions, RefMap, Type } from '../../type/index.js';
2
2
  /**
3
3
  * Marks a class as an entity and finalizes its metadata.
4
4
  *
@@ -8,13 +8,13 @@ import type { EntityIndexColumnInput, EntityIndexOptions, EntityOptions, FilterO
8
8
  * here would find only what the base class left behind. `defineEntity` reads it off the class instead,
9
9
  * which is correct for the imperative path because it runs later still.
10
10
  */
11
- export declare function Entity<E>(opts?: EntityOptions<E>): (entity: Type<E>, context?: ClassDecoratorContext) => void;
11
+ export declare function Entity<E>(opts?: NoInfer<EntityOptions<E>>): (entity: Type<E>, context?: ClassDecoratorContext) => void;
12
12
  /**
13
13
  * Registers a named `$where` filter, applied to every query unless bypassed via `QueryOptions.filters`.
14
14
  *
15
15
  * @example `@Filter('active', { where: { status: 'active' }, default: false })`
16
16
  */
17
- export declare function Filter<E>(name: string, opts: FilterOptions<E>): (entity: Type<E>) => void;
17
+ export declare function Filter<E, N extends string>(name: FilterName<N>, opts: FilterOptions<E>): (entity: Type<E>) => void;
18
18
  /**
19
19
  * Declares a composite index, its columns read off the entity's refs, so `@Index((user) => [user.nope])`
20
20
  * does not compile and a rename reaches every column. Stacks, so several may sit above one class.
@@ -1,4 +1,4 @@
1
- import type { EntityGetter, FieldOptions, FieldType, IdValue, NamedIdKey, RelationManyToManyOptions, RelationManyToOneOptions, RelationOneToManyOptions, RelationOneToOneOptions, TsTypeOf } from '../../type/index.js';
1
+ import type { EntityGetter, FieldOptions, FieldType, HasCompositeKey, IdValue, NamedIdKey, RelationManyToManyOptions, RelationManyToOneOptions, RelationOneToManyOptions, RelationOneToOneOptions, TsTypeOf } from '../../type/index.js';
2
2
  import type { RejectIncompatible } from '../../util/index.js';
3
3
  /** A member decorator that also constrains the property it may be applied to, on a class `O`. */
4
4
  type MemberDecorator<V, O = unknown> = (value: undefined, context: ClassFieldDecoratorContext<O, V>) => void;
@@ -21,7 +21,9 @@ type DeclaredValue<O> = O extends {
21
21
  readonly enum: infer E extends readonly unknown[];
22
22
  } ? EnumValue<Extract<E[number], TsTypeOf<T>>, TsTypeOf<T>> : TsTypeOf<T> : O extends {
23
23
  readonly references: EntityGetter<infer E>;
24
- } ? IdValue<E> : never;
24
+ } ? HasCompositeKey<E> extends true ? {
25
+ readonly __compositeKeyNeedsAColumnPerKey: true;
26
+ } : IdValue<E> : never;
25
27
  /**
26
28
  * The enum's members, or a named complaint when they widened.
27
29
  *
@@ -1,4 +1,4 @@
1
- import type { EntityData, EntityIndexInput, EntityMembers, EntityMeta, EntityOptions, FieldMeta, FieldOptions, FilterOptions, HookEvent, IdKey, RelationKey, RelationMeta, RelationOptions, RelationRegistration, Type, WrittenId } from '../../type/index.js';
1
+ import type { EntityData, EntityIndexInput, EntityMembers, EntityMeta, EntityOptions, FieldMeta, FieldOptions, FilterName, FilterOptions, HookEvent, IdKey, RelationKey, RelationMeta, RelationOptions, RelationRegistration, Type, WrittenId } from '../../type/index.js';
2
2
  export declare function defineField<E>(entity: Type<E>, key: string, opts?: FieldOptions): EntityMeta<E>;
3
3
  export declare function defineId<E>(entity: Type<E>, key: string, opts: FieldOptions): EntityMeta<E>;
4
4
  /** `T` is the relation's target, independent of the owner `E`. */
@@ -15,7 +15,7 @@ export declare function defineHook<E>(entity: Type<E>, methodName: string, event
15
15
  * sugar are normalized here, which is what lets the dialects render one shape instead of re-parsing it.
16
16
  */
17
17
  export declare function defineIndex<E>(entity: Type<E>, index: EntityIndexInput<E>): EntityMeta<E>;
18
- export declare function defineFilter<E>(entity: Type<E>, name: string, opts: FilterOptions<E>): EntityMeta<E>;
18
+ export declare function defineFilter<E, N extends string>(entity: Type<E>, name: FilterName<N>, opts: FilterOptions<E>): EntityMeta<E>;
19
19
  /**
20
20
  * Feeds fields, relations and hooks into the `define*` primitives, so the decorators and the imperative
21
21
  * API converge on one registration path before anything is finalized.
@@ -71,6 +71,6 @@ export declare function getMeta<E>(entity: Type<E>): EntityMeta<E>;
71
71
  /**
72
72
  * The foreign keys an entity holds: each owning to-one's columns, and each `@Field({ references })` no
73
73
  * relation joins on, as the many-to-one it describes, once its target has registered a key. What the
74
- * schema build constrains and a junction joins by.
74
+ * schema build constrains and a junction joins by, settling the relations holding them first.
75
75
  */
76
76
  export declare function foreignKeysOf<E>(meta: EntityMeta<E>): RelationMeta[];
@@ -1,6 +1,6 @@
1
1
  import { SOFT_DELETE_FILTER } from '../../type/index.js';
2
2
  import { isInlinedExpression } from '../../util/field.util.js';
3
- import { entitySql, entityWhere, fieldOptionConflict, getKeys, hasKeys, isToManyRelation, lowerFirst, memberRefs, normalizeIndexColumn, upperFirst, definedEntries, } from '../../util/index.js';
3
+ import { entitySql, entityWhere, fieldOptionConflict, getKeys, hasKeys, isToManyRelation, memberRefs, normalizeIndexColumn, definedEntries, } from '../../util/index.js';
4
4
  import { ownRegistrations } from '../decorator/bag.js';
5
5
  /**
6
6
  * A map held on `globalThis` through the global symbol registry, so a single one survives multiple
@@ -108,7 +108,9 @@ export function defineFilter(entity, name, opts) {
108
108
  if (name === SOFT_DELETE_FILTER) {
109
109
  throw TypeError(`'${entity.name}' filter name '${SOFT_DELETE_FILTER}' is reserved; it is auto-registered from @Field({ softDelete })`);
110
110
  }
111
- if (opts.security && opts.onMissing === 'skip') {
111
+ // Widened for a caller the types did not reach, which is the only one this can refuse.
112
+ const { security, onMissing } = opts;
113
+ if (security && onMissing === 'skip') {
112
114
  throw TypeError(`'${entity.name}' security filter '${name}' cannot use onMissing: 'skip' (it must fail closed)`);
113
115
  }
114
116
  (meta.filters ??= {})[name] = opts;
@@ -306,114 +308,85 @@ function ensureMeta(entity) {
306
308
  return meta;
307
309
  }
308
310
  export function getMeta(entity) {
311
+ const meta = registeredMeta(entity);
312
+ // Stamped once finalizing succeeds, so a read after a failure reports the same mistake again. Finalizing
313
+ // reads other entities without resolving them, so no entity is ever read half resolved.
314
+ if (meta.processedAt !== meta.revision) {
315
+ fillRelations(meta);
316
+ meta.processedAt = meta.revision;
317
+ }
318
+ return meta;
319
+ }
320
+ /** The metadata `entity` registered, however much of it is resolved. */
321
+ function registeredMeta(entity) {
309
322
  const meta = metas.get(entity);
310
323
  if (!meta) {
311
324
  throw TypeError(`'${entity.name}' is not an entity`);
312
325
  }
313
- if (meta.processedAt === meta.revision) {
314
- return meta;
315
- }
316
- // Stamped before finalizing: `fillInverseSide` reads the other side through `getMeta`, and with each
317
- // side mapped by the other that recursion has to find this half-filled meta rather than run again.
318
- // Unstamped when finalizing throws, so the next read reports the same mistake; running it again is
319
- // harmless, since every step skips what it settled.
320
- meta.processedAt = meta.revision;
321
- try {
322
- return fillRelations(meta);
323
- }
324
- catch (error) {
325
- meta.processedAt = undefined;
326
- throw error;
327
- }
326
+ return meta;
328
327
  }
329
328
  function fillRelations(meta) {
330
329
  for (const [relKey, relation] of definedEntries(meta.relations)) {
331
- // The registered view: `references` may be unset, or the one column a to-one names, until this settles it.
332
- const relOpts = relation;
333
330
  const at = `'${meta.entity.name}.${relKey}'`;
334
- const references = relOpts.mappedBy
335
- ? fillInverseSide(at, meta, relOpts, relOpts.mappedBy)
336
- : (pairedReferences(at, relOpts) ?? fillOwningSide(at, meta, relKey, relOpts));
331
+ const references = settledReferences(at, meta, relKey, relation);
337
332
  if (!references.length) {
338
333
  throw new TypeError(`${at} has no columns to join on.`);
339
334
  }
335
+ if (!relation.through) {
336
+ assertJoins(at, meta, relation, references);
337
+ }
340
338
  }
341
339
  // A column `references` names is a foreign key with or without a relation over it, and one cannot point
342
340
  // at a composite key: refused on first read, as a relation that cannot join is, not at the schema build.
343
341
  foreignKeysOf(meta);
344
- return meta;
345
342
  }
346
343
  /**
347
- * The pairs a relation joins on, with the one column a to-one names paired with the target's primary key:
348
- * on first read rather than at registration, which can run before the target has a key to pair with.
344
+ * The pairs a relation joins on, settled on first read, whether its own entity is being resolved or another
345
+ * needs it. Never resolving an entity is what keeps it from recursing, and the one column a to-one names is
346
+ * paired with its target's key only here, since registration can run before the target has one.
349
347
  */
350
- function pairedReferences(at, relOpts) {
351
- const { references } = relOpts;
352
- if (typeof references !== 'string') {
353
- return references;
354
- }
355
- const target = ensureMeta(relOpts.entity());
356
- if (relOpts.mappedBy || isToManyRelation(relOpts) || target.ids.length > 1) {
357
- throw new TypeError(`${at} names one column, '${references}', which only a to-one holding a foreign key to a one-column key ` +
358
- 'can: pair the columns, [{ local, foreign }].');
359
- }
360
- relOpts.references = [{ local: references, foreign: soleIdOf(target, 'a foreign key') }];
361
- return relOpts.references;
362
- }
363
- function fillOwningSide(at, meta, relKey, relOpts) {
364
- const relMeta = ensureMeta(relOpts.entity());
365
- if (relOpts.through) {
366
- // Both columns live on the junction, whatever the cardinality: `deleteRelations` and every dialect
367
- // read them as junction columns. A composite key contributes one pair per column of it, which is
368
- // what makes the join address a whole key rather than part.
369
- const junction = getMeta(relOpts.through());
370
- relOpts.references = [...junctionReferences(at, junction, meta), ...junctionReferences(at, junction, relMeta)];
348
+ function settledReferences(at, meta, relKey, relOpts) {
349
+ const { references, mappedBy, through } = relOpts;
350
+ if (typeof references === 'string') {
351
+ const target = ensureMeta(relOpts.entity());
352
+ if (mappedBy || isToManyRelation(relOpts) || target.ids.length > 1) {
353
+ throw new TypeError(`${at} names one column, '${references}', which only a to-one holding a foreign key to a one-column key ` +
354
+ 'can: pair the columns, [{ local, foreign }].');
355
+ }
356
+ relOpts.references = [{ local: references, foreign: soleIdOf(target, 'a foreign key') }];
371
357
  return relOpts.references;
372
358
  }
373
- if (isToManyRelation(relOpts)) {
374
- throw new TypeError(`${at} is a to-many relation with no way to join: it needs 'mappedBy' (the field on the other side), ` +
375
- "'through' (a junction entity), or 'references' (the columns).");
376
- }
377
- // `<rel>Id` for the one-key case it has always been; `<rel><Key>` per column otherwise. Both name a
378
- // property, so both are spelled from the referenced *property* - a column name is what the naming
379
- // strategy makes of this afterwards.
380
- const sole = relMeta.ids.length === 1;
381
- const references = relMeta.ids.map((key) => ({
382
- local: sole ? `${relKey}Id` : `${relKey}${upperFirst(key)}`,
383
- foreign: key,
384
- }));
385
- // A column the entity declares would be joined by its name alone, so renaming either one would leave
386
- // the other behind, still compiling.
387
- const fields = meta.fields;
388
- if (references.some(({ local }) => fields[local])) {
389
- const own = lowerFirst(meta.entity.name);
390
- throw new TypeError(`${at} joins ${references.map(({ local }) => `'${local}'`).join(', ')} by name, which a rename does not ` +
391
- `follow: link them with ${sole ? `'references: (${own}) => ${own}.${references[0].local}'` : "'references' pairs"}.`);
392
- }
393
- // `typeFromReference` so schema generation resolves the referenced primary key's exact type
394
- // (columnType, length, chained keys) rather than trusting the fallback, as it does for an
395
- // explicit `@Field({ references })`.
396
- for (const { local, foreign } of references) {
397
- fields[local] = {
398
- name: local,
399
- type: fieldOf(relMeta, foreign).type ?? Number,
400
- references: relOpts.entity,
401
- referencedKey: foreign,
402
- typeFromReference: true,
403
- };
404
- }
405
- relOpts.references = references;
406
- return references;
359
+ if (references)
360
+ return references;
361
+ if (mappedBy)
362
+ return fillInverseSide(at, meta, relOpts, mappedBy);
363
+ if (through)
364
+ return fillThrough(at, meta, relOpts, through);
365
+ throw new TypeError(isToManyRelation(relOpts)
366
+ ? `${at} is a to-many relation with no way to join: it needs 'mappedBy' (the member on the other side), ` +
367
+ "'through' (a junction entity), or 'references' (the columns)."
368
+ : `${at} needs 'references', the foreign key column it joins by, or 'mappedBy', the member on the other ` +
369
+ 'side holding it.');
370
+ }
371
+ /**
372
+ * Each key of this entity, then each of the target, paired with the junction's one column referencing it.
373
+ * Both groups live on the junction whatever the cardinality, as `deleteRelations` and every dialect read
374
+ * them, and a composite key gives a pair per column, which is what makes a join address a whole key.
375
+ */
376
+ function fillThrough(at, meta, relOpts, through) {
377
+ const junction = registeredMeta(through());
378
+ relOpts.references = [
379
+ ...junctionReferences(at, junction, meta),
380
+ ...junctionReferences(at, junction, ensureMeta(relOpts.entity())),
381
+ ];
382
+ return relOpts.references;
407
383
  }
408
384
  function fillInverseSide(at, meta, relOpts, mappedBy) {
409
- const relEntity = relOpts.entity();
410
- const relMeta = getMeta(relEntity);
411
- const own = pairedReferences(at, relOpts);
412
- if (own)
413
- return own;
385
+ const relMeta = registeredMeta(relOpts.entity());
386
+ const other = `'${relMeta.entity.name}.${mappedBy}'`;
414
387
  if (relMeta.fields[mappedBy]) {
415
388
  if (meta.ids.length > 1) {
416
- throw new TypeError(`${at} is mapped by '${relEntity.name}.${mappedBy}', one column, but the primary key of ` +
389
+ throw new TypeError(`${at} is mapped by ${other}, one column, but the primary key of ` +
417
390
  `'${meta.entity.name}' is composite (${meta.ids.join(', ')}). Map it by the relation on the other side ` +
418
391
  'instead, which joins every column of the key.');
419
392
  }
@@ -421,16 +394,18 @@ function fillInverseSide(at, meta, relOpts, mappedBy) {
421
394
  relOpts.references = [{ local: meta.ids[0], foreign: mappedBy }];
422
395
  return relOpts.references;
423
396
  }
424
- // Authored view again: with each side mapped by the other, the target is still mid-resolution here and
425
- // its own `references` are unset, which is what the second throw reports.
426
397
  const owner = relMeta.relations[mappedBy];
427
398
  if (!owner) {
428
- throw new TypeError(`${at} is mapped by '${mappedBy}', which is neither a field nor a relation of '${relEntity.name}'.`);
399
+ throw new TypeError(`${at} is mapped by '${mappedBy}', which is neither a field nor a relation of '${relMeta.entity.name}'.`);
400
+ }
401
+ if (owner.mappedBy) {
402
+ throw new TypeError(`${at} is mapped by ${other}, an inverse side too, so neither owns the foreign key.`);
429
403
  }
430
- const ownerReferences = pairedReferences(`'${relEntity.name}.${mappedBy}'`, owner);
431
- if (!ownerReferences?.length) {
432
- throw new TypeError(`${at} is mapped by '${relEntity.name}.${mappedBy}', an inverse side too, so neither owns the foreign key.`);
404
+ const ownerTarget = owner.entity();
405
+ if (!isA(meta.entity, ownerTarget)) {
406
+ throw new TypeError(`${at} is mapped by ${other}, a relation to '${ownerTarget.name}', not to '${meta.entity.name}'.`);
433
407
  }
408
+ const ownerReferences = settledReferences(other, relMeta, mappedBy, owner);
434
409
  // Two different flips: a junction's pairs are the owner's group followed by ours, so the two groups
435
410
  // swap - `toReversed` would also reverse each group, pairing a composite's columns crosswise. A
436
411
  // plain foreign key is one pair per key whose ends swap.
@@ -441,15 +416,52 @@ function fillInverseSide(at, meta, relOpts, mappedBy) {
441
416
  relOpts.through = owner.through;
442
417
  return relOpts.references;
443
418
  }
419
+ /**
420
+ * Refuses a join on a column either entity does not store, and a one-column join whose foreign key, on
421
+ * whichever side holds it, points at another entity: it would match unrelated rows by their keys.
422
+ */
423
+ function assertJoins(at, meta, relOpts, pairs) {
424
+ const target = registeredMeta(relOpts.entity());
425
+ // Only the owning side of a to-one holds its foreign key; an inverse side and a to-many join on the target's.
426
+ const holdsLocally = !relOpts.mappedBy && !isToManyRelation(relOpts);
427
+ const sides = [
428
+ { meta: columnsOf(meta), keys: pairs.map(({ local }) => local), joins: target.entity, holds: holdsLocally },
429
+ { meta: columnsOf(target), keys: pairs.map(({ foreign }) => foreign), joins: meta.entity, holds: !holdsLocally },
430
+ ];
431
+ for (const side of sides) {
432
+ for (const key of side.keys) {
433
+ const column = `'${side.meta.entity.name}.${key}'`;
434
+ const field = side.meta.fields[key];
435
+ if (!field || isInlinedExpression(field)) {
436
+ throw new TypeError(`${at} joins ${column}, which is not a column: declare it with '@Field'.`);
437
+ }
438
+ const referenced = side.holds && pairs.length === 1 ? field.references?.() : undefined;
439
+ if (referenced && !isA(side.joins, referenced)) {
440
+ throw new TypeError(`${at} joins ${column}, a foreign key to '${referenced.name}', not to '${side.joins.name}'.`);
441
+ }
442
+ }
443
+ }
444
+ }
445
+ /** `meta` with its fields read by any name, as a join's columns come. */
446
+ function columnsOf(meta) {
447
+ return meta;
448
+ }
449
+ /** Whether `entity` is `base` or extends it, as an entity inheriting a relation does. */
450
+ function isA(entity, base) {
451
+ return entity === base || entity.prototype instanceof base;
452
+ }
444
453
  /**
445
454
  * The foreign keys an entity holds: each owning to-one's columns, and each `@Field({ references })` no
446
455
  * relation joins on, as the many-to-one it describes, once its target has registered a key. What the
447
- * schema build constrains and a junction joins by.
456
+ * schema build constrains and a junction joins by, settling the relations holding them first.
448
457
  */
449
458
  export function foreignKeysOf(meta) {
450
459
  const owning = definedEntries(meta.relations)
451
- .map(([, relation]) => relation)
452
- .filter(({ cardinality, mappedBy }) => cardinality === 'm1' || (cardinality === '11' && !mappedBy));
460
+ .filter(([, relation]) => !relation.mappedBy && !relation.through && !isToManyRelation(relation))
461
+ .map(([relKey, relation]) => {
462
+ settledReferences(`'${meta.entity.name}.${relKey}'`, meta, relKey, relation);
463
+ return relation;
464
+ });
453
465
  const joined = new Set(owning.flatMap(({ references }) => references.map(({ local }) => local)));
454
466
  const columns = definedEntries(meta.fields).flatMap(([key, field]) => {
455
467
  if (!field.references || joined.has(key))
@@ -459,8 +471,8 @@ export function foreignKeysOf(meta) {
459
471
  return [];
460
472
  if (target.ids.length > 1) {
461
473
  throw new TypeError(`'${meta.entity.name}.${key}' cannot reference '${target.entity.name}', whose primary key is composite ` +
462
- `(${target.ids.join(', ')}): a column points at one. Use ` +
463
- `'@ManyToOne({ entity: () => ${target.entity.name} })', which declares one column per key.`);
474
+ `(${target.ids.join(', ')}): a column points at one. Declare a column per key and pair each with it ` +
475
+ `in a '@ManyToOne' to '${target.entity.name}'.`);
464
476
  }
465
477
  return [{ entity: field.references, cardinality: 'm1', references: [{ local: key, foreign: target.ids[0] }] }];
466
478
  });
@@ -474,9 +486,9 @@ function junctionReferences(at, junction, side) {
474
486
  const referenced = `'${side.entity.name}.${key}'`;
475
487
  if (!pair) {
476
488
  const declare = side.ids.length > 1
477
- ? `@ManyToOne({ entity: () => ${side.entity.name} })`
478
- : `@Field({ references: () => ${side.entity.name} })`;
479
- throw new TypeError(`${at} joins through '${junction.entity.name}', which has no column referencing ${referenced}: declare one, '${declare}'.`);
489
+ ? `a column per key, paired in a '@ManyToOne' to '${side.entity.name}'`
490
+ : `'@Field({ references: () => ${side.entity.name} })'`;
491
+ throw new TypeError(`${at} joins through '${junction.entity.name}', which has no column referencing ${referenced}: declare ${declare}.`);
480
492
  }
481
493
  if (others.length) {
482
494
  const columns = [pair, ...others].map(({ local }) => `'${local}'`).join(' and ');
@@ -104,6 +104,10 @@ export declare class SqlSchemaGenerator implements SchemaGenerator {
104
104
  * enum's `CHECK` comes last, the only place MariaDB takes it.
105
105
  */
106
106
  private renderColumn;
107
+ /**
108
+ * The column type a field gets, resolved as its table resolves it. A field alone cannot tell that it is
109
+ * one column of a composite key, which its table never makes serial.
110
+ */
107
111
  getSqlType(field: FieldMeta): string;
108
112
  /** The statements that alter `column` in place, as this dialect spells them. */
109
113
  generateAlterColumnStatements(tableName: string, column: ColumnSchema, newDefinition: string): string[];
@@ -1,7 +1,7 @@
1
- import { getMeta, soleIdOf } from '../entity/index.js';
2
- import { canonicalToSql, engineType, fieldOptionsToCanonical, isVectorCategory } from '../schema/canonicalType.js';
1
+ import { getMeta } from '../entity/index.js';
2
+ import { canonicalToSql, engineType, isVectorCategory } from '../schema/canonicalType.js';
3
3
  import { indexSignature } from '../schema/indexDifferences.js';
4
- import { buildSchemaAST } from '../schema/schemaASTBuilder.js';
4
+ import { buildSchemaAST, resolveColumnCanonicalType } from '../schema/schemaASTBuilder.js';
5
5
  import { diffRelationshipNodes, diffTable } from '../schema/schemaASTDiffer.js';
6
6
  import { isAutoIncrement, qualifyName } from '../util/index.js';
7
7
  import { derivedCheckName, derivedForeignKeyName, derivedPrimaryKeyName } from '../util/sql.util.js';
@@ -309,23 +309,15 @@ export class SqlSchemaGenerator {
309
309
  }
310
310
  return def;
311
311
  }
312
+ /**
313
+ * The column type a field gets, resolved as its table resolves it. A field alone cannot tell that it is
314
+ * one column of a composite key, which its table never makes serial.
315
+ */
312
316
  getSqlType(field) {
313
- // A foreign key takes the type of the key it points at. A `referencedKey` the target does not
314
- // have falls through to this column's own options, as the AST builder does with the same case.
315
- if (field.references) {
316
- const refMeta = getMeta(field.references());
317
- const refIdField = refMeta.fields[field.referencedKey ?? soleIdOf(refMeta, 'a foreign key')];
318
- if (refIdField) {
319
- return this.getSqlType({ ...refIdField, references: undefined, isId: undefined, autoIncrement: false });
320
- }
321
- }
322
- // Get canonical type and convert to SQL
323
- const canonical = fieldOptionsToCanonical(field);
324
- // Special case for serial primary keys
325
- if (isAutoIncrement(field, field.isId === true)) {
326
- return this.serialType(canonical);
327
- }
328
- return this.canonicalTypeToSql(canonical);
317
+ const canonical = resolveColumnCanonicalType(field);
318
+ return isAutoIncrement(field, field.isId === true)
319
+ ? this.serialType(canonical)
320
+ : this.canonicalTypeToSql(canonical);
329
321
  }
330
322
  /** The statements that alter `column` in place, as this dialect spells them. */
331
323
  generateAlterColumnStatements(tableName, column, newDefinition) {
@@ -5,7 +5,7 @@
5
5
  * - Entity metadata (decorator-based entities)
6
6
  * - Database introspection results (TableSchema[])
7
7
  */
8
- import { foreignKeysOf, getMeta, soleIdOf } from '../entity/metadata/definition.js';
8
+ import { fieldOf, foreignKeysOf, getMeta, soleIdOf } from '../entity/metadata/definition.js';
9
9
  import { declaredIndexes, indexNameParts, renderIndexColumn } from '../util/ddlExpression.util.js';
10
10
  import { isInlinedExpression } from '../util/field.util.js';
11
11
  import { isSoleIdField } from '../util/field.util.js';
@@ -69,12 +69,7 @@ export function resolveColumnCanonicalType(field, seen = new Set()) {
69
69
  if (!hasExplicitType && field.references && !seen.has(field.references)) {
70
70
  seen.add(field.references);
71
71
  const referencedMeta = getMeta(field.references());
72
- // The column names which key it points at when the target has several; otherwise there is one.
73
- const referencedKey = field.referencedKey ?? soleIdOf(referencedMeta, 'a foreign key');
74
- const referencedIdField = referencedMeta.fields[referencedKey];
75
- if (referencedIdField) {
76
- return resolveColumnCanonicalType(referencedIdField, seen);
77
- }
72
+ return resolveColumnCanonicalType(fieldOf(referencedMeta, soleIdOf(referencedMeta, 'a foreign key')), seen);
78
73
  }
79
74
  return fieldOptionsToCanonical(field);
80
75
  }
@@ -141,10 +136,8 @@ function addRelationshipsFromEntity(ctx, meta) {
141
136
  const localColumns = [];
142
137
  const foreignColumns = [];
143
138
  for (const { local: localProp, foreign: foreignProp } of foreignKey.references) {
144
- const localField = meta.fields[localProp];
145
- const foreignField = relatedMeta.fields[foreignProp];
146
- const localColumn = localField && table.columns.get(ctx.resolveColumnName(localProp, localField));
147
- const foreignColumn = foreignField && relatedTable.columns.get(ctx.resolveColumnName(foreignProp, foreignField));
139
+ const localColumn = table.columns.get(ctx.resolveColumnName(localProp, fieldOf(meta, localProp)));
140
+ const foreignColumn = relatedTable.columns.get(ctx.resolveColumnName(foreignProp, fieldOf(relatedMeta, foreignProp)));
148
141
  if (!localColumn || !foreignColumn)
149
142
  break;
150
143
  localColumns.push(localColumn);
@@ -15,6 +15,8 @@ export declare const idKey: unique symbol;
15
15
  * that lets it through on an entity which never declared one.
16
16
  */
17
17
  export declare const SOFT_DELETE_FILTER = "softDelete";
18
+ /** A filter name an entity may declare: any but {@link SOFT_DELETE_FILTER}, which a refusal names. */
19
+ export type FilterName<N extends string> = N extends typeof SOFT_DELETE_FILTER ? `'${N}' is reserved for the filter @Field({ softDelete }) registers` : N;
18
20
  /**
19
21
  * Infers the key names of an entity
20
22
  */
@@ -212,6 +214,8 @@ export type IdKey<E> = ([NamedIdKey<E>] extends [never] ? FieldKey<E> : NamedIdK
212
214
  * filter"; `assertIdValue` is what rejects it.
213
215
  */
214
216
  export type IdValue<E> = E[IdKey<E>];
217
+ /** Whether `E`'s primary key spans several columns, which no single column can reference. */
218
+ export type HasCompositeKey<E> = true extends IsUnion<IdKey<E>> ? true : false;
215
219
  /** Every column of a key, which is how a composite row is named and what a `$where` reduces to. */
216
220
  type IdMap<E> = Partial<Pick<E, IdKey<E>>>;
217
221
  /**
@@ -309,11 +313,6 @@ export type FieldMeta<V = TsTypeOf<FieldType>> = Except<FieldOptions<V>, 'comput
309
313
  * what keeps a `uuid` primary key from becoming TEXT on every foreign key pointing at it.
310
314
  */
311
315
  readonly typeFromReference?: boolean;
312
- /**
313
- * Which key of the referenced entity this column points at, where that entity has more than one.
314
- * Set by `fillOwningSide`; without it a composite target's columns would all take the first key's type.
315
- */
316
- readonly referencedKey?: string;
317
316
  };
318
317
  /**
319
318
  * Configurable options for a field, carrying `V`, the value the column holds: what a generator returns
@@ -531,7 +530,7 @@ export type RelationOptions<E, O = unknown> = {
531
530
  readonly onDelete?: ForeignKeyAction;
532
531
  readonly onUpdate?: ForeignKeyAction;
533
532
  /** The inverse side: the member of the target holding the foreign key or the owning relation, `(post) => post.author`. */
534
- mappedBy?: (keys: KeyMap<E>) => Key<E>;
533
+ mappedBy?: (keys: KeyMap<E>) => RelationKey<E> | ForeignKey<E, O>;
535
534
  /**
536
535
  * The pivot entity of a many-to-many. Unconstrained by `E`: a pivot holds foreign keys to both
537
536
  * sides and is not a relation value of the target, so nothing about it is derivable from `E`.
@@ -543,13 +542,21 @@ export type RelationOptions<E, O = unknown> = {
543
542
  * foreign: customer.code }]`. A `through` relation takes none: it joins by the junction's column
544
543
  * referencing each side.
545
544
  */
546
- references?: (local: KeyMap<O>, foreign: KeyMap<E>) => FieldKey<O> | readonly RelationReference<O, E>[];
545
+ references?: (local: KeyMap<O>, foreign: KeyMap<E>) => ForeignKey<O, E> | readonly RelationReference<O, E>[];
547
546
  };
548
- /** One pair of join columns, each a field read off its entity's key map. */
547
+ /** The one column of `O` that can be a foreign key to `E`: a field holding `E`'s key, which has to be a single one. */
548
+ type ForeignKey<O, E> = HasCompositeKey<E> extends true ? never : FieldKeyHolding<O, IdValue<E>>;
549
+ /** One pair of join columns, each a field read off its entity's key map, the local one holding the foreign's value. */
549
550
  export type RelationReference<O, E> = {
550
- readonly local: FieldKey<O>;
551
- readonly foreign: FieldKey<E>;
552
- };
551
+ readonly [F in keyof E]-?: {
552
+ readonly local: FieldKeyHolding<O, E[F]>;
553
+ readonly foreign: F;
554
+ };
555
+ }[FieldKey<E>];
556
+ /** The fields of `O` that can hold any value `V` takes. */
557
+ type FieldKeyHolding<O, V> = {
558
+ readonly [K in keyof O]-?: [NonNullable<V>] extends [NonNullable<O[K]>] ? K : never;
559
+ }[FieldKey<O>];
553
560
  /** {@link RelationOptions.references} as pairs alone, for a to-many, which holds no foreign key of its own to name. */
554
561
  type RelationReferencePairs<E, O> = (local: KeyMap<O>, foreign: KeyMap<E>) => readonly RelationReference<O, E>[];
555
562
  /**
@@ -558,10 +565,9 @@ type RelationReferencePairs<E, O> = (local: KeyMap<O>, foreign: KeyMap<E>) => re
558
565
  * assertions - `fillRelations` establishes the invariant once, and throws where it cannot.
559
566
  *
560
567
  * `entity` and `through` stay {@link EntityGetter}s. Resolution could call them once and store the class,
561
- * but only by keeping the authored relations in a second map: it reads them *across* entities, and a
562
- * circular import can leave the entity being read mid-resolution, where telling "no such relation" apart
563
- * from "declared, but an inverse side too, so neither owns the foreign key" needs the unresolved shape
564
- * still there to find. A phase-split metadata map costs more than the call parentheses it saves.
568
+ * but only by keeping the authored relations in a second map: it settles them in place, reading them across
569
+ * entities not resolved yet, so the authored and the settled shape have to be one object. A phase-split
570
+ * metadata map costs more than the call parentheses it saves.
565
571
  */
566
572
  export type RelationMeta = Omit<RelationRegistration, 'references'> & {
567
573
  references: RelationReferences;
@@ -582,8 +588,8 @@ type RelationOwnerJoin<E, O> = (Required<Pick<RelationOptions<E, O>, 'through'>>
582
588
  readonly references: RelationReferencePairs<E, O>;
583
589
  readonly through?: never;
584
590
  };
585
- type RelationOptionsOwner<E, O> = Pick<RelationOptions<E, O>, 'entity' | 'references' | 'cascade' | 'onDelete' | 'onUpdate'>;
586
- type RelationOptionsInverseSide<E> = Pick<RelationOptions<E>, 'entity' | 'cascade'> & Required<Pick<RelationOptions<E>, 'mappedBy'>>;
591
+ type RelationOptionsOwner<E, O> = Pick<RelationOptions<E, O>, 'entity' | 'cascade' | 'onDelete' | 'onUpdate'> & Required<Pick<RelationOptions<E, O>, 'references'>>;
592
+ type RelationOptionsInverseSide<E, O> = Pick<RelationOptions<E, O>, 'entity' | 'cascade'> & Required<Pick<RelationOptions<E, O>, 'mappedBy'>>;
587
593
  type RelationOptionsThroughOwner<E, O> = Pick<RelationOptions<E, O>, 'entity' | 'cascade'> & RelationOwnerJoin<E, O>;
588
594
  /**
589
595
  * The key names of `E` as values, so a definition reads a member off it - `(post) => post.author` -
@@ -630,10 +636,10 @@ export type RelationReferences = {
630
636
  readonly foreign: string;
631
637
  }[];
632
638
  export type RelationCardinality = '11' | 'm1' | '1m' | 'mm';
633
- export type RelationOneToOneOptions<E, O = unknown> = RelationOptionsOwner<E, O> | RelationOptionsInverseSide<E>;
634
- export type RelationOneToManyOptions<E, O = unknown> = RelationOptionsInverseSide<E> | RelationOptionsThroughOwner<E, O>;
639
+ export type RelationOneToOneOptions<E, O = unknown> = RelationOptionsOwner<E, O> | RelationOptionsInverseSide<E, O>;
640
+ export type RelationOneToManyOptions<E, O = unknown> = RelationOptionsInverseSide<E, O> | RelationOptionsThroughOwner<E, O>;
635
641
  export type RelationManyToOneOptions<E, O = unknown> = RelationOptionsOwner<E, O>;
636
- export type RelationManyToManyOptions<E, O = unknown> = RelationOptionsThroughOwner<E, O> | RelationOptionsInverseSide<E>;
642
+ export type RelationManyToManyOptions<E, O = unknown> = RelationOptionsThroughOwner<E, O> | RelationOptionsInverseSide<E, O>;
637
643
  /**
638
644
  * Lifecycle hook event names.
639
645
  */
@@ -927,7 +933,9 @@ export type EntityOptions<E = unknown> = {
927
933
  */
928
934
  readonly schema?: string;
929
935
  /** Named, default-on `$where` filters (soft-delete is auto-registered from `@Field({ softDelete })`). */
930
- readonly filters?: Record<string, FilterOptions<E>>;
936
+ readonly filters?: Record<string, FilterOptions<E>> & {
937
+ readonly [SOFT_DELETE_FILTER]?: never;
938
+ };
931
939
  /** Scalar fields; use `isId: true` on exactly one field for the primary key. */
932
940
  readonly fields?: EntityFieldOptions<E>;
933
941
  readonly relations?: EntityRelationOptions<E>;
@@ -103,14 +103,18 @@ export type FilterOptions<E = unknown> = {
103
103
  readonly where: FilterWhere<E>;
104
104
  /** Applied to every query unless bypassed via `QueryOptions.filters`. Defaults to `true`. */
105
105
  readonly default?: boolean;
106
+ } & ({
107
+ readonly security?: false;
108
+ /** What to do when {@link FilterOptions.where} returns `undefined`. Defaults to `skip`. */
109
+ readonly onMissing?: FilterOnMissing;
110
+ } | {
106
111
  /**
107
112
  * Row-level-security filter: always applied (ignores `QueryOptions.filters` bypass) and
108
- * AND-merged so a client `$where` on the same field can't override it.
113
+ * AND-merged so a client `$where` on the same field can't override it. It fails closed.
109
114
  */
110
- readonly security?: boolean;
111
- /** What to do when {@link where} returns `undefined`. Defaults to `skip`, or `throw` for `security`. */
112
- readonly onMissing?: FilterOnMissing;
113
- };
115
+ readonly security: true;
116
+ readonly onMissing?: 'throw';
117
+ });
114
118
  /**
115
119
  * direction for the sort.
116
120
  */
@@ -49,11 +49,8 @@ export declare function isDatabaseWritten(field: FieldOptions): boolean;
49
49
  */
50
50
  export declare function isSoleIdField<E>(meta: EntityMeta<E>, field: FieldOptions): boolean;
51
51
  /**
52
- * Whether the database generates this column's value.
53
- *
54
- * The only answer: the schema AST asked it separately and disagreed on three counts - it ignored
55
- * `onInsert`, so a key the application generates was still emitted `AUTO_INCREMENT`, and it ignored
56
- * `columnType`, where this one used to let *any* declared width suppress the whole inference. A key
57
- * that states its width is still a generated key; one that states how it is filled is not.
52
+ * Whether the database generates this column's value: a numeric key nothing else fills. `onInsert` fills
53
+ * it from the application and `references` from the row it shares its key with; a `columnType` only
54
+ * states its width. The one answer the create statement and the diff both read.
58
55
  */
59
56
  export declare function isAutoIncrement(field: FieldOptions, isPrimaryKey: boolean): boolean;
@@ -93,15 +93,12 @@ export function isSoleIdField(meta, field) {
93
93
  return field.isId === true && meta.ids.length === 1;
94
94
  }
95
95
  /**
96
- * Whether the database generates this column's value.
97
- *
98
- * The only answer: the schema AST asked it separately and disagreed on three counts - it ignored
99
- * `onInsert`, so a key the application generates was still emitted `AUTO_INCREMENT`, and it ignored
100
- * `columnType`, where this one used to let *any* declared width suppress the whole inference. A key
101
- * that states its width is still a generated key; one that states how it is filled is not.
96
+ * Whether the database generates this column's value: a numeric key nothing else fills. `onInsert` fills
97
+ * it from the application and `references` from the row it shares its key with; a `columnType` only
98
+ * states its width. The one answer the create statement and the diff both read.
102
99
  */
103
100
  export function isAutoIncrement(field, isPrimaryKey) {
104
101
  if (field.autoIncrement !== undefined)
105
102
  return field.autoIncrement;
106
- return isPrimaryKey && columnFamily(field.type) === 'numeric' && !field.onInsert;
103
+ return isPrimaryKey && columnFamily(field.type) === 'numeric' && !field.onInsert && !field.references;
107
104
  }
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.65.0",
6
+ "version": "0.66.0",
7
7
  "type": "module",
8
8
  "engines": {
9
9
  "node": ">=24"