uql-orm 0.79.0 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/uql-browser.min.js.map +2 -2
- package/dist/cockroachdb/cockroachDialect.js +5 -1
- package/dist/dialect/abstractDialect.d.ts +1 -31
- package/dist/dialect/abstractDialect.js +3 -27
- package/dist/dialect/abstractSqlDialect.d.ts +25 -56
- package/dist/dialect/abstractSqlDialect.js +78 -145
- package/dist/dialect/aliases.d.ts +5 -0
- package/dist/dialect/aliases.js +5 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +4 -2
- package/dist/dialect/mysqlLikeSqlDialect.js +14 -1
- package/dist/dialect/operators.d.ts +66 -0
- package/dist/dialect/operators.js +129 -0
- package/dist/dialect/pgLikeSqlDialect.d.ts +4 -1
- package/dist/dialect/pgLikeSqlDialect.js +16 -3
- package/dist/entity/decorator/entity.d.ts +6 -1
- package/dist/entity/decorator/entity.js +12 -1
- package/dist/entity/index.d.ts +1 -1
- package/dist/entity/index.js +1 -1
- package/dist/entity/metadata/definition.d.ts +6 -1
- package/dist/entity/metadata/definition.js +19 -0
- package/dist/migrate/codegen/entityTypes.js +1 -2
- package/dist/migrate/ddl/mssqlIndexDdl.d.ts +5 -0
- package/dist/migrate/ddl/mssqlIndexDdl.js +10 -0
- package/dist/migrate/ddl/mssqlTableDdl.d.ts +2 -0
- package/dist/migrate/ddl/mssqlTableDdl.js +5 -0
- package/dist/migrate/ddl/tableDdl.d.ts +2 -0
- package/dist/migrate/ddl/tableDdl.js +4 -0
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +4 -0
- package/dist/migrate/generator/mongoSchemaGenerator.js +10 -0
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +13 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +21 -0
- package/dist/migrate/introspection/mongoIntrospector.d.ts +3 -1
- package/dist/migrate/introspection/mongoIntrospector.js +4 -0
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/mssqlIntrospector.js +8 -0
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/mysqlIntrospector.js +9 -0
- package/dist/migrate/introspection/postgresIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/postgresIntrospector.js +10 -0
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +3 -0
- package/dist/migrate/migrator.d.ts +28 -1
- package/dist/migrate/migrator.js +88 -9
- package/dist/migrate/schemaGenerator.d.ts +12 -1
- package/dist/migrate/schemaGenerator.js +47 -5
- package/dist/migrate/storage/databaseStorage.d.ts +4 -0
- package/dist/migrate/storage/databaseStorage.js +14 -8
- package/dist/migrate/triggerSql.d.ts +24 -0
- package/dist/migrate/triggerSql.js +229 -0
- package/dist/mongo/mongoDialect.d.ts +0 -21
- package/dist/mongo/mongoDialect.js +105 -100
- package/dist/mongo/mongodbQuerier.js +17 -1
- package/dist/mssql/mssqlDialect.d.ts +18 -7
- package/dist/mssql/mssqlDialect.js +77 -33
- package/dist/mssql/mssqlQuerier.js +2 -2
- package/dist/schema/canonicalType.d.ts +6 -1
- package/dist/schema/canonicalType.js +14 -0
- package/dist/schema/schemaASTBuilder.d.ts +2 -8
- package/dist/schema/schemaASTBuilder.js +6 -20
- package/dist/sqlite/sqliteDialect.d.ts +1 -1
- package/dist/sqlite/sqliteDialect.js +12 -3
- package/dist/type/dialect.d.ts +69 -9
- package/dist/type/entity.d.ts +96 -3
- package/dist/type/migration.d.ts +17 -0
- package/dist/type/query.d.ts +13 -4
- package/dist/type/queryWhere.d.ts +4 -2
- package/dist/util/field.util.d.ts +9 -1
- package/dist/util/field.util.js +14 -2
- package/dist/util/fieldOption.util.d.ts +2 -2
- package/dist/util/fieldOption.util.js +2 -2
- package/dist/util/raw.d.ts +9 -1
- package/dist/util/raw.js +36 -11
- package/dist/util/sql.util.d.ts +12 -0
- package/dist/util/sql.util.js +24 -3
- package/dist/util/uqlError.d.ts +2 -0
- package/dist/util/uqlError.js +4 -0
- package/package.json +4 -4
- package/skills/uql-orm/SKILL.md +11 -6
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"sources": ["../../src/browser/http/bus.ts", "../../src/type/query.ts", "../../src/type/queryRaw.ts", "../../src/util/object.util.ts", "../../src/http/query.ts", "../../src/browser/http/http.ts", "../../src/util/string.util.ts", "../../src/http/contract.ts", "../../src/browser/querier/httpQuerier.ts", "../../src/browser/options.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import type { RequestCallback, RequestNotification } from '../type/index.js';\n\nconst subscriptors: RequestCallback[] = [];\n\nexport function notify(notification: RequestNotification): void {\n for (const subscriptor of subscriptors) {\n subscriptor(notification);\n }\n}\n\nexport function on(cb: RequestCallback): () => void {\n subscriptors.push(cb);\n const index = subscriptors.length - 1;\n return (): void => {\n subscriptors.splice(index, 1);\n };\n}\n",
|
|
6
|
-
"import type { FieldKey, JsonFieldPaths, RelationKey, RelationTarget, ToManyRelationKey, WrittenId } from './entity.js';\nimport type { QueryLock } from './queryLock.js';\nimport type { QueryRaw } from './queryRaw.js';\nimport type { QueryWhere } from './queryWhere.js';\nimport type { BooleanLike, Except, IsMany, PrimaryKey } from './utility.js';\nimport type { QueryVectorQuery, QueryVectorSearch } from './vector.js';\n\nexport type QueryOptions = {\n /**\n * Toggle named entity filters for this query. `false` disables all filters;\n * `{ softDelete: false }` disables one; `{ myFilter: true }` force-enables a `default: false` filter.\n * Security filters cannot be disabled here.\n */\n filters?: false | Record<string, boolean>;\n /**\n * Delete only: physically remove rows instead of soft-deleting, ignoring the soft-delete filter so\n * already-deleted rows are removed too. No effect on entities without a soft-delete field.\n */\n hardDelete?: boolean;\n /**\n * `updateMany`/`deleteMany` only: address every row of the table on purpose. Without it a bulk write\n * that names none - no `$where` and no `$limit` - is refused, since a forgotten filter and the whole\n * table look alike. The entity's own filters never count as naming one.\n */\n unfiltered?: boolean;\n /**\n * prefix the query with this.\n */\n prefix?: string;\n /**\n * automatically infer the prefix for the query.\n */\n autoPrefix?: boolean;\n};\n\n/**\n * Field selection - `{ name: true }` whitelists fields; relations go in `$populate`. Declared over\n * `F extends keyof E`, like every map keyed by an entity's members, so each key stays linked to its\n * property and an editor rename reaches it. `F` is also how a projection passes its captured key set.\n */\nexport type QuerySelect<E, F extends keyof E = FieldKey<E>, V = BooleanLike> = {\n [K in F]?: V;\n};\n\n/**\n * Accepted `$select` value: a field map, or raw SQL projections built with `raw()`\n * (e.g. ``[raw`*`, raw`LOG10(points)`.as('score')]``). The raw form is SQL-only.\n */\nexport type QuerySelectValue<E, Raw = QueryRaw> = QuerySelect<E> | readonly Raw[];\n\n/**\n * Fields to exclude from the query result - `{ name: true }` blacklists fields.\n * Mutually exclusive with positive field selections in `$select`.\n */\nexport type QueryExclude<E> = QuerySelect<E>;\n\n/**\n * relation population map.\n */\nexport type QueryPopulate<E, Raw = QueryRaw, R extends keyof E = RelationKey<E>> = {\n [K in R]?: BooleanLike | QueryPopulateRelationOptions<E[K], Raw>;\n};\n\n/**\n * The key a read carries its relation tallies under. One spelling for the type and the runtime that\n * fills it: they sit in different modules, so a drift would type-check and answer `undefined`.\n */\nexport const COUNT_RESULT_KEY = '_count';\n\n/**\n * How many rows each named relation holds per parent, `true` for all of them or a filter to narrow\n * which ones count: a correlated count in the read's own statement, so no related row is loaded. Comes\n * back under `_count`, which keeps it clear of a relation of the same name `$populate` filled.\n */\nexport type QueryCount<E, Raw = QueryRaw, R extends keyof E = ToManyRelationKey<E>> = {\n [K in R]?: BooleanLike | QueryFilter<RelationTarget<E[K]>, Raw>;\n};\n\n/**\n * query conflict paths - subset of field keys used to detect upsert conflicts.\n */\nexport type QueryConflictPaths<E> = QuerySelect<E, FieldKey<E>, true>;\n\n/**\n * Options to populate a relation declared as `V`, by its cardinality.\n */\nexport type QueryPopulateRelationOptions<V, Raw = QueryRaw> =\n IsMany<V> extends true\n ? RelationQuery<RelationTarget<V>, Raw>\n : QueryUnique<RelationTarget<V>, Raw> & { $required?: boolean };\n\n/**\n * The per-request context parameterized filters read, set with `withContext(ctx, cb)`. An interface,\n * so its keys can be typed once: `declare module 'uql-orm' { interface UqlContext { tenantId: number } }`.\n */\nexport interface UqlContext {\n [key: string]: unknown;\n}\n\n/**\n * A filter's `$where` fragment: a plain fragment, or a function of the ambient {@link UqlContext}.\n * Return `undefined` when the condition can't resolve (see {@link FilterOptions.onMissing}).\n */\nexport type FilterWhere<E> = QueryWhere<E> | ((context: UqlContext | undefined) => QueryWhere<E> | undefined);\n\n/**\n * What to do when a filter's condition returns `undefined`. `skip` omits it (convenience filters);\n * `throw` fails closed (the default for `security` filters).\n */\nexport type FilterOnMissing = 'skip' | 'throw';\n\n/**\n * Authoring shape for `@Entity({ filters })` / `@Filter` / `defineFilter`.\n */\nexport type FilterOptions<E = unknown> = {\n readonly where: FilterWhere<E>;\n /** Applied to every query unless bypassed via `QueryOptions.filters`. Defaults to `true`. */\n readonly default?: boolean;\n} & (\n | {\n readonly security?: false;\n /** What to do when {@link FilterOptions.where} returns `undefined`. Defaults to `skip`. */\n readonly onMissing?: FilterOnMissing;\n }\n | {\n /**\n * Row-level-security filter: always applied (ignores `QueryOptions.filters` bypass) and\n * AND-merged so a client `$where` on the same field can't override it. It fails closed.\n */\n readonly security: true;\n readonly onMissing?: 'throw';\n }\n);\n\n/**\n * direction for the sort, and where nulls land in it.\n *\n * Unqualified, each engine has its own answer - Postgres and CockroachDB sort nulls last on `asc`, the\n * rest sort them first - so a placement is the only portable one. Engines with no `NULLS FIRST/LAST`\n * emulate it with a leading term, which no index can serve, which is why it is asked for and never\n * applied by default.\n */\nexport type QuerySortDirection = -1 | 1 | 'asc' | 'desc' | QuerySortNullsDirection;\n\n/** A {@link QuerySortDirection} stating where nulls land. */\nexport type QuerySortNullsDirection = 'ascNullsFirst' | 'ascNullsLast' | 'descNullsFirst' | 'descNullsLast';\n\n/**\n * Accepted value for a field in `$sort` - either a direction or a vector similarity search.\n */\nexport type QuerySortValue = QuerySortDirection | QueryVectorSearch;\n\n/**\n * Ordering parents by how many rows a to-many relation holds - \"the ten users with the most posts\".\n * The tally is computed per parent as a correlated count, never by loading the rows.\n */\nexport type QuerySortByCount = {\n $count: QuerySortDirection;\n};\n\n/** The fields of `E` a vector search can rank by. */\ntype VectorFieldKey<E> = { [P in FieldKey<E>]: NonNullable<E[P]> extends readonly number[] ? P : never }[FieldKey<E>];\n\n/**\n * Ordering parents by the row of a to-many nearest a vector, per vector field: its distance is the\n * smallest of theirs. Nothing to `$project`, since no one row of the parent's answers under it. Never\n * where the target has no vector, since an empty map would admit any value at all.\n */\nexport type QuerySortByNearest<E> = [VectorFieldKey<E>] extends [never]\n ? never\n : { [P in VectorFieldKey<E>]?: QueryVectorQuery };\n\n/**\n * Ordering by relevance to the `$text` at the root of `$where`, in either direction as any key sorts. The\n * object form also answers it under the name `$project` gives it, most relevant first unless `$order` says.\n */\nexport type QuerySortByText = {\n $text?: QuerySortDirection | { readonly $project: string; readonly $order?: QuerySortDirection };\n};\n\n/**\n * A row with the value a `$sort` projects under the name its `$project` gives - a vector's distance, or a\n * `$text` relevance - which is not inferred: `(await querier.findMany(Post, q)) as WithProjection<Post, 'score'>[]`.\n */\nexport type WithProjection<E, K extends string> = E & Record<K, number>;\n\n/**\n * A sort by fields, JSON paths, a to-one relation's fields, a to-many's `$count` or nearest row, a vector\n * distance, or - where `Root` says it sorts the queried entity itself, not a relation's rows - a `$text`\n * relevance or a distance it projects. One mapped type over the key sets: an intersection is checked once\n * per member, which made this the costliest.\n */\nexport type QuerySortMap<E, Root extends boolean = true, K extends keyof E = FieldKey<E> | RelationKey<E>> = {\n [P in K]?: P extends RelationKey<E>\n ? // A to-many has no single value to order by, so what it offers instead is its size or nearest row.\n IsMany<E[P]> extends true\n ? QuerySortByCount | QuerySortByNearest<RelationTarget<E[P]>>\n : QuerySortMap<RelationTarget<E[P]>, false>\n : NonNullable<E[P]> extends readonly number[]\n ? Root extends true\n ? QuerySortValue\n : QuerySortDirection | QueryVectorQuery\n : QuerySortDirection;\n} & ([JsonFieldPaths<E>] extends [never] ? unknown : { [P in JsonFieldPaths<E>]?: QuerySortDirection }) &\n (Root extends true ? QuerySortByText : unknown);\n\n/**\n * pager options.\n */\nexport type QueryPager = {\n /**\n * Index from where start the search\n */\n $skip?: number;\n\n /**\n * Max number of records to retrieve\n */\n $limit?: number;\n};\n\n/**\n * Which rows a statement addresses.\n */\nexport type QueryFilter<E, Raw = QueryRaw> = {\n /**\n * filtering options.\n */\n $where?: QueryWhere<E, Raw>;\n};\n\n/**\n * A filter plus the page `count` takes. No `$sort`: ordering picks *which* rows a page holds, never\n * how many, so a count that accepted one would promise an influence it cannot have.\n */\nexport type QueryPage<E, Raw = QueryRaw> = QueryFilter<E, Raw> & QueryPager;\n\n/**\n * A filter plus the ordering and page `updateMany`/`deleteMany` take. Both settle the\n * rows they address with a SELECT first, so the page is portable rather than MySQL-only, and a\n * vector `$sort` is as valid here as on a read: it ranks the settle query's rows, which has the\n * projection list to hold the distance. `$lock` stays off these, declared on {@link Query} instead.\n */\nexport type QuerySearch<E, Raw = QueryRaw> = QueryPage<E, Raw> & {\n /**\n * sorting options.\n */\n $sort?: QuerySortMap<E>;\n};\n\n/**\n * query options.\n */\nexport type Query<E, Raw = QueryRaw> = {\n /**\n * field selection - `{ name: true }` whitelists fields, or raw SQL projections\n * (``[raw`LOG10(points)`.as('score')]``, SQL dialects only - MongoDB rejects the raw-array form).\n * Mutually exclusive with `$exclude`.\n */\n $select?: QuerySelectValue<E, Raw>;\n\n /**\n * relation population options.\n */\n $populate?: QueryPopulate<E, Raw>;\n\n /**\n * how many rows each named relation holds, under `_count` on every row. See {@link QueryCount}.\n */\n $count?: QueryCount<E, Raw>;\n\n /**\n * field exclusion - `{ name: true }` blacklists fields. Mutually exclusive with positive `$select`.\n * Keys a relation is assembled from (a joined row's primary key, a to-many's foreign key) are kept\n * regardless, since subtracting them would leave the relation unfilled.\n */\n $exclude?: QueryExclude<E>;\n\n /**\n * sorting options, vector similarity search included: a SELECT is the one statement with a\n * projection list to hold the distance such a search computes.\n */\n $sort?: QuerySortMap<E>;\n\n /**\n * whether to return only distinct rows.\n */\n $distinct?: boolean;\n\n /**\n * Lock the rows this query returns, `SELECT ... FOR UPDATE`, inside an open transaction: outside one\n * it is refused, since the lock would drop before the rows are used. SQL only, and not the SQLite family.\n */\n $lock?: QueryLock;\n\n /**\n * How many candidates an ANN index explores before ranking a vector search, in that index's own units\n * (`hnsw.ef_search`, `numCandidates`...); ignored where the search is exact. Postgres needs a transaction.\n */\n $candidates?: number;\n\n // `$where`, `$skip` and `$limit` are declared here rather than intersected in from\n // {@link QueryFilter} and {@link QueryPager}: an assignability check against an intersection is\n // repeated per constituent, and every query in a consuming codebase pays that. The two shapes are\n // pinned together in `queryStatementClauses.test-d.ts` so the copies cannot drift.\n\n /**\n * filtering options.\n */\n $where?: QueryWhere<E, Raw>;\n\n /**\n * Index from where start the search\n */\n $skip?: number;\n\n /**\n * Max number of records to retrieve\n */\n $limit?: number;\n};\n\n/**\n * A {@link Query} as it travels as JSON, which a `raw` SQL fragment cannot: what the browser client takes,\n * and what an RPC contract (tRPC, oRPC, TanStack Start) declares as its input.\n */\nexport type WireQuery<E> = Query<E, never>;\n\n/**\n * `Query`'s clauses grouped by the shape of their value, for the wire parser and the relation query\n * check alike; `satisfies` keeps them in step with `Query`.\n */\nexport const QUERY_OBJECT_CLAUSES = [\n '$select',\n '$populate',\n '$exclude',\n '$where',\n '$sort',\n] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Object clauses only the statement itself takes: a populated relation's rows keep their declared type,\n * so a `$count` inside one would have no `_count` to land in.\n */\nexport const QUERY_ROOT_OBJECT_CLAUSES = ['$count'] as const satisfies readonly (keyof Query<unknown>)[];\n\nexport const QUERY_NUMBER_CLAUSES = ['$skip', '$limit'] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Number clauses only the statement itself takes - the numeric mirror of {@link QUERY_ROOT_OBJECT_CLAUSES}.\n * `$candidates` tunes the index behind a vector search, and a vector search only ever ranks the rows\n * the statement returns, so a relation's own query has nothing to tune.\n */\nexport const QUERY_ROOT_NUMBER_CLAUSES = ['$candidates'] as const satisfies readonly (keyof Query<unknown>)[];\n\nexport const QUERY_BOOLEAN_CLAUSES = ['$distinct'] as const satisfies readonly (keyof Query<unknown>)[];\n\n/** The clauses that describe the statement, which a populated relation's own query refuses by name. */\nexport const QUERY_STATEMENT_CLAUSES = [\n '$lock',\n ...QUERY_ROOT_OBJECT_CLAUSES,\n ...QUERY_ROOT_NUMBER_CLAUSES,\n] as const satisfies readonly (keyof Query<unknown>)[];\n\ntype RelationClause = (\n | typeof QUERY_OBJECT_CLAUSES\n | typeof QUERY_NUMBER_CLAUSES\n | typeof QUERY_BOOLEAN_CLAUSES\n)[number];\n\n/**\n * A populated relation's own query: the clause groups its runtime check accepts, so the two cannot\n * drift, and a clause added to {@link Query} stays off it until it joins one of them.\n */\nexport type RelationQuery<E = object, Raw = QueryRaw> = Pick<Query<E, Raw>, RelationClause> & {\n $required?: boolean;\n};\n\n/**\n * options to get a single record.\n */\nexport type QueryOne<E, Raw = QueryRaw> = Except<Query<E, Raw>, '$limit'>;\n\n/**\n * options to get an unique record.\n */\nexport type QueryUnique<E, Raw = QueryRaw> = Pick<QueryOne<E, Raw>, '$select' | '$exclude' | '$populate' | '$where'>;\n\n/**\n * The clauses that shape a row, captured as key sets rather than maps: a naked type parameter skips\n * excess-property checks, while a key set fails its own constraint on a typo.\n * @internal\n */\ntype QueryProjection<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n Raw = QueryRaw,\n> = {\n $select?: QuerySelect<E, S, V> | readonly Raw[];\n $exclude?: QuerySelect<E, X, V>;\n $populate?: QueryPopulate<E, Raw, P>;\n // Narrowing the captured names to the to-many ones leaves a to-one relation no key here at all,\n // so counting one is an excess property rather than a value to check.\n $count?: QueryCount<E, Raw, C & ToManyRelationKey<E>>;\n};\n\n/**\n * A {@link Query} whose projection is captured, so {@link QueryFindResult} can shape the row.\n */\nexport type QueryProjected<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E> = never,\n Raw = QueryRaw,\n> = Query<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;\n\n/**\n * A {@link QueryOne} whose projection is captured, so {@link QueryFindResult} can shape the row.\n */\nexport type QueryOneProjected<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E> = never,\n Raw = QueryRaw,\n> = QueryOne<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;\n\n/**\n * The keys a query comes back with, as the runtime projects them: a positive `$select`'s, or every\n * field minus what `$select` or `$exclude` subtracts, plus the populated relations.\n * @internal\n */\ntype ProjectedKeys<E, S, V, X, P> =\n | ([V] extends [false | 0] ? Exclude<FieldKey<E>, S> : [S] extends [never] ? Exclude<FieldKey<E>, X> : S)\n | P;\n\n/**\n * Whether every entry of the captured map says the same thing: all selected, or all subtracted.\n * @internal\n */\ntype IsUniform<V> = [V] extends [true | 1] ? true : [V] extends [false | 0] ? true : false;\n\n/**\n * A find's row: the entity narrowed to what the query projected and populated, so reading anything\n * else does not compile. The entity itself where the projection is raw, absent or not uniform.\n * @example `QueryFindResult<User, 'id' | 'name'>`\n */\nexport type QueryFindResult<\n E,\n S extends FieldKey<E> = never,\n // A whitelist by default, so the hand-written form reads `QueryFindResult<User, 'id' | 'name'>`.\n V = true,\n X extends FieldKey<E> = never,\n P extends RelationKey<E> = never,\n C extends RelationKey<E> = never,\n> = QueryProjectedRow<E, S, V, X, P, C> & CountedRelations<C>;\n\n/**\n * The `_count` a query asked for, or an inert intersection member when it asked for none - so a read\n * without `$count` keeps exactly the row type it had.\n */\ntype CountedRelations<C extends PropertyKey> = [C] extends [never]\n ? unknown\n : { [K in typeof COUNT_RESULT_KEY]: { [R in C]: number } };\n\n/** @internal */\ntype QueryProjectedRow<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n> = [S | X] extends [never]\n ? E\n : IsUniform<V> extends true\n ? [PopulatedToMany<E, P>] extends [never]\n ? // `Pick`, not a key remap: an entity keyed by an index signature - a content type defined at\n // runtime - has `string` for its keys, and a remap keeps no literal one, so every projection\n // over one came back as `{}`.\n Pick<E, ProjectedKeys<E, S, V, X, P> & keyof E>\n : // A populated to-many is always a list, empty where the parent has no children, so it maps\n // and counts without a guard. Only that promotion needs a second member, and only a query\n // that populates one pays for it; every other key keeps the modifier the entity declared,\n // a to-one relation included, since a join that finds no row leaves it absent.\n Pick<E, Exclude<ProjectedKeys<E, S, V, X, P>, PopulatedToMany<E, P>> & keyof E> & {\n [K in PopulatedToMany<E, P>]-?: NonNullable<E[K]>;\n }\n : E;\n\n/** The to-many relations a query populated, which come back as lists rather than as optional ones. */\ntype PopulatedToMany<E, P> = Extract<P, ToManyRelationKey<E>>;\n\n/**\n * stringified query.\n */\nexport type QueryStringified = {\n [K in keyof Query<unknown>]?: string;\n};\n\n/** What upserting one row reports. `created` is only knowable for a single statement, so a batch has none. */\nexport type QueryUpsertOneResult<E> = {\n readonly id?: WrittenId<E>;\n readonly changes?: number;\n /** Whether the record was created (`true`) or updated (`false`), where the dialect can tell. */\n readonly created?: boolean;\n};\n\n/**\n * What upserting many rows reports. `ids` is payload-aligned like an insert's, so it zips with the\n * rows that were passed, and carries a composite key as the map naming it.\n */\nexport type QueryUpsertManyResult<E> = {\n readonly ids: (WrittenId<E> | undefined)[];\n readonly changes?: number;\n};\n\n/**\n * result of an update operation, as the driver reports it - which is what `run` hands back, where\n * there is no entity to name the ids against. The `QueryUpsert*Result` pair is the entity-level shape.\n */\nexport type QueryUpdateResult = {\n /**\n * number of affected records.\n */\n changes?: number;\n /**\n * the IDs the statement reported, in payload order, `undefined` where it reported none for that\n * row - a MongoDB upsert names only the documents it inserted. Exact on `'returning'` dialects;\n * inferred from the driver header on the others (see {@link InsertIdSource}), and absent\n * altogether when the header reports nothing.\n */\n ids?: (PrimaryKey | undefined)[];\n /**\n * first inserted ID.\n */\n firstId?: PrimaryKey;\n /**\n * whether the record was created (`true`) or updated (`false`).\n * `undefined` when the dialect cannot determine this (e.g. SQLite).\n */\n created?: boolean;\n};\n",
|
|
6
|
+
"import type { FieldKey, JsonFieldPaths, RelationKey, RelationTarget, ToManyRelationKey, WrittenId } from './entity.js';\nimport type { QueryLock } from './queryLock.js';\nimport type { QueryRaw } from './queryRaw.js';\nimport type { QueryWhere } from './queryWhere.js';\nimport type { BooleanLike, Except, IsMany, PrimaryKey } from './utility.js';\nimport type { QueryVectorQuery, QueryVectorSearch } from './vector.js';\n\nexport type QueryOptions = {\n /**\n * Toggle named entity filters for this query. `false` disables all filters;\n * `{ softDelete: false }` disables one; `{ myFilter: true }` force-enables a `default: false` filter.\n * Security filters cannot be disabled here.\n */\n filters?: false | Record<string, boolean>;\n /**\n * Delete only: physically remove rows instead of soft-deleting, ignoring the soft-delete filter so\n * already-deleted rows are removed too. No effect on entities without a soft-delete field.\n */\n hardDelete?: boolean;\n /**\n * `updateMany`/`deleteMany` only: address every row of the table on purpose. Without it a bulk write\n * that names none - no `$where` and no `$limit` - is refused, since a forgotten filter and the whole\n * table look alike. The entity's own filters never count as naming one.\n */\n unfiltered?: boolean;\n};\n\n/**\n * What a statement is rendered with, on top of the options its caller passed. Kept apart from\n * {@link QueryOptions} because that one is public - it is the third argument of every querier method -\n * and none of this is a caller's to set: an alias is the dialect's to choose and to spell.\n */\nexport type QueryRenderOptions = QueryOptions & {\n /** The alias columns are read off, escaped by the dialect unless {@link escapedPrefix} spells it. */\n prefix?: string;\n /**\n * The prefix already written out, for the one caller whose row is not an identifier: a trigger reads\n * `NEW.\"col\"`, where `NEW` is a record the engine declares, and quoting it names a table that is not\n * in scope. Defaults to {@link prefix} escaped.\n */\n escapedPrefix?: string;\n /** Whether to infer the alias where none is given. */\n autoPrefix?: boolean;\n};\n\n/**\n * Field selection - `{ name: true }` whitelists fields; relations go in `$populate`. Declared over\n * `F extends keyof E`, like every map keyed by an entity's members, so each key stays linked to its\n * property and an editor rename reaches it. `F` is also how a projection passes its captured key set.\n */\nexport type QuerySelect<E, F extends keyof E = FieldKey<E>, V = BooleanLike> = {\n [K in F]?: V;\n};\n\n/**\n * Accepted `$select` value: a field map, or raw SQL projections built with `raw()`\n * (e.g. ``[raw`*`, raw`LOG10(points)`.as('score')]``). The raw form is SQL-only.\n */\nexport type QuerySelectValue<E, Raw = QueryRaw> = QuerySelect<E> | readonly Raw[];\n\n/**\n * Fields to exclude from the query result - `{ name: true }` blacklists fields.\n * Mutually exclusive with positive field selections in `$select`.\n */\nexport type QueryExclude<E> = QuerySelect<E>;\n\n/**\n * relation population map.\n */\nexport type QueryPopulate<E, Raw = QueryRaw, R extends keyof E = RelationKey<E>> = {\n [K in R]?: BooleanLike | QueryPopulateRelationOptions<E[K], Raw>;\n};\n\n/**\n * The key a read carries its relation tallies under. One spelling for the type and the runtime that\n * fills it: they sit in different modules, so a drift would type-check and answer `undefined`.\n */\nexport const COUNT_RESULT_KEY = '_count';\n\n/**\n * How many rows each named relation holds per parent, `true` for all of them or a filter to narrow\n * which ones count: a correlated count in the read's own statement, so no related row is loaded. Comes\n * back under `_count`, which keeps it clear of a relation of the same name `$populate` filled.\n */\nexport type QueryCount<E, Raw = QueryRaw, R extends keyof E = ToManyRelationKey<E>> = {\n [K in R]?: BooleanLike | QueryFilter<RelationTarget<E[K]>, Raw>;\n};\n\n/**\n * query conflict paths - subset of field keys used to detect upsert conflicts.\n */\nexport type QueryConflictPaths<E> = QuerySelect<E, FieldKey<E>, true>;\n\n/**\n * Options to populate a relation declared as `V`, by its cardinality.\n */\nexport type QueryPopulateRelationOptions<V, Raw = QueryRaw> =\n IsMany<V> extends true\n ? RelationQuery<RelationTarget<V>, Raw>\n : QueryUnique<RelationTarget<V>, Raw> & { $required?: boolean };\n\n/**\n * The per-request context parameterized filters read, set with `withContext(ctx, cb)`. An interface,\n * so its keys can be typed once: `declare module 'uql-orm' { interface UqlContext { tenantId: number } }`.\n */\nexport interface UqlContext {\n [key: string]: unknown;\n}\n\n/**\n * A filter's `$where` fragment: a plain fragment, or a function of the ambient {@link UqlContext}.\n * Return `undefined` when the condition can't resolve (see {@link FilterOptions.onMissing}).\n */\nexport type FilterWhere<E> = QueryWhere<E> | ((context: UqlContext | undefined) => QueryWhere<E> | undefined);\n\n/**\n * What to do when a filter's condition returns `undefined`. `skip` omits it (convenience filters);\n * `throw` fails closed (the default for `security` filters).\n */\nexport type FilterOnMissing = 'skip' | 'throw';\n\n/**\n * Authoring shape for `@Entity({ filters })` / `@Filter` / `defineFilter`.\n */\nexport type FilterOptions<E = unknown> = {\n readonly where: FilterWhere<E>;\n /** Applied to every query unless bypassed via `QueryOptions.filters`. Defaults to `true`. */\n readonly default?: boolean;\n} & (\n | {\n readonly security?: false;\n /** What to do when {@link FilterOptions.where} returns `undefined`. Defaults to `skip`. */\n readonly onMissing?: FilterOnMissing;\n }\n | {\n /**\n * Row-level-security filter: always applied (ignores `QueryOptions.filters` bypass) and\n * AND-merged so a client `$where` on the same field can't override it. It fails closed.\n */\n readonly security: true;\n readonly onMissing?: 'throw';\n }\n);\n\n/**\n * direction for the sort, and where nulls land in it.\n *\n * Unqualified, each engine has its own answer - Postgres and CockroachDB sort nulls last on `asc`, the\n * rest sort them first - so a placement is the only portable one. Engines with no `NULLS FIRST/LAST`\n * emulate it with a leading term, which no index can serve, which is why it is asked for and never\n * applied by default.\n */\nexport type QuerySortDirection = -1 | 1 | 'asc' | 'desc' | QuerySortNullsDirection;\n\n/** A {@link QuerySortDirection} stating where nulls land. */\nexport type QuerySortNullsDirection = 'ascNullsFirst' | 'ascNullsLast' | 'descNullsFirst' | 'descNullsLast';\n\n/**\n * Accepted value for a field in `$sort` - either a direction or a vector similarity search.\n */\nexport type QuerySortValue = QuerySortDirection | QueryVectorSearch;\n\n/**\n * Ordering parents by how many rows a to-many relation holds - \"the ten users with the most posts\".\n * The tally is computed per parent as a correlated count, never by loading the rows.\n */\nexport type QuerySortByCount = {\n $count: QuerySortDirection;\n};\n\n/** The fields of `E` a vector search can rank by. */\ntype VectorFieldKey<E> = { [P in FieldKey<E>]: NonNullable<E[P]> extends readonly number[] ? P : never }[FieldKey<E>];\n\n/**\n * Ordering parents by the row of a to-many nearest a vector, per vector field: its distance is the\n * smallest of theirs. Nothing to `$project`, since no one row of the parent's answers under it. Never\n * where the target has no vector, since an empty map would admit any value at all.\n */\nexport type QuerySortByNearest<E> = [VectorFieldKey<E>] extends [never]\n ? never\n : { [P in VectorFieldKey<E>]?: QueryVectorQuery };\n\n/**\n * Ordering by relevance to the `$text` at the root of `$where`, in either direction as any key sorts. The\n * object form also answers it under the name `$project` gives it, most relevant first unless `$order` says.\n */\nexport type QuerySortByText = {\n $text?: QuerySortDirection | { readonly $project: string; readonly $order?: QuerySortDirection };\n};\n\n/**\n * A row with the value a `$sort` projects under the name its `$project` gives - a vector's distance, or a\n * `$text` relevance - which is not inferred: `(await querier.findMany(Post, q)) as WithProjection<Post, 'score'>[]`.\n */\nexport type WithProjection<E, K extends string> = E & Record<K, number>;\n\n/**\n * A sort by fields, JSON paths, a to-one relation's fields, a to-many's `$count` or nearest row, a vector\n * distance, or - where `Root` says it sorts the queried entity itself, not a relation's rows - a `$text`\n * relevance or a distance it projects. One mapped type over the key sets: an intersection is checked once\n * per member, which made this the costliest.\n */\nexport type QuerySortMap<E, Root extends boolean = true, K extends keyof E = FieldKey<E> | RelationKey<E>> = {\n [P in K]?: P extends RelationKey<E>\n ? // A to-many has no single value to order by, so what it offers instead is its size or nearest row.\n IsMany<E[P]> extends true\n ? QuerySortByCount | QuerySortByNearest<RelationTarget<E[P]>>\n : QuerySortMap<RelationTarget<E[P]>, false>\n : NonNullable<E[P]> extends readonly number[]\n ? Root extends true\n ? QuerySortValue\n : QuerySortDirection | QueryVectorQuery\n : QuerySortDirection;\n} & ([JsonFieldPaths<E>] extends [never] ? unknown : { [P in JsonFieldPaths<E>]?: QuerySortDirection }) &\n (Root extends true ? QuerySortByText : unknown);\n\n/**\n * pager options.\n */\nexport type QueryPager = {\n /**\n * Index from where start the search\n */\n $skip?: number;\n\n /**\n * Max number of records to retrieve\n */\n $limit?: number;\n};\n\n/**\n * Which rows a statement addresses.\n */\nexport type QueryFilter<E, Raw = QueryRaw> = {\n /**\n * filtering options.\n */\n $where?: QueryWhere<E, Raw>;\n};\n\n/**\n * A filter plus the page `count` takes. No `$sort`: ordering picks *which* rows a page holds, never\n * how many, so a count that accepted one would promise an influence it cannot have.\n */\nexport type QueryPage<E, Raw = QueryRaw> = QueryFilter<E, Raw> & QueryPager;\n\n/**\n * A filter plus the ordering and page `updateMany`/`deleteMany` take. Both settle the\n * rows they address with a SELECT first, so the page is portable rather than MySQL-only, and a\n * vector `$sort` is as valid here as on a read: it ranks the settle query's rows, which has the\n * projection list to hold the distance. `$lock` stays off these, declared on {@link Query} instead.\n */\nexport type QuerySearch<E, Raw = QueryRaw> = QueryPage<E, Raw> & {\n /**\n * sorting options.\n */\n $sort?: QuerySortMap<E>;\n};\n\n/**\n * query options.\n */\nexport type Query<E, Raw = QueryRaw> = {\n /**\n * field selection - `{ name: true }` whitelists fields, or raw SQL projections\n * (``[raw`LOG10(points)`.as('score')]``, SQL dialects only - MongoDB rejects the raw-array form).\n * Mutually exclusive with `$exclude`.\n */\n $select?: QuerySelectValue<E, Raw>;\n\n /**\n * relation population options.\n */\n $populate?: QueryPopulate<E, Raw>;\n\n /**\n * how many rows each named relation holds, under `_count` on every row. See {@link QueryCount}.\n */\n $count?: QueryCount<E, Raw>;\n\n /**\n * field exclusion - `{ name: true }` blacklists fields. Mutually exclusive with positive `$select`.\n * Keys a relation is assembled from (a joined row's primary key, a to-many's foreign key) are kept\n * regardless, since subtracting them would leave the relation unfilled.\n */\n $exclude?: QueryExclude<E>;\n\n /**\n * sorting options, vector similarity search included: a SELECT is the one statement with a\n * projection list to hold the distance such a search computes.\n */\n $sort?: QuerySortMap<E>;\n\n /**\n * whether to return only distinct rows.\n */\n $distinct?: boolean;\n\n /**\n * Lock the rows this query returns, `SELECT ... FOR UPDATE`, inside an open transaction: outside one\n * it is refused, since the lock would drop before the rows are used. SQL only, and not the SQLite family.\n */\n $lock?: QueryLock;\n\n /**\n * How many candidates an ANN index explores before ranking a vector search, in that index's own units\n * (`hnsw.ef_search`, `numCandidates`...); ignored where the search is exact. Postgres needs a transaction.\n */\n $candidates?: number;\n\n // `$where`, `$skip` and `$limit` are declared here rather than intersected in from\n // {@link QueryFilter} and {@link QueryPager}: an assignability check against an intersection is\n // repeated per constituent, and every query in a consuming codebase pays that. The two shapes are\n // pinned together in `queryStatementClauses.test-d.ts` so the copies cannot drift.\n\n /**\n * filtering options.\n */\n $where?: QueryWhere<E, Raw>;\n\n /**\n * Index from where start the search\n */\n $skip?: number;\n\n /**\n * Max number of records to retrieve\n */\n $limit?: number;\n};\n\n/**\n * A {@link Query} as it travels as JSON, which a `raw` SQL fragment cannot: what the browser client takes,\n * and what an RPC contract (tRPC, oRPC, TanStack Start) declares as its input.\n */\nexport type WireQuery<E> = Query<E, never>;\n\n/**\n * `Query`'s clauses grouped by the shape of their value, for the wire parser and the relation query\n * check alike; `satisfies` keeps them in step with `Query`.\n */\nexport const QUERY_OBJECT_CLAUSES = [\n '$select',\n '$populate',\n '$exclude',\n '$where',\n '$sort',\n] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Object clauses only the statement itself takes: a populated relation's rows keep their declared type,\n * so a `$count` inside one would have no `_count` to land in.\n */\nexport const QUERY_ROOT_OBJECT_CLAUSES = ['$count'] as const satisfies readonly (keyof Query<unknown>)[];\n\nexport const QUERY_NUMBER_CLAUSES = ['$skip', '$limit'] as const satisfies readonly (keyof Query<unknown>)[];\n\n/**\n * Number clauses only the statement itself takes - the numeric mirror of {@link QUERY_ROOT_OBJECT_CLAUSES}.\n * `$candidates` tunes the index behind a vector search, and a vector search only ever ranks the rows\n * the statement returns, so a relation's own query has nothing to tune.\n */\nexport const QUERY_ROOT_NUMBER_CLAUSES = ['$candidates'] as const satisfies readonly (keyof Query<unknown>)[];\n\nexport const QUERY_BOOLEAN_CLAUSES = ['$distinct'] as const satisfies readonly (keyof Query<unknown>)[];\n\n/** The clauses that describe the statement, which a populated relation's own query refuses by name. */\nexport const QUERY_STATEMENT_CLAUSES = [\n '$lock',\n ...QUERY_ROOT_OBJECT_CLAUSES,\n ...QUERY_ROOT_NUMBER_CLAUSES,\n] as const satisfies readonly (keyof Query<unknown>)[];\n\ntype RelationClause = (\n | typeof QUERY_OBJECT_CLAUSES\n | typeof QUERY_NUMBER_CLAUSES\n | typeof QUERY_BOOLEAN_CLAUSES\n)[number];\n\n/**\n * A populated relation's own query: the clause groups its runtime check accepts, so the two cannot\n * drift, and a clause added to {@link Query} stays off it until it joins one of them.\n */\nexport type RelationQuery<E = object, Raw = QueryRaw> = Pick<Query<E, Raw>, RelationClause> & {\n $required?: boolean;\n};\n\n/**\n * options to get a single record.\n */\nexport type QueryOne<E, Raw = QueryRaw> = Except<Query<E, Raw>, '$limit'>;\n\n/**\n * options to get an unique record.\n */\nexport type QueryUnique<E, Raw = QueryRaw> = Pick<QueryOne<E, Raw>, '$select' | '$exclude' | '$populate' | '$where'>;\n\n/**\n * The clauses that shape a row, captured as key sets rather than maps: a naked type parameter skips\n * excess-property checks, while a key set fails its own constraint on a typo.\n * @internal\n */\ntype QueryProjection<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n Raw = QueryRaw,\n> = {\n $select?: QuerySelect<E, S, V> | readonly Raw[];\n $exclude?: QuerySelect<E, X, V>;\n $populate?: QueryPopulate<E, Raw, P>;\n // Narrowing the captured names to the to-many ones leaves a to-one relation no key here at all,\n // so counting one is an excess property rather than a value to check.\n $count?: QueryCount<E, Raw, C & ToManyRelationKey<E>>;\n};\n\n/**\n * A {@link Query} whose projection is captured, so {@link QueryFindResult} can shape the row.\n */\nexport type QueryProjected<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E> = never,\n Raw = QueryRaw,\n> = Query<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;\n\n/**\n * A {@link QueryOne} whose projection is captured, so {@link QueryFindResult} can shape the row.\n */\nexport type QueryOneProjected<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E> = never,\n Raw = QueryRaw,\n> = QueryOne<E, Raw> & QueryProjection<E, S, V, X, P, C, Raw>;\n\n/**\n * The keys a query comes back with, as the runtime projects them: a positive `$select`'s, or every\n * field minus what `$select` or `$exclude` subtracts, plus the populated relations.\n * @internal\n */\ntype ProjectedKeys<E, S, V, X, P> =\n | ([V] extends [false | 0] ? Exclude<FieldKey<E>, S> : [S] extends [never] ? Exclude<FieldKey<E>, X> : S)\n | P;\n\n/**\n * Whether every entry of the captured map says the same thing: all selected, or all subtracted.\n * @internal\n */\ntype IsUniform<V> = [V] extends [true | 1] ? true : [V] extends [false | 0] ? true : false;\n\n/**\n * A find's row: the entity narrowed to what the query projected and populated, so reading anything\n * else does not compile. The entity itself where the projection is raw, absent or not uniform.\n * @example `QueryFindResult<User, 'id' | 'name'>`\n */\nexport type QueryFindResult<\n E,\n S extends FieldKey<E> = never,\n // A whitelist by default, so the hand-written form reads `QueryFindResult<User, 'id' | 'name'>`.\n V = true,\n X extends FieldKey<E> = never,\n P extends RelationKey<E> = never,\n C extends RelationKey<E> = never,\n> = QueryProjectedRow<E, S, V, X, P, C> & CountedRelations<C>;\n\n/**\n * The `_count` a query asked for, or an inert intersection member when it asked for none - so a read\n * without `$count` keeps exactly the row type it had.\n */\ntype CountedRelations<C extends PropertyKey> = [C] extends [never]\n ? unknown\n : { [K in typeof COUNT_RESULT_KEY]: { [R in C]: number } };\n\n/** @internal */\ntype QueryProjectedRow<\n E,\n S extends FieldKey<E>,\n V,\n X extends FieldKey<E>,\n P extends RelationKey<E>,\n C extends RelationKey<E>,\n> = [S | X] extends [never]\n ? E\n : IsUniform<V> extends true\n ? [PopulatedToMany<E, P>] extends [never]\n ? // `Pick`, not a key remap: an entity keyed by an index signature - a content type defined at\n // runtime - has `string` for its keys, and a remap keeps no literal one, so every projection\n // over one came back as `{}`.\n Pick<E, ProjectedKeys<E, S, V, X, P> & keyof E>\n : // A populated to-many is always a list, empty where the parent has no children, so it maps\n // and counts without a guard. Only that promotion needs a second member, and only a query\n // that populates one pays for it; every other key keeps the modifier the entity declared,\n // a to-one relation included, since a join that finds no row leaves it absent.\n Pick<E, Exclude<ProjectedKeys<E, S, V, X, P>, PopulatedToMany<E, P>> & keyof E> & {\n [K in PopulatedToMany<E, P>]-?: NonNullable<E[K]>;\n }\n : E;\n\n/** The to-many relations a query populated, which come back as lists rather than as optional ones. */\ntype PopulatedToMany<E, P> = Extract<P, ToManyRelationKey<E>>;\n\n/**\n * stringified query.\n */\nexport type QueryStringified = {\n [K in keyof Query<unknown>]?: string;\n};\n\n/** What upserting one row reports. `created` is only knowable for a single statement, so a batch has none. */\nexport type QueryUpsertOneResult<E> = {\n readonly id?: WrittenId<E>;\n readonly changes?: number;\n /** Whether the record was created (`true`) or updated (`false`), where the dialect can tell. */\n readonly created?: boolean;\n};\n\n/**\n * What upserting many rows reports. `ids` is payload-aligned like an insert's, so it zips with the\n * rows that were passed, and carries a composite key as the map naming it.\n */\nexport type QueryUpsertManyResult<E> = {\n readonly ids: (WrittenId<E> | undefined)[];\n readonly changes?: number;\n};\n\n/**\n * result of an update operation, as the driver reports it - which is what `run` hands back, where\n * there is no entity to name the ids against. The `QueryUpsert*Result` pair is the entity-level shape.\n */\nexport type QueryUpdateResult = {\n /**\n * number of affected records.\n */\n changes?: number;\n /**\n * the IDs the statement reported, in payload order, `undefined` where it reported none for that\n * row - a MongoDB upsert names only the documents it inserted. Exact on `'returning'` dialects;\n * inferred from the driver header on the others (see {@link InsertIdSource}), and absent\n * altogether when the header reports nothing.\n */\n ids?: (PrimaryKey | undefined)[];\n /**\n * first inserted ID.\n */\n firstId?: PrimaryKey;\n /**\n * whether the record was created (`true`) or updated (`false`).\n * `undefined` when the dialect cannot determine this (e.g. SQLite).\n */\n created?: boolean;\n};\n",
|
|
7
7
|
"import type { QueryContext, RelationAggregateSpec, SqlQueryDialect } from './dialect.js';\nimport type { Type } from './utility.js';\n\n/** What a `raw` callback receives. See {@link QueryRawFn}. */\nexport type QueryRawRenderOptions = {\n /** The dialect rendering the SQL. */\n dialect: SqlQueryDialect;\n /** The alias of the table in scope, unescaped; empty where there is none. */\n prefix: string;\n /** {@link prefix} escaped, with its trailing dot. */\n escapedPrefix: string;\n /** The query context the SQL is written into. */\n ctx: QueryContext;\n /**\n * The entity being rendered, which a ref read off a definition's map resolves its column against: a\n * computed field's own, or the one whose schema is built. Absent where a statement renders SQL.\n */\n entity?: Type<unknown>;\n};\n\n/** {@link QueryRawRenderOptions} as the callers along the way fill them in, every one still optional. */\nexport type QueryRawFnOptions = Partial<QueryRawRenderOptions>;\n\n/**\n * A `raw` callback: write into `ctx`, or return a string or number to have it appended. Anything else\n * it returns is ignored, which is why the return type is `unknown` rather than `void | Scalar` - the\n * latter rejected `({ ctx }) => ctx.append(...)`, the form every computed field is written in, because\n * TypeScript's \"returning a value where void is expected\" allowance does not apply to a union.\n */\nexport type QueryRawFn = (opts: QueryRawRenderOptions) => unknown;\n\nexport const RAW_VALUE: unique symbol = Symbol('rawValue');\nexport const RAW_ALIAS: unique symbol = Symbol('rawAlias');\nexport const RAW_TEXT: unique symbol = Symbol('rawText');\n\nexport class QueryRaw {\n readonly [RAW_VALUE]: QueryRawFn;\n readonly [RAW_ALIAS]?: string;\n /**\n * The SQL verbatim, set only where it is a constant: a template that interpolates nothing binds no\n * value and reads no column, so it needs no dialect to render. What a DDL clause with nowhere to\n * bind reads - see {@link constantSql}.\n */\n readonly [RAW_TEXT]?: string;\n\n constructor(value: QueryRawFn, alias?: string, text?: string) {\n this[RAW_VALUE] = value;\n this[RAW_ALIAS] = alias;\n this[RAW_TEXT] = text;\n }\n\n /** The same expression under an alias, for a `$select` projection. */\n as(alias: string): QueryRaw {\n return new QueryRaw(this[RAW_VALUE], alias, this[RAW_TEXT]);\n }\n\n /** Writes the expression into `opts.ctx`. The alias is the projection's to write, after the term. */\n render(opts: QueryRawRenderOptions): void {\n const emitted = this[RAW_VALUE](opts);\n if (typeof emitted === 'string' || (typeof emitted === 'number' && !Number.isNaN(emitted))) {\n opts.ctx.append(String(emitted));\n }\n }\n}\n\n/**\n * A field of an entity as SQL, read off `refs(Entity)` or a definition's refs: interpolated into `raw`, it\n * renders as the field's column. Its `key` is how an index tells a column from an expression.\n */\nexport class ColumnRef<K extends string = string> extends QueryRaw {\n constructor(\n readonly key: K,\n value: QueryRawFn,\n ) {\n super(value);\n }\n}\n\n/**\n * A relation aggregate as SQL, read off a `computed` field's refs: `(user) => user.resources.count()`.\n * It renders as the correlated subquery a `$count` reads, so a field holding one is read, filtered and\n * sorted like any other.\n *\n * `V` is the value it reads and `Storable` whether a trigger could keep it, both carried in phantom\n * fields so the aggregate a field declares decides the property's type and refuses `stored: true` on\n * one no delta can maintain.\n */\nexport class RelationAggregate<V = unknown, Storable extends boolean = boolean> extends QueryRaw {\n declare private readonly __value: V;\n declare private readonly __storable: Storable;\n\n constructor(\n /** What it reads, kept beside the SQL so a read decodes the value the way the target's field does. */\n readonly spec: RelationAggregateSpec,\n value: QueryRawFn,\n ) {\n super(value);\n }\n}\n",
|
|
8
8
|
"import type { EntityMeta } from '../type/index.js';\n\nexport function throwPendingTransaction(): never {\n throw TypeError('pending transaction');\n}\n\nexport function throwNoPendingTransaction(): never {\n throw TypeError('not a pending transaction');\n}\n\nexport function clone<T>(value: T): T {\n if (typeof value !== 'object' || value === null) {\n return value;\n }\n if (Array.isArray(value)) {\n return value.map((it) => clone(it)) as T;\n }\n return { ...value };\n}\n\n/** Whether `obj` has at least one enumerable key. Narrows away `undefined`/`null` for callers. */\nexport function hasKeys<T>(obj: T): obj is NonNullable<T> {\n if (typeof obj !== 'object' || obj === null) return false;\n for (const _ in obj) return true;\n return false;\n}\n\n/**\n * Whether any enumerable key of `obj` satisfies `pred`, short-circuiting on the first match\n * without materializing a key array (unlike `Object.keys(obj).some(pred)`).\n */\nexport function someKey<T extends object>(obj: T, pred: (key: keyof T & string) => boolean): boolean {\n for (const key in obj) {\n if (pred(key)) return true;\n }\n return false;\n}\n\n/** Whether `key` names an operator (`$eq`, `$push`...) rather than a field. */\nexport function isOperatorKey(key: string): boolean {\n return key.startsWith('$');\n}\n\n/** Whether `value` is a non-empty object with an operator key (`$eq`, `$push`...): the one test every dialect classifies with. */\nexport function isOperatorObject(value: unknown): value is Record<string, unknown> {\n return isRecord(value) && someKey(value, isOperatorKey);\n}\n\n/** Whether `value` is an object that is not an array, whose keys can be read. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nexport function getKeys<T extends object>(obj: T | null | undefined): (keyof T & string)[] {\n return obj ? (Object.keys(obj) as (keyof T & string)[]) : [];\n}\n\n/** The entries of `record` holding a value: a key declared but left `undefined` is no entry at all. */\nexport function definedEntries<K extends string, V>(record: Partial<Record<K, V>>): [K, V][] {\n return (Object.entries(record) as [K, V | undefined][]).filter((entry): entry is [K, V] => entry[1] !== undefined);\n}\n\n/**\n * The entity's own name, declared or its class's. `meta.name` holds only what the author wrote, so\n * the fallback is what an entity that named no table is called - which is why the sites spelling this\n * out reached for three different fallbacks, `?? ''` among them, and named nothing at all.\n */\nexport function entityName<E>(meta: EntityMeta<E>): string {\n return meta.name ?? meta.entity.name;\n}\n\n/**\n * Whether `value` addresses a row by itself rather than naming columns: every primitive, and the\n * object ids a driver deals in (`ObjectId`, `Date`, bytes). Only a plain object names columns, which\n * is what a `$where` map and a composite key's id object both are; an array is a list of either.\n */\nexport function isScalarId(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) {\n return true;\n }\n if (Array.isArray(value)) {\n return false;\n }\n // `null` as well as `Object.prototype`: an object with no prototype is what a query-string parser\n // hands back (`qs`, express's `req.params`), and reading one as a bare id would name one column\n // with a map of several.\n const proto = Object.getPrototypeOf(value);\n return proto !== Object.prototype && proto !== null;\n}\n\n/** Whether `value` is a plain object naming columns, the one shape a `$where` takes. */\nexport function isWhereMap(value: unknown): value is Record<string, unknown> {\n return !Array.isArray(value) && !isScalarId(value);\n}\n",
|
|
9
9
|
"import type { QueryOptions, WireQuery } from '../type/index.js';\n// the clause lists themselves, not the barrel: this module is in the browser bundle's graph\nimport {\n QUERY_BOOLEAN_CLAUSES,\n QUERY_NUMBER_CLAUSES,\n QUERY_OBJECT_CLAUSES,\n QUERY_ROOT_NUMBER_CLAUSES,\n QUERY_ROOT_OBJECT_CLAUSES,\n} from '../type/query.js';\n// the brand alone, not the class: importing `QueryRaw` for an `instanceof` kept it, and `ColumnRef`\n// with it, in the browser bundle, which is on a size budget\nimport { RAW_VALUE } from '../type/queryRaw.js';\n// the specific util module, not the barrel, so the browser bundle does not pull in entity metadata\nimport { getKeys, isWhereMap } from '../util/object.util.js';\n// the error class alone, from its own leaf module: `queryError.ts` carries every driver's code map\nimport { UqlUsageError } from '../util/uqlError.js';\n\n/**\n * Keys accepted from the wire - query structure ({@link Query}) plus the `hardDelete`/`count` scalar\n * flags. Anything else (e.g. `filters`, `context`, `$entity`) is dropped so a remote client can't\n * bypass a security filter or inject ambient context - those are server-only. The `satisfies` ties\n * every entry to a real query/option key, so a typo or a renamed option fails to compile.\n */\nconst ALLOWED_QUERY_KEYS = new Set<string>([\n ...QUERY_OBJECT_CLAUSES,\n ...QUERY_ROOT_OBJECT_CLAUSES,\n ...QUERY_NUMBER_CLAUSES,\n ...QUERY_ROOT_NUMBER_CLAUSES,\n ...QUERY_BOOLEAN_CLAUSES,\n 'hardDelete',\n 'count',\n] satisfies (keyof WireQuery<unknown> | keyof Pick<QueryOptions, 'hardDelete'> | 'count')[]);\n\n/**\n * Keys that mean something locally but that this transport can never honor, so they are rejected\n * rather than dropped like the rest. Each request runs on its own auto-committing connection, so a\n * row lock taken here is released before the response is written: honoring `$lock` is impossible,\n * and ignoring it would hand the caller a read they believe is serialized and is not.\n */\nconst REJECTED_QUERY_KEYS = new Set<string>(['$lock'] satisfies (keyof WireQuery<unknown>)[]);\n\n/**\n * Parse raw query-string entries (with JSON-stringified values) into a UQL query object.\n * Symmetric counterpart of {@link stringifyQuery}. Only {@link ALLOWED_QUERY_KEYS} are honored.\n */\nexport function parseQueryParams(params: Record<string, unknown> = {}): WireQuery<unknown> {\n const query: Record<string, unknown> = {};\n for (const key of getKeys(params)) {\n if (REJECTED_QUERY_KEYS.has(key)) {\n throw new UqlUsageError(`'${key}' is not supported over HTTP`);\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 new UqlUsageError(\"'$where' must be a JSON object\");\n }\n\n // A query string carries every value as text, so what decodes a clause is the shape its group\n // declares. `'false'` is the reason the boolean pass exists rather than the raw value being taken:\n // it is a non-empty string, so a `$distinct=false` would otherwise read as asking for one.\n for (const key of [...QUERY_NUMBER_CLAUSES, ...QUERY_ROOT_NUMBER_CLAUSES]) {\n if (query[key] !== undefined) {\n query[key] = Number(query[key]);\n }\n }\n for (const key of QUERY_BOOLEAN_CLAUSES) {\n if (query[key] !== undefined) {\n query[key] = query[key] === true || query[key] === 'true';\n }\n }\n\n return query as WireQuery<unknown>;\n}\n\n/**\n * Serialize a UQL query object into a percent-encoded query string where object values\n * are JSON-stringified. Symmetric counterpart of {@link parseQueryParams}.\n */\nexport function stringifyQuery(query?: Record<string, unknown>): string {\n if (!query) {\n return '';\n }\n const params = new URLSearchParams();\n for (const key of getKeys(query)) {\n const value = query[key];\n if (value === undefined) {\n continue;\n }\n params.append(key, typeof value === 'object' && value !== null ? wireJson(value) : String(value));\n }\n const qs = params.toString();\n return qs ? `?${qs}` : '';\n}\n\n/**\n * What leaves the browser, as JSON, refusing what JSON keeps nothing of rather than letting the server\n * build a statement around the remains. A `raw` fragment renders SQL against a dialect the client does not\n * have and arrives as `{}`; binary arrives as an object keyed by index. A `Date` is not among them - it\n * serializes to ISO 8601, which is what a date column reads. This is what a cast, or a JavaScript caller,\n * hits where the client's types already refuse a fragment.\n */\nexport function wireJson(value: unknown): string {\n return JSON.stringify(value, (_key: string, held: unknown) => {\n if (typeof held !== 'object' || held === null) {\n return held;\n }\n if (RAW_VALUE in held) {\n throw new TypeError('raw SQL cannot travel over HTTP: what leaves the browser is JSON');\n }\n // A blob is a field value, so no type parameter reaches it: this is the only place it is caught.\n if (held instanceof ArrayBuffer || ArrayBuffer.isView(held)) {\n throw new TypeError('binary cannot travel over HTTP: what leaves the browser is JSON');\n }\n return held;\n });\n}\n",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"import { CRUD_ROUTES, entityPath, type HttpMethod } from '../../http/contract.js';\nimport { stringifyQuery } from '../../http/query.js';\nimport type {\n EntityWrite,\n EntityId,\n FieldKey,\n QueryFilter,\n QueryFindResult,\n QueryOneProjected,\n QueryOptions,\n QueryPage,\n QueryProjected,\n QuerySearch,\n RelationKey,\n RequestCountedSuccessResponse,\n RequestSuccessResponse,\n Type,\n UpdateWrite,\n WireQuery,\n WrittenId,\n} from '../../type/index.js';\nimport { isScalarId } from '../../util/object.util.js';\nimport { get, query as httpQuery, patch, post, put, remove } from '../http/index.js';\nimport type { ClientQuerier, RequestFindOptions, RequestOptions } from '../type/index.js';\n\nexport type HttpQuerierDefaults = {\n /**\n * headers sent with every request from this instance, merged under per-call headers.\n * Create one instance per request (e.g. during SSR) to scope auth headers safely.\n */\n readonly headers?: Record<string, string>;\n /**\n * transport for read queries (findOne, findMany, count). 'QUERY' (RFC 10008) sends the\n * JSON query in the request body, avoiding URL-length limits for large queries; requires\n * infrastructure (proxies, CDNs) that forwards the QUERY method. Defaults to 'GET'.\n */\n readonly readMethod?: Extract<HttpMethod, 'GET' | 'QUERY'>;\n /**\n * The URL segment an entity is addressed by, defaulting to its kebab-cased class name - the same\n * option the server handler takes, so one map serves both. State it where the default cannot: a\n * build that minifies class names renames every route.\n */\n readonly entityPath?: (entity: Type<unknown>) => string;\n};\n\n/** The id as one path segment, refusing a composite key, which has no spelling in `/:id` yet. Callers are `async`. */\nfunction idSegment<E>(entity: Type<E>, id: EntityId<E>): string {\n if (!isScalarId(id)) {\n throw new TypeError(`'${entity.name}' was addressed by an id object, which the HTTP route cannot carry.`);\n }\n return String(id);\n}\n\nexport class HttpQuerier implements ClientQuerier {\n constructor(\n readonly basePath: string,\n readonly defaults: HttpQuerierDefaults = {},\n ) {}\n\n async findOneById<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n id: EntityId<E>,\n q?: QueryOneProjected<E, S, V, X, P, C, never>,\n opts?: RequestOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C> | undefined>> {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(q);\n return get<QueryFindResult<E, S, V, X, P, C> | undefined>(\n `${basePath}/${idSegment(entity, id)}${qs}`,\n this.buildOptions(opts),\n );\n }\n\n findOne<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n q: QueryOneProjected<E, S, V, X, P, C, never>,\n opts?: RequestOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C> | undefined>> {\n return this.read<QueryFindResult<E, S, V, X, P, C> | undefined>(\n `${this.getBasePath(entity)}${CRUD_ROUTES.findOne.path}`,\n q,\n opts,\n );\n }\n\n findMany<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n q: QueryProjected<E, S, V, X, P, C, never>,\n opts?: RequestFindOptions,\n ): Promise<RequestSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>> {\n const data: WireQuery<E> & { count?: boolean } = { ...q };\n if (opts?.count) {\n data.count = true;\n }\n return this.read<QueryFindResult<E, S, V, X, P, C>[]>(this.getBasePath(entity), data, opts);\n }\n\n async findManyAndCount<\n E extends object,\n const S extends FieldKey<E> = never,\n const V = true,\n const X extends FieldKey<E> = never,\n const P extends RelationKey<E> = never,\n const C extends RelationKey<E> = never,\n >(\n entity: Type<E>,\n q: QueryProjected<E, S, V, X, P, C, never>,\n opts?: RequestFindOptions,\n ): Promise<RequestCountedSuccessResponse<QueryFindResult<E, S, V, X, P, C>[]>> {\n const response = await this.findMany(entity, q, { ...opts, count: true });\n if (typeof response.count !== 'number') {\n throw new TypeError('findManyAndCount response has an invalid count');\n }\n return { ...response, count: response.count };\n }\n\n count<E extends object>(entity: Type<E>, q?: QueryPage<E, never>, opts?: RequestOptions) {\n return this.read<number>(`${this.getBasePath(entity)}${CRUD_ROUTES.count.path}`, q, opts);\n }\n\n /** The `count` route capped at one row, so existence needs no endpoint of its own. */\n async exists<E extends object>(entity: Type<E>, q?: QueryFilter<E, never>, opts?: RequestOptions) {\n const res = await this.count(entity, { ...q, $limit: 1 }, opts);\n return { ...res, data: res.data > 0 };\n }\n\n insertOne<E extends object>(entity: Type<E>, payload: EntityWrite<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: EntityWrite<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: UpdateWrite<E, never>,\n opts?: RequestOptions,\n ) {\n const basePath = this.getBasePath(entity);\n return patch<number>(`${basePath}/${idSegment(entity, id)}`, payload, this.buildOptions(opts));\n }\n\n updateMany<E extends object>(\n entity: Type<E>,\n q: QuerySearch<E, never>,\n payload: UpdateWrite<E, never>,\n opts?: RequestOptions,\n ) {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(q);\n return patch<number>(`${basePath}${qs}`, payload, this.buildOptions(opts));\n }\n\n saveOne<E extends object>(entity: Type<E>, payload: EntityWrite<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: EntityWrite<E>[], opts?: RequestOptions) {\n const basePath = this.getBasePath(entity);\n return put<(WrittenId<E> | undefined)[]>(\n `${basePath}${CRUD_ROUTES.saveMany.path}`,\n payload,\n this.buildOptions(opts),\n );\n }\n\n async deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts: QueryOptions & RequestOptions = {}) {\n const basePath = this.getBasePath(entity);\n const qs = opts.hardDelete ? stringifyQuery({ hardDelete: opts.hardDelete }) : '';\n return remove<number>(`${basePath}/${idSegment(entity, id)}${qs}`, this.buildOptions(opts));\n }\n\n deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E, never>, opts: QueryOptions & RequestOptions = {}) {\n const basePath = this.getBasePath(entity);\n const qs = stringifyQuery(opts.hardDelete ? { ...q, hardDelete: opts.hardDelete } : q);\n return remove<number>(`${basePath}${qs}`, this.buildOptions(opts));\n }\n\n getBasePath<E>(entity: Type<E>) {\n return `${this.basePath}/${(this.defaults.entityPath ?? entityPath)(entity)}`;\n }\n\n protected read<T>(path: string, q: Record<string, unknown> | undefined, opts?: RequestOptions) {\n if (this.defaults.readMethod === 'QUERY') {\n return httpQuery<T>(path, q ?? {}, this.buildOptions(opts));\n }\n return get<T>(`${path}${stringifyQuery(q)}`, this.buildOptions(opts));\n }\n\n protected buildOptions(opts?: RequestOptions): RequestOptions | undefined {\n if (!this.defaults.headers && !opts?.headers) {\n return opts;\n }\n return { ...opts, headers: { ...this.defaults.headers, ...opts?.headers } };\n }\n}\n",
|
|
14
14
|
"import { HttpQuerier } from './querier/httpQuerier.js';\nimport type { ClientQuerier, ClientQuerierPool } from './type/index.js';\n\nlet defaultPool: ClientQuerierPool = {\n getQuerier: () => new HttpQuerier('/api'),\n};\n\nexport function setQuerierPool<T extends ClientQuerierPool>(pool: T) {\n defaultPool = pool;\n}\n\nexport function getQuerierPool(): ClientQuerierPool {\n return defaultPool;\n}\n\nexport function getQuerier(): ClientQuerier {\n return getQuerierPool().getQuerier();\n}\n"
|
|
15
15
|
],
|
|
16
|
-
"mappings": "AAEA,IAAM,EAAkC,CAAC,EAElC,SAAS,CAAM,CAAC,EAAyC,CAC9D,QAAW,KAAe,EACxB,EAAY,CAAY,EAIrB,SAAS,CAAE,CAAC,EAAiC,CAClD,EAAa,KAAK,CAAE,EACpB,IAAM,EAAQ,EAAa,OAAS,EACpC,MAAO,IAAY,CACjB,EAAa,OAAO,EAAO,CAAC,
|
|
16
|
+
"mappings": "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",
|
|
17
17
|
"debugId": "C0A88AD7218EC87664756E2164756E21",
|
|
18
18
|
"names": []
|
|
19
19
|
}
|
|
@@ -19,7 +19,11 @@ export class CockroachDialect extends PgLikeSqlDialect {
|
|
|
19
19
|
['vector', 'vector_search_beam_size'],
|
|
20
20
|
]);
|
|
21
21
|
/** An upsert batch mixing an update and an insert returns the update first (verified on v26.2). */
|
|
22
|
-
features = {
|
|
22
|
+
features = {
|
|
23
|
+
...PG_FEATURES,
|
|
24
|
+
orderedUpsertReturning: false,
|
|
25
|
+
triggers: { ...PG_FEATURES.triggers, guards: 'thenEndIf' },
|
|
26
|
+
};
|
|
23
27
|
/**
|
|
24
28
|
* Not Postgres' `pg_class.reltuples`, which CockroachDB answers `NULL` for even straight after an
|
|
25
29
|
* `ANALYZE` (verified live on v26.2) - it keeps its optimizer's row counts in its own statistics
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DialectFeatures, DialectName, EntityMeta, ExtraOptions, FieldOptions, InsertIdSource, NamingStrategy, Query,
|
|
1
|
+
import type { DialectFeatures, DialectName, EntityMeta, ExtraOptions, FieldOptions, InsertIdSource, NamingStrategy, Query, QueryOptions, QueryWhere, Type } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Options for initializing a dialect.
|
|
4
4
|
*/
|
|
@@ -73,36 +73,6 @@ export declare abstract class AbstractDialect {
|
|
|
73
73
|
* Recursion within one scope renders the returned map directly instead.
|
|
74
74
|
*/
|
|
75
75
|
protected scopedWhere<E>(meta: EntityMeta<E>, where?: QueryWhere<E>, opts?: QueryOptions): QueryWhere<E>;
|
|
76
|
-
/**
|
|
77
|
-
* How each grouping operator renders: the operator joining its clauses, and whether the group is
|
|
78
|
-
* negated (`$not` is `NOT (a AND b)`). Total over {@link QueryGroupOp}.
|
|
79
|
-
*/
|
|
80
|
-
protected static readonly GROUP_OPS: {
|
|
81
|
-
readonly $and: {
|
|
82
|
-
readonly join: '$and';
|
|
83
|
-
readonly negate: false;
|
|
84
|
-
};
|
|
85
|
-
readonly $or: {
|
|
86
|
-
readonly join: '$or';
|
|
87
|
-
readonly negate: false;
|
|
88
|
-
};
|
|
89
|
-
readonly $not: {
|
|
90
|
-
readonly join: '$and';
|
|
91
|
-
readonly negate: true;
|
|
92
|
-
};
|
|
93
|
-
readonly $nor: {
|
|
94
|
-
readonly join: '$or';
|
|
95
|
-
readonly negate: true;
|
|
96
|
-
};
|
|
97
|
-
};
|
|
98
|
-
/** Whether a `$where` key groups clauses, narrowing it for the renderers that read {@link GROUP_OPS}. */
|
|
99
|
-
protected static isGroupOp(key: string): key is QueryGroupOp;
|
|
100
|
-
/**
|
|
101
|
-
* A group operator's clauses, rejecting what the types do not cover: `/http` casts client JSON
|
|
102
|
-
* straight to `Query`, so a scalar can arrive where an array belongs. Shared so both backends
|
|
103
|
-
* refuse the same payload rather than one throwing and the other failing further in.
|
|
104
|
-
*/
|
|
105
|
-
protected static groupClauses<E>(key: QueryGroupOp, val: QueryWhereArray<E> | undefined): QueryWhereArray<E>;
|
|
106
76
|
/**
|
|
107
77
|
* Whether a `$where` reads a relation at any depth: filters by one, or by a relation aggregate. What
|
|
108
78
|
* cannot host that read - a MongoDB filter, a write without {@link DialectFeatures.correlatedWrites} -
|
|
@@ -5,6 +5,7 @@ import { applyFilters, assertWhere } from '../util/dialect.util.js';
|
|
|
5
5
|
import { aggregateOf, definedEntries, entityName, someKey } from '../util/index.js';
|
|
6
6
|
import { qualifyName } from '../util/sql.util.js';
|
|
7
7
|
import { UqlUsageError } from '../util/uqlError.js';
|
|
8
|
+
import { groupClauses, isGroupOp } from './operators.js';
|
|
8
9
|
/**
|
|
9
10
|
* The dialect's share of a pool's {@link ExtraOptions}: what changes the SQL rather than the
|
|
10
11
|
* connection. Every pool builds its dialect through this, so a new option lands here instead of in
|
|
@@ -109,31 +110,6 @@ export class AbstractDialect {
|
|
|
109
110
|
assertWhere(meta, where);
|
|
110
111
|
return applyFilters(meta, where, opts);
|
|
111
112
|
}
|
|
112
|
-
/**
|
|
113
|
-
* How each grouping operator renders: the operator joining its clauses, and whether the group is
|
|
114
|
-
* negated (`$not` is `NOT (a AND b)`). Total over {@link QueryGroupOp}.
|
|
115
|
-
*/
|
|
116
|
-
static GROUP_OPS = {
|
|
117
|
-
$and: { join: '$and', negate: false },
|
|
118
|
-
$or: { join: '$or', negate: false },
|
|
119
|
-
$not: { join: '$and', negate: true },
|
|
120
|
-
$nor: { join: '$or', negate: true },
|
|
121
|
-
};
|
|
122
|
-
/** Whether a `$where` key groups clauses, narrowing it for the renderers that read {@link GROUP_OPS}. */
|
|
123
|
-
static isGroupOp(key) {
|
|
124
|
-
return Object.hasOwn(AbstractDialect.GROUP_OPS, key);
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* A group operator's clauses, rejecting what the types do not cover: `/http` casts client JSON
|
|
128
|
-
* straight to `Query`, so a scalar can arrive where an array belongs. Shared so both backends
|
|
129
|
-
* refuse the same payload rather than one throwing and the other failing further in.
|
|
130
|
-
*/
|
|
131
|
-
static groupClauses(key, val) {
|
|
132
|
-
if (val !== undefined && !Array.isArray(val)) {
|
|
133
|
-
throw new UqlUsageError(`${key} expects an array, got ${val === null ? 'null' : typeof val}`);
|
|
134
|
-
}
|
|
135
|
-
return val ?? [];
|
|
136
|
-
}
|
|
137
113
|
/**
|
|
138
114
|
* Whether a `$where` reads a relation at any depth: filters by one, or by a relation aggregate. What
|
|
139
115
|
* cannot host that read - a MongoDB filter, a write without {@link DialectFeatures.correlatedWrites} -
|
|
@@ -144,8 +120,8 @@ export class AbstractDialect {
|
|
|
144
120
|
return false;
|
|
145
121
|
}
|
|
146
122
|
const meta = getMeta(entity);
|
|
147
|
-
return someKey(where, (key) =>
|
|
148
|
-
?
|
|
123
|
+
return someKey(where, (key) => isGroupOp(key)
|
|
124
|
+
? groupClauses(key, where[key]).some((it) => !(it instanceof QueryRaw) && this.constrainsRelations(entity, it))
|
|
149
125
|
: !!meta.relations[key] || aggregateOf(meta.fields[key]) !== undefined);
|
|
150
126
|
}
|
|
151
127
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ColumnFamily, type EntityData, type EntityMeta, type EntityWhereMeta, type FieldKey, type FieldOptions, type IsolationLevel, type JsonColumnType, type JsonUpdateOp, type Query, type QueryAggMap, type QueryAggregate, type QueryBuildFn, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryContextOptions, type QueryExclude, type QueryGroupMap, type QueryGroupOp, type QueryHavingMap, type
|
|
1
|
+
import { type ColumnFamily, type EntityData, type EntityMeta, type EntityWhereMeta, type FieldKey, type FieldOptions, type IsolationLevel, type JsonColumnType, type JsonUpdateOp, type Query, type QueryAggMap, type QueryAggregate, type QueryBuildFn, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryContextOptions, type QueryExclude, type QueryGroupMap, type QueryGroupOp, type QueryHavingMap, type QueryRenderOptions, type QueryPage, type QueryPager, QueryRaw, type QueryRawFnOptions, type QuerySearch, type QuerySelectValue, type QuerySizeComparisonOps, type QueryTextSearchOptions, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOp, type QueryWhereOptions, type RelationAggregateSpec, type RelationMeta, type SqlDialectName, type SqlQueryDialect, type Type, type UpdatePayload } from '../type/index.js';
|
|
2
2
|
import type { HydrateKind } from './hydrateColumn.js';
|
|
3
3
|
import { type JsonAccessMode, type JsonSlot } from './jsonSql.js';
|
|
4
4
|
import { type QueryJoins, type QuerySortOptions } from './queryJoins.js';
|
|
@@ -44,7 +44,7 @@ export type ReadProjection = {
|
|
|
44
44
|
* relation's rows read inside the parent's statement cross JSON, and where their aggregate orders them,
|
|
45
45
|
* carry their sort terms out as columns.
|
|
46
46
|
*/
|
|
47
|
-
type ReadOptions =
|
|
47
|
+
type ReadOptions = QueryRenderOptions & {
|
|
48
48
|
readonly alias?: string;
|
|
49
49
|
readonly json?: boolean;
|
|
50
50
|
readonly carried?: boolean;
|
|
@@ -85,6 +85,8 @@ export declare function relationTermKey({ sql, key }: SelectTerm): string;
|
|
|
85
85
|
export type { HydrateKind };
|
|
86
86
|
export declare abstract class AbstractSqlDialect extends VectorSqlDialect implements SqlQueryDialect {
|
|
87
87
|
abstract readonly dialectName: SqlDialectName;
|
|
88
|
+
/** Itself, unless the engine is a fork running another's SQL, which is the only case that overrides. */
|
|
89
|
+
get dialectFamily(): SqlDialectName;
|
|
88
90
|
abstract readonly escapeIdChar: '"' | '`';
|
|
89
91
|
/**
|
|
90
92
|
* The column type of a database-generated key: only the type, never the key itself, which the table
|
|
@@ -151,6 +153,8 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
151
153
|
placeholder(_index: number): string;
|
|
152
154
|
/** `RETURNING <id column> id`, or nothing on a composite key, whose every column the payload already names. */
|
|
153
155
|
returningId<E>(meta: EntityMeta<E>): string;
|
|
156
|
+
/** What the returned row is read off, for an engine that names it: SQL Server's `INSERTED.`. */
|
|
157
|
+
protected readonly returnedRowPrefix: string;
|
|
154
158
|
/** `<id column> AS id` on its own, for a statement composing a `RETURNING` list of several items. */
|
|
155
159
|
protected returningIdExpression<E>(meta: EntityMeta<E>): string;
|
|
156
160
|
search<E>(ctx: QueryContext, entity: Type<E>, q?: Query<E>, opts?: ReadOptions, joins?: QueryJoins, order?: readonly SortRef[]): void;
|
|
@@ -244,29 +248,9 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
244
248
|
/** Renders a `$where` tree without applying entity filters (used for same-scope group-operator recursion). */
|
|
245
249
|
protected renderWhere<E>(ctx: QueryContext, entity: Type<E>, where?: QueryWhere<E>, opts?: QueryWhereOptions): void;
|
|
246
250
|
compare<E>(ctx: QueryContext, entity: Type<E>, key: string, val: unknown, opts?: QueryComparisonOptions): void;
|
|
247
|
-
/** Conditions joined by `AND`, parenthesized where there is more than one. */
|
|
248
|
-
private static conjunction;
|
|
249
251
|
protected compareLogicalOperator<E>(ctx: QueryContext, entity: Type<E>, key: QueryGroupOp, val: QueryWhereArray<E>, opts: QueryComparisonOptions): void;
|
|
250
252
|
/** Memoizes {@link escapedColumnName}; see there for why it is per dialect instance. */
|
|
251
253
|
private readonly escapedColumns;
|
|
252
|
-
private static readonly COMPARE_OP_MAP;
|
|
253
|
-
/** What a `$near` says about the search itself; everything else in it is a bound. */
|
|
254
|
-
private static readonly VECTOR_QUERY_KEYS;
|
|
255
|
-
/**
|
|
256
|
-
* The ordered comparisons, `QueryOrderedOp` at runtime, derived from the map above rather than spelled
|
|
257
|
-
* again: {@link QueryVectorNear}'s bounds, so `$near` never accepts one the renderer has no operator
|
|
258
|
-
* for, and the operators that read a JSON path as a number.
|
|
259
|
-
*/
|
|
260
|
-
private static readonly ORDERED_OPS;
|
|
261
|
-
/** The operators an equality compares by value, which a JSON path reads the way that value compares. */
|
|
262
|
-
private static readonly EQUALITY_OPS;
|
|
263
|
-
/**
|
|
264
|
-
* Every `$like`-family operator: the pattern it wraps its value in, and whether it ignores case.
|
|
265
|
-
* Each case-sensitive operator is paired here with the `$i` twin that shares its pattern, so the
|
|
266
|
-
* two can never drift apart - and neither one decides case folding, which is
|
|
267
|
-
* {@link caseInsensitiveMatch}'s single call.
|
|
268
|
-
*/
|
|
269
|
-
private static readonly LIKE_OPS;
|
|
270
254
|
/**
|
|
271
255
|
* How the engine matches case-insensitively: `ilike` has the operator, `native` ignores case already
|
|
272
256
|
* (SQLite, where folding in JS would break non-ASCII), and `fold` lowers both sides.
|
|
@@ -277,7 +261,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
277
261
|
* JSON paths, and the only place a pattern is folded - always together with the column it is
|
|
278
262
|
* compared against.
|
|
279
263
|
*/
|
|
280
|
-
protected likeCondition(ctx: QueryContext, operand: string, op:
|
|
264
|
+
protected likeCondition(ctx: QueryContext, operand: string, op: QueryWhereFieldOp, val: unknown): string | undefined;
|
|
281
265
|
/** Builds `prefix.column` from an already-resolved field, through the same memo writes use. */
|
|
282
266
|
private columnWithPrefix;
|
|
283
267
|
/**
|
|
@@ -285,7 +269,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
285
269
|
* as text rather than appending it, so every operator gets a real operand to wrap - `LOWER(...)`,
|
|
286
270
|
* `NOT (... <=> ...)` - instead of having to fall back to a form that takes none.
|
|
287
271
|
*/
|
|
288
|
-
protected resolveOperandField<E>(ctx: QueryContext, entity: Type<E>, key: string, opts:
|
|
272
|
+
protected resolveOperandField<E>(ctx: QueryContext, entity: Type<E>, key: string, opts: QueryRenderOptions): string;
|
|
289
273
|
/**
|
|
290
274
|
* The expression an inlined computed field stands for, or nothing when the field is a real column.
|
|
291
275
|
*
|
|
@@ -294,15 +278,16 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
294
278
|
*/
|
|
295
279
|
private inlinedOperand;
|
|
296
280
|
/** {@link fieldCondition}, appended. */
|
|
297
|
-
compareFieldOperator<E>(ctx: QueryContext, entity: Type<E>, key: FieldKey<E>, op:
|
|
281
|
+
compareFieldOperator<E>(ctx: QueryContext, entity: Type<E>, key: FieldKey<E>, op: QueryWhereFieldOp, val: unknown, opts?: QueryComparisonOptions): void;
|
|
298
282
|
/** One operator of a field's condition. Both come from the query as data, so neither is trusted. */
|
|
299
283
|
private fieldCondition;
|
|
300
284
|
/**
|
|
301
285
|
* `<operand> <op> <value>` for every operator that needs only its left-hand SQL, shared by a column, a
|
|
302
286
|
* JSON path, a `HAVING` expression, a count and a distance; `undefined` for the rest. `bind` renders
|
|
303
|
-
* each compared value, a plain placeholder unless a JSON path reads it otherwise.
|
|
287
|
+
* each compared value, a plain placeholder unless a JSON path reads it otherwise. NULL compares as the
|
|
288
|
+
* engine compares it: `<>`, `NOT IN` and `NOT` are unknown on a NULL, which SQL drops.
|
|
304
289
|
*/
|
|
305
|
-
protected operatorCondition(ctx: QueryContext, operand: string, op:
|
|
290
|
+
protected operatorCondition(ctx: QueryContext, operand: string, op: QueryWhereFieldOp, val: unknown, bind?: (value: unknown) => string): string | undefined;
|
|
306
291
|
/** `$all`, `$size` and `$elemMatch`, which read the JSON array at `slot`; `undefined` for the rest. */
|
|
307
292
|
private jsonArrayCondition;
|
|
308
293
|
/** A path of a JSON document, read the way each operator reads it. */
|
|
@@ -310,11 +295,6 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
310
295
|
/** Every operator `target` is compared with, `AND`-joined. */
|
|
311
296
|
private jsonConditions;
|
|
312
297
|
private jsonCondition;
|
|
313
|
-
/**
|
|
314
|
-
* How `op` reads a JSON value: an ordered comparison as a number, an equality as its operand compares,
|
|
315
|
-
* and a pattern or a null check as text.
|
|
316
|
-
*/
|
|
317
|
-
private static jsonOperatorMode;
|
|
318
298
|
/** A bound operand of a JSON comparison, read the way `mode` reads the value it is compared with. */
|
|
319
299
|
private jsonOperand;
|
|
320
300
|
/** The JSON value at `slot`, as an array operator reads it. */
|
|
@@ -362,7 +342,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
362
342
|
*/
|
|
363
343
|
protected jsonScalarParam(ctx: QueryContext, value: unknown): string;
|
|
364
344
|
/** {@link resolveOperandField}, appended. */
|
|
365
|
-
getComparisonKey<E>(ctx: QueryContext, entity: Type<E>, key: FieldKey<E>, opts?:
|
|
345
|
+
getComparisonKey<E>(ctx: QueryContext, entity: Type<E>, key: FieldKey<E>, opts?: QueryRenderOptions): void;
|
|
366
346
|
/** Appends the `ORDER BY`, reporting whether there was one - which {@link pager} needs on the
|
|
367
347
|
* engines that refuse to page an unordered statement. */
|
|
368
348
|
sort<E>(ctx: QueryContext, entity: Type<E>, q: Query<E>, opts?: QuerySortOptions): boolean;
|
|
@@ -412,12 +392,12 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
412
392
|
* `COUNT(*)` over the filter, or over the rows a page settles. The clauses are read off `q` one by one:
|
|
413
393
|
* `/http` hands it over untyped, and a smuggled `$sort` changes no count.
|
|
414
394
|
*/
|
|
415
|
-
count<E>(ctx: QueryContext, entity: Type<E>, q: QueryPage<E>, opts?:
|
|
395
|
+
count<E>(ctx: QueryContext, entity: Type<E>, q: QueryPage<E>, opts?: QueryRenderOptions): void;
|
|
416
396
|
/**
|
|
417
397
|
* How many rows a `$distinct` read returns: the deduplication runs after `COUNT(*)` and a window
|
|
418
398
|
* alike, so the deduplicated set is counted as a derived table, never paged.
|
|
419
399
|
*/
|
|
420
|
-
countDistinct<E>(ctx: QueryContext, entity: Type<E>, q: Query<E>, opts?:
|
|
400
|
+
countDistinct<E>(ctx: QueryContext, entity: Type<E>, q: Query<E>, opts?: QueryRenderOptions): void;
|
|
421
401
|
/** `SELECT COUNT(*)` over the rows `rows` appends, as a derived table. */
|
|
422
402
|
private countRows;
|
|
423
403
|
/**
|
|
@@ -426,9 +406,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
426
406
|
* caller reached for this to avoid, and only say so by taking a long time.
|
|
427
407
|
*/
|
|
428
408
|
estimatedCount<E>(_ctx: QueryContext, _entity: Type<E>): void;
|
|
429
|
-
|
|
430
|
-
private static readonly AGGREGATE_FN;
|
|
431
|
-
aggregate<E, G extends QueryGroupMap<E>, A extends QueryAggMap<E>>(ctx: QueryContext, entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): void;
|
|
409
|
+
aggregate<E, G extends QueryGroupMap<E>, A extends QueryAggMap<E>>(ctx: QueryContext, entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryRenderOptions): void;
|
|
432
410
|
/**
|
|
433
411
|
* What one entry reads: a grouped field, through the join its path passes, or an aggregate's argument,
|
|
434
412
|
* narrowed by its own `$where` to `CASE WHEN … THEN … END`. Bare where it is a column or `'*'`.
|
|
@@ -445,7 +423,6 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
445
423
|
/** The SQL referencing one of an aggregate's emitted columns, rejecting any other name. */
|
|
446
424
|
private aggregateRef;
|
|
447
425
|
protected having(ctx: QueryContext, having: QueryHavingMap, emittedColumns: Record<string, string>): void;
|
|
448
|
-
private static readonly SORT_DIRECTION_MAP;
|
|
449
426
|
private resolveSortDirection;
|
|
450
427
|
/**
|
|
451
428
|
* One `ORDER BY` term. A placement the engine has no `NULLS FIRST/LAST` for becomes a term of its
|
|
@@ -460,7 +437,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
460
437
|
* LIMIT/OFFSET, so what it counts is the whole match rather than the page cut out of it.
|
|
461
438
|
*/
|
|
462
439
|
protected readonly totalOverExpr = "COUNT(*) OVER ()";
|
|
463
|
-
find<E>(ctx: QueryContext, entity: Type<E>, q?: Query<E>, opts?:
|
|
440
|
+
find<E>(ctx: QueryContext, entity: Type<E>, q?: Query<E>, opts?: QueryRenderOptions, totalAlias?: string): void;
|
|
464
441
|
/**
|
|
465
442
|
* A read's whole statement. The lock is appended here rather than in `search`, which `count`,
|
|
466
443
|
* `update` and `delete` share: it belongs to a SELECT alone, and every engine spells it last.
|
|
@@ -471,7 +448,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
471
448
|
* another table of the statement took it first.
|
|
472
449
|
*/
|
|
473
450
|
private readOptions;
|
|
474
|
-
insert<E>(ctx: QueryContext, entity: Type<E>, payload: E | E[], opts?:
|
|
451
|
+
insert<E>(ctx: QueryContext, entity: Type<E>, payload: E | E[], opts?: QueryRenderOptions): void;
|
|
475
452
|
/**
|
|
476
453
|
* What a text search matches and a fulltext index covers, which have to agree for the index to serve the
|
|
477
454
|
* search: the columns themselves, where the engine indexes them as they are (MySQL's `MATCH (a, b)`).
|
|
@@ -496,7 +473,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
496
473
|
* it does not support the `DEFAULT` keyword inside `VALUES`.
|
|
497
474
|
*/
|
|
498
475
|
protected appendDefaultInsertValue(ctx: QueryContext, _field: FieldOptions | undefined): void;
|
|
499
|
-
update<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?:
|
|
476
|
+
update<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryRenderOptions): void;
|
|
500
477
|
/**
|
|
501
478
|
* `INSERT ... ON CONFLICT ... DO UPDATE/NOTHING RETURNING`. The assignments are built before the insert
|
|
502
479
|
* fills `onInsert` columns, which must stay out of them, and their values bound after it, where a `?` reads them.
|
|
@@ -510,7 +487,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
510
487
|
protected readonly upsertExcluded: (columnName: string) => string;
|
|
511
488
|
protected getUpsertUpdateAssignments<E>(ctx: QueryContext, meta: EntityMeta<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[], callback: (columnName: string) => string): string;
|
|
512
489
|
protected getUpsertConflictPathsStr<E>(meta: EntityMeta<E>, conflictPaths: QueryConflictPaths<E>): string;
|
|
513
|
-
delete<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, opts?:
|
|
490
|
+
delete<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, opts?: QueryRenderOptions): void;
|
|
514
491
|
escapeId(val: string | undefined, forbidQualified?: boolean, addDot?: boolean): string;
|
|
515
492
|
/**
|
|
516
493
|
* A name behind its schema, each part escaped on its own rather than as one dotted string taken
|
|
@@ -605,11 +582,6 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
605
582
|
jsonPathExpr(escapedColumn: string, path: string, mode: JsonAccessMode): string;
|
|
606
583
|
/** A path of the JSON in `escapedColumn`, `''` for the document itself, as its JSON value or its text. */
|
|
607
584
|
protected abstract jsonPathReading(escapedColumn: string, path: string, mode: 'json' | 'text'): string;
|
|
608
|
-
/**
|
|
609
|
-
* Normalizes a raw WHERE value into an operator map.
|
|
610
|
-
* Arrays become `$in`, operator maps pass through, everything else becomes `$eq`.
|
|
611
|
-
*/
|
|
612
|
-
private normalizeWhereValue;
|
|
613
585
|
/**
|
|
614
586
|
* A field key's mapped column (`@Field({ name })`), escaped, memoized per dialect instance: field
|
|
615
587
|
* metadata is shared between dialects while this result is not, since `escapeIdChar` and the naming
|
|
@@ -712,10 +684,7 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
712
684
|
* counts, and `$near`, which measures a distance.
|
|
713
685
|
*/
|
|
714
686
|
private boundConditions;
|
|
715
|
-
/**
|
|
716
|
-
* A count compared with `size`, a number or its bounds. A count is never NULL, so its equality stays
|
|
717
|
-
* plain rather than the null-safe `$ne` (`IS DISTINCT FROM`, `IS NOT`): same rows, shorter SQL.
|
|
718
|
-
*/
|
|
687
|
+
/** A count compared with `size`, a number or its bounds. */
|
|
719
688
|
private sizeCondition;
|
|
720
689
|
/** `<distance> <op> ?`, the `$where` half of a vector search, its bounds checked here since `/http` input is untyped. */
|
|
721
690
|
private vectorNearCondition;
|
|
@@ -731,11 +700,11 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
731
700
|
protected regexCondition(operand: string, placeholder: string): string;
|
|
732
701
|
protected get likeFn(): string;
|
|
733
702
|
/**
|
|
734
|
-
*
|
|
735
|
-
*
|
|
703
|
+
* Two fragments compared null-safely: true where they differ, and where one side alone is NULL. What a
|
|
704
|
+
* trigger compares a column's two rows with, where the portable `<>` would miss a column set to or
|
|
705
|
+
* from NULL. Abstract, so no engine inherits that miss.
|
|
736
706
|
*/
|
|
737
|
-
|
|
738
|
-
protected neExpr(field: string, ph: string): string;
|
|
707
|
+
abstract neExpr(field: string, ph: string): string;
|
|
739
708
|
/** `operand IN (...)` of each value as `bind` renders it, or the constant an empty set reduces to: no value is in it. */
|
|
740
709
|
protected formatIn(_ctx: QueryContext, operand: string, values: unknown[], negate: boolean, bind: (value: unknown) => string): string;
|
|
741
710
|
/** Reads extracted JSON text as a number, which every engine spells its own way. */
|