uql-orm 0.88.0 → 0.90.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 +2 -2
- package/dist/browser/uql-browser.min.js.map +2 -2
- package/dist/cockroachdb/cockroachDialect.js +1 -1
- package/dist/dialect/abstractSqlDialect.d.ts +3 -3
- package/dist/dialect/abstractSqlDialect.js +8 -4
- package/dist/dialect/mysqlLikeSqlDialect.js +1 -2
- package/dist/dialect/pgLikeSqlDialect.js +1 -2
- package/dist/migrate/acquireQuerierForMigrations.d.ts +3 -3
- package/dist/migrate/acquireQuerierForMigrations.js +3 -6
- package/dist/migrate/codegen/migrationFile.d.ts +4 -0
- package/dist/migrate/codegen/migrationFile.js +4 -2
- package/dist/migrate/index.d.ts +1 -2
- package/dist/migrate/index.js +0 -2
- package/dist/migrate/introspection/index.d.ts +0 -1
- package/dist/migrate/introspection/index.js +0 -1
- package/dist/migrate/introspection/registry.js +1 -1
- package/dist/migrate/migrationTarget.js +6 -5
- package/dist/migrate/triggerSql.js +39 -50
- package/dist/mongo/index.d.ts +4 -1
- package/dist/mongo/index.js +4 -1
- package/dist/{migrate/generator → mongo}/mongoCommand.js +1 -1
- package/dist/{migrate/introspection → mongo}/mongoIntrospector.d.ts +3 -3
- package/dist/{migrate/introspection → mongo}/mongoIntrospector.js +4 -4
- package/dist/{migrate/storage/mongoStorage.d.ts → mongo/mongoMigrationStorage.d.ts} +2 -1
- package/dist/{migrate/storage/mongoStorage.js → mongo/mongoMigrationStorage.js} +2 -2
- package/dist/mongo/mongoQuerier.d.ts +18 -0
- package/dist/mongo/mongoQuerier.js +13 -0
- package/dist/{migrate/generator → mongo}/mongoSchemaGenerator.d.ts +4 -4
- package/dist/{migrate/generator → mongo}/mongoSchemaGenerator.js +14 -14
- package/dist/mongo/mongodbQuerier.d.ts +2 -1
- package/dist/mssql/mssqlDialect.js +1 -2
- package/dist/sqlite/sqliteDialect.js +1 -2
- package/dist/type/dialect.d.ts +15 -11
- package/dist/type/migratorDialect.d.ts +8 -3
- package/dist/type/querier.d.ts +0 -15
- package/dist/type/querier.js +0 -7
- package/dist/type/queryRaw.d.ts +11 -4
- package/dist/type/queryRaw.js +7 -0
- package/dist/type/queryWhere.d.ts +16 -6
- package/dist/util/index.d.ts +1 -1
- package/dist/util/index.js +1 -1
- package/dist/util/raw.js +4 -2
- package/dist/util/triggerWrite.d.ts +3 -1
- package/dist/util/triggerWrite.js +4 -3
- package/package.json +2 -2
- package/skills/uql-orm/SKILL.md +1 -1
- /package/dist/{migrate/generator → mongo}/mongoCommand.d.ts +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var c=[];function d(e){for(let r of c)r(e)}function
|
|
1
|
+
var c=[];function d(e){for(let r of c)r(e)}function v(e){c.push(e);let r=c.length-1;return()=>{c.splice(r,1)}}var O=["$select","$populate","$exclude","$where","$sort"],f=["$count"],b=["$skip","$limit"],R=["$candidates"],T=["$distinct"],L=["$lock",...f,...R];var C=Symbol("rawValue"),N=Symbol("rawAlias"),D=Symbol("rawText");class k extends Error{}class i extends k{name="UqlUsageError";kind="usage";status=400}function l(e){return e?Object.keys(e):[]}function K(e){if(typeof e!=="object"||e===null)return!0;if(Array.isArray(e))return!1;let r=Object.getPrototypeOf(e);return r!==Object.prototype&&r!==null}var _=["hardDelete","count"],te=new Set([...O,...f,...b,...R,...T,..._]);function u(e){if(!e)return"";let r=new URLSearchParams;for(let n of l(e)){let o=e[n];if(o===void 0)continue;r.append(n,typeof o==="object"&&o!==null?a(o):String(o))}let t=r.toString();return t?`?${t}`:""}function a(e){return JSON.stringify(e,(r,t)=>{if(typeof t!=="object"||t===null)return t;if(C in t)throw new i("raw SQL cannot travel over HTTP: what leaves the browser is JSON");if(t instanceof ArrayBuffer||ArrayBuffer.isView(t))throw new i("binary cannot travel over HTTP: what leaves the browser is JSON");return t})}class U extends Error{status;constructor(e,r){super(e);this.status=r;this.name="RequestError"}}function x(e,r){return y(e,{method:"get"},r)}function h(e,r,t){return y(e,{method:"post",body:a(r)},t)}function Q(e,r,t){return y(e,{method:"patch",body:a(r)},t)}function g(e,r,t){return y(e,{method:"put",body:a(r)},t)}function m(e,r){return y(e,{method:"delete"},r)}function q(e,r,t){return y(e,{method:"QUERY",body:a(r)},t)}function y(e,r,t){if(d({phase:"start",opts:t}),r.headers={accept:"application/json","content-type":"application/json",...t?.headers},t?.signal)r.signal=t.signal;return fetch(e,r).then((n)=>n.json().then((o)=>{if(n.status>=200&&n.status<300)return d({phase:"success",opts:t}),o;let P=o,E={message:P?.error?.message??n.statusText,code:P?.error?.code??n.status};throw d({phase:"error",error:E,opts:t}),new U(E.message,E.code)})).finally(()=>{d({phase:"complete",opts:t})})}function F(e){let r=e.charAt(0).toLowerCase();for(let t=1;t<e.length;++t)r+=e[t]===e[t].toUpperCase()?"-"+e[t].toLowerCase():e[t];return r}var s={findMany:{method:"GET",path:""},findOne:{method:"GET",path:"/one"},count:{method:"GET",path:"/count"},findOneById:{method:"GET",path:"/:id"},insertOne:{method:"POST",path:""},insertMany:{method:"POST",path:"/many"},saveOne:{method:"PUT",path:""},saveMany:{method:"PUT",path:"/many"},updateMany:{method:"PATCH",path:""},updateOneById:{method:"PATCH",path:"/:id"},deleteOneById:{method:"DELETE",path:"/:id"},deleteMany:{method:"DELETE",path:""}},A=l(s),ye=new Map(A.filter((e)=>s[e].method==="GET"&&s[e].path!=="/:id").map((e)=>[s[e].path,e]));function V(e){return F(e.name)}function w(e,r){if(!K(r))throw new i(`'${e.name}' was addressed by an id object, which the HTTP route cannot carry.`);return String(r)}class S{basePath;defaults;constructor(e,r={}){this.basePath=e;this.defaults=r}async findOneById(e,r,t,n){let o=this.getBasePath(e),p=u(t);return x(`${o}/${w(e,r)}${p}`,this.buildOptions(n))}findOne(e,r,t){return this.read(`${this.getBasePath(e)}${s.findOne.path}`,r,t)}findMany(e,r,t){let n={...r};if(t?.count)n.count=!0;return this.read(this.getBasePath(e),n,t)}async findManyAndCount(e,r,t){let n=await this.findMany(e,r,{...t,count:!0});if(typeof n.count!=="number")throw TypeError("findManyAndCount response has an invalid count");return{...n,count:n.count}}count(e,r,t){return this.read(`${this.getBasePath(e)}${s.count.path}`,r,t)}async exists(e,r,t){let n=await this.count(e,{...r,$limit:1},t);return{...n,data:n.data>0}}insertOne(e,r,t){let n=this.getBasePath(e);return h(n,r,this.buildOptions(t))}insertMany(e,r,t){let n=this.getBasePath(e);return h(`${n}${s.insertMany.path}`,r,this.buildOptions(t))}async updateOneById(e,r,t,n){let o=this.getBasePath(e);return Q(`${o}/${w(e,r)}`,t,this.buildOptions(n))}updateMany(e,r,t,n){let o=this.getBasePath(e),p=u(r);return Q(`${o}${p}`,t,this.buildOptions(n))}saveOne(e,r,t){let n=this.getBasePath(e);return g(n,r,this.buildOptions(t))}saveMany(e,r,t){let n=this.getBasePath(e);return g(`${n}${s.saveMany.path}`,r,this.buildOptions(t))}async deleteOneById(e,r,t={}){let n=this.getBasePath(e),o=t.hardDelete?u({hardDelete:t.hardDelete}):"";return m(`${n}/${w(e,r)}${o}`,this.buildOptions(t))}deleteMany(e,r,t={}){let n=this.getBasePath(e),o=u(t.hardDelete?{...r,hardDelete:t.hardDelete}:r);return m(`${n}${o}`,this.buildOptions(t))}getBasePath(e){return`${this.basePath}/${(this.defaults.entityPath??V)(e)}`}read(e,r,t){if(this.defaults.readMethod==="QUERY")return q(e,r??{},this.buildOptions(t));return x(`${e}${u(r)}`,this.buildOptions(t))}buildOptions(e){if(!this.defaults.headers&&!e?.headers)return e;return{...e,headers:{...this.defaults.headers,...e?.headers}}}}var j={getQuerier:()=>new S("/api")};function he(e){j=e}function W(){return j}function Qe(){return W().getQuerier()}export{S as HttpQuerier,U as RequestError,x as get,Qe as getQuerier,W as getQuerierPool,d as notify,v as on,Q as patch,h as post,g as put,q as query,m as remove,he as setQuerierPool};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=345B48498EF4662664756E2164756E21
|
|
4
4
|
//# sourceMappingURL=uql-browser.min.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
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
6
|
"import type {\n FieldKey,\n FieldKeyOf,\n JsonFieldPaths,\n RelationKey,\n RelationTarget,\n ToManyRelationKey,\n WrittenId,\n} 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/**\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> = [FieldKeyOf<E, readonly number[]>] extends [never]\n ? never\n : { [P in FieldKeyOf<E, readonly number[]>]?: 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/** What upserting one row reports. `created` is only knowable for a single statement, so a batch has none. */\nexport type QueryUpsertOneResult<E> = {\n readonly id?: WrittenId<E>;\n readonly changes?: number;\n /** Whether the record was created (`true`) or updated (`false`), where the dialect can tell. */\n readonly created?: boolean;\n};\n\n/**\n * What upserting many rows reports. `ids` is payload-aligned like an insert's, so it zips with the\n * rows that were passed, and carries a composite key as the map naming it.\n */\nexport type QueryUpsertManyResult<E> = {\n readonly ids: (WrittenId<E> | undefined)[];\n readonly changes?: number;\n};\n\n/**\n * result of an update operation, as the driver reports it - which is what `run` hands back, where\n * there is no entity to name the ids against. The `QueryUpsert*Result` pair is the entity-level shape.\n */\nexport type QueryUpdateResult = {\n /**\n * number of affected records.\n */\n changes?: number;\n /**\n * the IDs the statement reported, in payload order, `undefined` where it reported none for that\n * row - a MongoDB upsert names only the documents it inserted. Exact on `'returning'` dialects;\n * inferred from the driver header on the others (see {@link InsertIdSource}), and absent\n * altogether when the header reports nothing.\n */\n ids?: (PrimaryKey | undefined)[];\n /**\n * first inserted ID.\n */\n firstId?: PrimaryKey;\n /**\n * whether the record was created (`true`) or updated (`false`).\n * `undefined` when the dialect cannot determine this (e.g. SQLite).\n */\n created?: boolean;\n};\n",
|
|
7
|
-
"import type { QueryContext, 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 * The
|
|
7
|
+
"import type { QueryContext, RelationAggregateSpec, SqlQueryDialect, TriggerRows } 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 * The rows a set-based trigger's writes read. Absent where the body reads `NEW` and `OLD` bare, and\n * outside a trigger.\n */\n rows?: TriggerRows;\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, and `V`,\n * the field's type, is what a value slot checks it against: see {@link RawFor}.\n */\nexport class ColumnRef<K extends string = string, V = unknown> extends QueryRaw {\n declare readonly __value?: V;\n\n constructor(\n readonly key: K,\n value: QueryRawFn,\n ) {\n super(value);\n }\n}\n\n/**\n * SQL where a value of type `V` goes: bare SQL, whose type is its author's to know, or a ref to a column\n * holding one, nullability aside. `Raw` is what the transport carries, so the wire's `never` stays one.\n */\nexport type RawFor<Raw, V> = Raw & { readonly __value?: V | null };\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 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\n/**\n * A write `insertInto`, `updateTable` or `deleteFrom` renders, or several joined in one `raw`. It reads a\n * set-based trigger's rows through {@link QueryRawRenderOptions.rows}, which is how `of` and `where` narrow\n * them there; SQL of its own reads `inserted` and `deleted` whole.\n */\nexport class TriggerWriteRaw extends QueryRaw {}\n",
|
|
8
8
|
"/**\n * What a failed query ran into, named the same on every engine - what {@link queryErrorKind} answers\n * with, whether a driver raised the error or UQL did. `retryable` is a deadlock, a serialization\n * failure, a lock timeout or a busy database: the transaction can simply run again. `usage` is the\n * caller's own mistake, which running it again will not fix. `security` is a `security` filter refusing.\n */\nexport type QueryErrorKind =\n | 'uniqueViolation'\n | 'foreignKeyViolation'\n | 'notNullViolation'\n | 'checkViolation'\n | 'optimisticLock'\n | 'retryable'\n | 'usage'\n | 'security';\n\n/** Every error UQL raises of its own: the kind `queryErrorKind` answers, and the status HTTP answers with. */\nexport abstract class UqlError extends Error {\n abstract readonly kind: QueryErrorKind;\n abstract readonly status: number;\n}\n\n/**\n * Thrown where the caller used the API in a way no statement can carry out: an update payload with no\n * version, a `$lock` outside a transaction, a method with no version to match. A `400` over HTTP: the\n * request is malformed, not the server's failure, and an untyped client is exactly who reaches this.\n */\nexport class UqlUsageError extends UqlError {\n override name = 'UqlUsageError';\n readonly kind = 'usage';\n readonly status = 400;\n}\n\n/**\n * Thrown where a `security` filter refuses: its context is missing, or a write would leave a row outside it.\n * Fails the statement closed.\n */\nexport class UqlSecurityError extends UqlError {\n override name = 'UqlSecurityError';\n readonly kind = 'security';\n readonly status = 403;\n}\n\n/** What a value is, for a refusal naming what `/http` handed over instead of what the types require. */\nexport function kindOf(value: unknown): string {\n return value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value;\n}\n\n/**\n * @deprecated since 0.77.1 - use {@link UqlUsageError}, which every misuse throws, lock or not. The\n * same class under both names, so an existing `instanceof` keeps working.\n */\nexport const UqlLockUsageError = UqlUsageError;\nexport type UqlLockUsageError = UqlUsageError;\n\n/**\n * Thrown when an update's `@Field({ version })` no longer matches the row: another writer moved it on,\n * or it is gone. `expected` is what the payload carried, `actual` what the row holds now, `undefined`\n * where there is no row left.\n */\nexport class UqlOptimisticLockError extends UqlError {\n override name = 'UqlOptimisticLockError';\n readonly kind = 'optimisticLock';\n readonly status = 409;\n\n constructor(\n message: string,\n readonly expected: unknown,\n readonly actual: unknown,\n ) {\n super(message);\n }\n}\n",
|
|
9
9
|
"import type { EntityMeta } from '../type/index.js';\nimport { UqlUsageError } from './uqlError.js';\n\nexport function throwPendingTransaction(): never {\n throw new UqlUsageError('pending transaction');\n}\n\nexport function throwNoPendingTransaction(): never {\n throw new UqlUsageError('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",
|
|
10
10
|
"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, isRecord, 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/** The flags a request carries beside its query: `hardDelete` on a delete, `count` on a `findMany`. */\nconst WIRE_FLAGS = ['hardDelete', 'count'] as const satisfies (keyof Pick<QueryOptions, 'hardDelete'> | 'count')[];\n\n/** {@link WIRE_FLAGS} as the booleans {@link parseQueryParams} decodes them to, where a hook may also set them. */\nexport type WireFlags = { readonly [K in (typeof WIRE_FLAGS)[number]]?: boolean };\n\n/**\n * Keys accepted from the wire - query structure ({@link Query}) plus the {@link WIRE_FLAGS}. Anything else\n * (e.g. `filters`, `context`, `$entity`) is dropped so a remote client can't bypass a security filter or\n * 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 ...WIRE_FLAGS,\n] satisfies (keyof WireQuery<unknown> | (typeof WIRE_FLAGS)[number])[]);\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), or a `QUERY` body, into a UQL query\n * object. Symmetric counterpart of {@link stringifyQuery}. Only {@link ALLOWED_QUERY_KEYS} are honored.\n */\nexport function parseQueryParams<E = unknown>(params: unknown = {}): WireQuery<E> & WireFlags {\n if (!isRecord(params)) {\n throw new UqlUsageError('the query must be a JSON object');\n }\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 new UqlUsageError(`invalid JSON in '${key}'`);\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, ...WIRE_FLAGS]) {\n if (query[key] !== undefined) {\n query[key] = query[key] === true || query[key] === 'true';\n }\n }\n\n return query;\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 UqlUsageError('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 UqlUsageError('binary cannot travel over HTTP: what leaves the browser is JSON');\n }\n return held;\n });\n}\n",
|
|
@@ -15,6 +15,6 @@
|
|
|
15
15
|
"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"
|
|
16
16
|
],
|
|
17
17
|
"mappings": "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",
|
|
18
|
-
"debugId": "
|
|
18
|
+
"debugId": "345B48498EF4662664756E2164756E21",
|
|
19
19
|
"names": []
|
|
20
20
|
}
|
|
@@ -22,7 +22,7 @@ export class CockroachDialect extends PgLikeSqlDialect {
|
|
|
22
22
|
features = {
|
|
23
23
|
...PG_FEATURES,
|
|
24
24
|
orderedUpsertReturning: false,
|
|
25
|
-
triggers: { ...PG_FEATURES.triggers,
|
|
25
|
+
triggers: { ...PG_FEATURES.triggers, fires: 'eachRowIf' },
|
|
26
26
|
};
|
|
27
27
|
/**
|
|
28
28
|
* Not Postgres' `pg_class.reltuples`, which CockroachDB answers `NULL` for even straight after an
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ColumnFamily, type DdlRenderOptions, 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 TriggerWrite, type Type, type UpdatePayload } from '../type/index.js';
|
|
1
|
+
import { type ColumnFamily, type DdlRenderOptions, 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 TriggerRows, type TriggerWrite, 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';
|
|
@@ -479,9 +479,9 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
479
479
|
/**
|
|
480
480
|
* A write in a trigger's body. Only the columns it names, none filled in JavaScript - that would bake
|
|
481
481
|
* one value into the trigger - no id read back, and no entity filter, which a request resolves and a
|
|
482
|
-
* trigger has none of. `rows`
|
|
482
|
+
* trigger has none of. `rows` are what a set-based engine's body reads, narrowed to the ones it fires for.
|
|
483
483
|
*/
|
|
484
|
-
triggerWrite(ctx: QueryContext, write: TriggerWrite, rows?:
|
|
484
|
+
triggerWrite(ctx: QueryContext, write: TriggerWrite, rows?: TriggerRows): void;
|
|
485
485
|
/** The values a write names, refusing one the entity has no column for. */
|
|
486
486
|
private writtenEntries;
|
|
487
487
|
/**
|
|
@@ -1298,7 +1298,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
1298
1298
|
/**
|
|
1299
1299
|
* A write in a trigger's body. Only the columns it names, none filled in JavaScript - that would bake
|
|
1300
1300
|
* one value into the trigger - no id read back, and no entity filter, which a request resolves and a
|
|
1301
|
-
* trigger has none of. `rows`
|
|
1301
|
+
* trigger has none of. `rows` are what a set-based engine's body reads, narrowed to the ones it fires for.
|
|
1302
1302
|
*/
|
|
1303
1303
|
triggerWrite(ctx, write, rows) {
|
|
1304
1304
|
const meta = getMeta(write.entity);
|
|
@@ -1311,7 +1311,9 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
1311
1311
|
}
|
|
1312
1312
|
const entries = this.writtenEntries(meta, write.row);
|
|
1313
1313
|
const columns = entries.map(([key]) => this.escapedColumnName(meta, key)).join(', ');
|
|
1314
|
-
const [open, close] = rows
|
|
1314
|
+
const [open, close] = rows
|
|
1315
|
+
? ['SELECT ', ` FROM ${rows.from}${rows.where ? ` WHERE ${rows.where}` : ''};`]
|
|
1316
|
+
: ['VALUES (', ');'];
|
|
1315
1317
|
ctx.append(`INSERT INTO ${table} (${columns}) ${open}`);
|
|
1316
1318
|
entries.forEach(([key, value], i) => {
|
|
1317
1319
|
if (i > 0) {
|
|
@@ -1345,10 +1347,12 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
1345
1347
|
ctx.append(`DELETE FROM ${table}`);
|
|
1346
1348
|
}
|
|
1347
1349
|
if (rows) {
|
|
1348
|
-
ctx.append(` ${rows}`);
|
|
1350
|
+
ctx.append(` FROM ${rows.from}`);
|
|
1349
1351
|
}
|
|
1352
|
+
const narrowed = rows?.where;
|
|
1353
|
+
const where = narrowed ? { $and: [write.where, raw(() => narrowed)] } : write.where;
|
|
1350
1354
|
// Qualified by the table: on a set-based engine `inserted` holds the same column names.
|
|
1351
|
-
this.renderWhere(ctx, write.entity,
|
|
1355
|
+
this.renderWhere(ctx, write.entity, where, { escapedPrefix: `${table}.` });
|
|
1352
1356
|
ctx.append(';');
|
|
1353
1357
|
}
|
|
1354
1358
|
/** The values a write names, refusing one the entity has no column for. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Querier, type QuerierPool, type SqlQuerier } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Querier used for schema migrations and the migration journal.
|
|
4
4
|
*
|
|
@@ -15,5 +15,5 @@ export declare function acquireQuerierForMigrations(pool: QuerierPool): Promise<
|
|
|
15
15
|
export declare function withQuerierForMigrations<T>(pool: QuerierPool, task: (querier: Querier) => Promise<T>): Promise<T>;
|
|
16
16
|
/** Same, for the paths that only work against SQL. `requiredBy` names the caller in the error. */
|
|
17
17
|
export declare function withSqlQuerierForMigrations<T>(pool: QuerierPool, requiredBy: string, task: (querier: SqlQuerier) => Promise<T>): Promise<T>;
|
|
18
|
-
/**
|
|
19
|
-
export declare function
|
|
18
|
+
/** Runs `task` on a migration querier `isKind` accepts, refusing any other with `error`. */
|
|
19
|
+
export declare function withQuerierOfKind<Q extends Querier, T>(pool: QuerierPool, isKind: (querier: Querier) => querier is Q, error: string, task: (querier: Q) => Promise<T>): Promise<T>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isSqlQuerier } from '../type/index.js';
|
|
2
2
|
import { UqlUsageError } from '../util/uqlError.js';
|
|
3
3
|
/**
|
|
4
4
|
* Querier used for schema migrations and the migration journal.
|
|
@@ -28,11 +28,8 @@ export async function withQuerierForMigrations(pool, task) {
|
|
|
28
28
|
export function withSqlQuerierForMigrations(pool, requiredBy, task) {
|
|
29
29
|
return withQuerierOfKind(pool, isSqlQuerier, `${requiredBy} requires a SQL-based querier`, task);
|
|
30
30
|
}
|
|
31
|
-
/**
|
|
32
|
-
export function
|
|
33
|
-
return withQuerierOfKind(pool, isMongoQuerier, `${requiredBy} requires a MongoDB querier`, task);
|
|
34
|
-
}
|
|
35
|
-
function withQuerierOfKind(pool, isKind, error, task) {
|
|
31
|
+
/** Runs `task` on a migration querier `isKind` accepts, refusing any other with `error`. */
|
|
32
|
+
export function withQuerierOfKind(pool, isKind, error, task) {
|
|
36
33
|
return withQuerierForMigrations(pool, (querier) => {
|
|
37
34
|
if (!isKind(querier)) {
|
|
38
35
|
throw new UqlUsageError(error);
|
|
@@ -31,6 +31,8 @@ export declare const EMPTY_MANUAL_MIGRATION_DOWN_INNER = " // Add your rollba
|
|
|
31
31
|
/** How a migration on one querier is scaffolded empty, and how a generated statement is spelled in it. */
|
|
32
32
|
export type MigrationSource = {
|
|
33
33
|
readonly querier: MigrationQuerierType;
|
|
34
|
+
/** The entry exporting that querier's type: a MongoDB one names the driver's `Db`, so only `uql-orm/mongo` has it. */
|
|
35
|
+
readonly module: string;
|
|
34
36
|
readonly emptyUp: string;
|
|
35
37
|
readonly emptyDown: string;
|
|
36
38
|
emit(statements: string[]): string;
|
|
@@ -38,12 +40,14 @@ export type MigrationSource = {
|
|
|
38
40
|
export declare const migrationSource: {
|
|
39
41
|
SqlQuerier: {
|
|
40
42
|
querier: "SqlQuerier";
|
|
43
|
+
module: string;
|
|
41
44
|
emptyUp: string;
|
|
42
45
|
emptyDown: string;
|
|
43
46
|
emit: typeof emitSqlRunCalls;
|
|
44
47
|
};
|
|
45
48
|
MongoQuerier: {
|
|
46
49
|
querier: "MongoQuerier";
|
|
50
|
+
module: string;
|
|
47
51
|
emptyUp: string;
|
|
48
52
|
emptyDown: string;
|
|
49
53
|
emit: typeof emitMongoCommandCalls;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mongoCommandSource } from '
|
|
1
|
+
import { mongoCommandSource } from '../../mongo/mongoCommand.js';
|
|
2
2
|
/**
|
|
3
3
|
* Emit one `await querier.run(...)` line for entity-generated migrations.
|
|
4
4
|
* Uses `JSON.stringify` so SQL with backticks (SQLite/LibSQL), quotes, `${`, etc. stays valid TS source.
|
|
@@ -27,12 +27,14 @@ export const EMPTY_MANUAL_MIGRATION_DOWN_INNER = ` // Add your rollback logic
|
|
|
27
27
|
export const migrationSource = {
|
|
28
28
|
SqlQuerier: {
|
|
29
29
|
querier: 'SqlQuerier',
|
|
30
|
+
module: 'uql-orm/migrate',
|
|
30
31
|
emptyUp: EMPTY_MANUAL_MIGRATION_UP_INNER,
|
|
31
32
|
emptyDown: EMPTY_MANUAL_MIGRATION_DOWN_INNER,
|
|
32
33
|
emit: emitSqlRunCalls,
|
|
33
34
|
},
|
|
34
35
|
MongoQuerier: {
|
|
35
36
|
querier: 'MongoQuerier',
|
|
37
|
+
module: 'uql-orm/mongo',
|
|
36
38
|
emptyUp: ` // Add your migration logic here, through the database handle.
|
|
37
39
|
// await querier.db.collection('users').updateMany({}, { $set: { active: true } });
|
|
38
40
|
`,
|
|
@@ -49,7 +51,7 @@ export function buildMigrationModule(options) {
|
|
|
49
51
|
const querier = options.querier ?? 'SqlQuerier';
|
|
50
52
|
const iso = options.createdAt.toISOString();
|
|
51
53
|
const extra = options.docExtraLines?.map((line) => `\n * ${line}`).join('') ?? '';
|
|
52
|
-
return /*ts*/ `import type { ${querier} } from '
|
|
54
|
+
return /*ts*/ `import type { ${querier} } from '${migrationSource[querier].module}';
|
|
53
55
|
|
|
54
56
|
/**
|
|
55
57
|
* Migration: ${options.migrationName}
|
package/dist/migrate/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { Change, ColumnSchema, DialectName, ForeignKeySchema, IndexSchema, Migration, MigrationDefinition, MigrationResult, MigrationStorage, MigratorOptions,
|
|
1
|
+
export type { Change, ColumnSchema, DialectName, ForeignKeySchema, IndexSchema, Migration, MigrationDefinition, MigrationResult, MigrationStorage, MigratorOptions, PrimaryKeySchema, SchemaDiff, SchemaGenerator, SchemaIntrospector, SqlDialectName, SqlQuerier, SqlQueryDialect, SyncOptions, TableSchema, } from '../type/index.js';
|
|
2
2
|
export { type Config, isSqlQuerier } from '../type/index.js';
|
|
3
3
|
export { acquireQuerierForMigrations } from './acquireQuerierForMigrations.js';
|
|
4
4
|
export { assertCliConfig } from './assertCliConfig.js';
|
|
@@ -14,4 +14,3 @@ export { reverseDiff } from './schemaChange.js';
|
|
|
14
14
|
export { SqlSchemaGenerator } from './schemaGenerator.js';
|
|
15
15
|
export { DatabaseMigrationStorage } from './storage/databaseStorage.js';
|
|
16
16
|
export { JsonMigrationStorage } from './storage/jsonStorage.js';
|
|
17
|
-
export { MongoMigrationStorage } from './storage/mongoStorage.js';
|
package/dist/migrate/index.js
CHANGED
|
@@ -21,5 +21,3 @@ export { SqlSchemaGenerator } from './schemaGenerator.js';
|
|
|
21
21
|
// Storage implementations
|
|
22
22
|
export { DatabaseMigrationStorage } from './storage/databaseStorage.js';
|
|
23
23
|
export { JsonMigrationStorage } from './storage/jsonStorage.js';
|
|
24
|
-
export { MongoMigrationStorage } from './storage/mongoStorage.js';
|
|
25
|
-
// Schema sync
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export * from './abstractSqlSchemaIntrospector.js';
|
|
2
|
-
export * from './mongoIntrospector.js';
|
|
3
2
|
export * from './mssqlIntrospector.js';
|
|
4
3
|
export { MariadbSchemaIntrospector, MysqlSchemaIntrospector } from './mysqlIntrospector.js';
|
|
5
4
|
export * from './postgresIntrospector.js';
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export * from './abstractSqlSchemaIntrospector.js';
|
|
2
|
-
export * from './mongoIntrospector.js';
|
|
3
2
|
export * from './mssqlIntrospector.js';
|
|
4
3
|
export { MariadbSchemaIntrospector, MysqlSchemaIntrospector } from './mysqlIntrospector.js';
|
|
5
4
|
export * from './postgresIntrospector.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MongoSchemaIntrospector } from '
|
|
1
|
+
import { MongoSchemaIntrospector } from '../../mongo/mongoIntrospector.js';
|
|
2
2
|
import { MsSqlSchemaIntrospector } from './mssqlIntrospector.js';
|
|
3
3
|
import { MariadbSchemaIntrospector, MysqlSchemaIntrospector } from './mysqlIntrospector.js';
|
|
4
4
|
import { CockroachSchemaIntrospector, PostgresSchemaIntrospector } from './postgresIntrospector.js';
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runMongoCommand } from '../mongo/mongoCommand.js';
|
|
2
|
+
import { MongoMigrationStorage } from '../mongo/mongoMigrationStorage.js';
|
|
3
|
+
import { isMongoQuerier, withMongoQuerierForMigrations } from '../mongo/mongoQuerier.js';
|
|
4
|
+
import { isSqlQuerier, } from '../type/index.js';
|
|
2
5
|
import { UqlUsageError } from '../util/uqlError.js';
|
|
3
|
-
import {
|
|
6
|
+
import { withSqlQuerierForMigrations } from './acquireQuerierForMigrations.js';
|
|
4
7
|
import { MigrationBuilder } from './builder/migrationBuilder.js';
|
|
5
8
|
import { migrationSource } from './codegen/migrationFile.js';
|
|
6
|
-
import { runMongoCommand } from './generator/mongoCommand.js';
|
|
7
9
|
import { SqlSchemaGenerator } from './schemaGenerator.js';
|
|
8
10
|
import { DatabaseMigrationStorage } from './storage/databaseStorage.js';
|
|
9
|
-
import { MongoMigrationStorage } from './storage/mongoStorage.js';
|
|
10
11
|
const sqlSession = (querier) => ({
|
|
11
12
|
querier,
|
|
12
13
|
run: (statement) => querier.run(statement),
|
|
@@ -45,7 +46,7 @@ const mongoSession = (querier) => ({
|
|
|
45
46
|
});
|
|
46
47
|
/** Imported on use, so the optional `mongodb` peer loads only on MongoDB. */
|
|
47
48
|
async function mongoSchemaGenerator(namingStrategy, defaultForeignKeyAction) {
|
|
48
|
-
const { MongoSchemaGenerator } = await import('
|
|
49
|
+
const { MongoSchemaGenerator } = await import('../mongo/mongoSchemaGenerator.js');
|
|
49
50
|
return new MongoSchemaGenerator(namingStrategy, defaultForeignKeyAction);
|
|
50
51
|
}
|
|
51
52
|
export function migrationTargetFor(pool, defaultForeignKeyAction) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { TriggerWriteRaw } from '../type/index.js';
|
|
1
2
|
import { stampEvents } from '../util/field.util.js';
|
|
2
3
|
import { definedEntries } from '../util/object.util.js';
|
|
3
4
|
import { raw, refs, rowRefs } from '../util/raw.js';
|
|
4
5
|
import { ownedName } from '../util/sql.util.js';
|
|
6
|
+
import { written } from '../util/triggerWrite.js';
|
|
5
7
|
import { UqlUsageError } from '../util/uqlError.js';
|
|
6
8
|
/**
|
|
7
9
|
* One trigger for `dialect`, named for its table and label and ending in a hash of its own SQL. That hash
|
|
@@ -48,32 +50,35 @@ function triggerStatements(dialect, meta, trigger, name) {
|
|
|
48
50
|
const features = dialect.features.triggers;
|
|
49
51
|
const [timing, operation] = EVENT_PARTS[trigger.on];
|
|
50
52
|
const before = timing === 'BEFORE';
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const table = dialect.escapedTableName(meta);
|
|
53
|
+
const { fires } = features;
|
|
54
|
+
const perStatement = fires === 'eachStatement';
|
|
54
55
|
const names = rowNames(dialect);
|
|
55
56
|
const rows = [rowRefs(meta.entity, names.$new), rowRefs(meta.entity, names.$old)];
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
57
|
+
const filter = triggerFilter(dialect, meta, trigger, rows, names);
|
|
58
|
+
const body = triggerBody(dialect, meta, trigger, before)(...rows);
|
|
59
|
+
if (perStatement && filter && !(body instanceof TriggerWriteRaw)) {
|
|
60
|
+
throw new UqlUsageError(`${dialect.dialectName} fires a trigger once per statement, so 'of' and 'where' narrow only what ` +
|
|
61
|
+
`insertInto, updateTable or deleteFrom reads, and '${meta.entity.name}' has one running its own SQL. ` +
|
|
62
|
+
'Keep the body to those writes, or filter inserted and deleted in that SQL.');
|
|
63
|
+
}
|
|
64
|
+
const sql = dialect.compileDdl(body, meta.entity, {
|
|
65
|
+
rows: perStatement ? rowsFrom(dialect, meta, operation, names, filter) : undefined,
|
|
66
|
+
});
|
|
67
|
+
// A per-row engine keeps to the selected rows by a guard; a per-statement one narrowed what the body reads.
|
|
68
|
+
const guarded = filter && fires === 'eachRowIf' ? `IF ${filter} THEN\n${sql}\nEND IF;` : sql;
|
|
67
69
|
const opened = features.preamble ? `${features.preamble}\n${guarded}` : guarded;
|
|
68
|
-
const
|
|
70
|
+
const id = triggerId(dialect, meta, name);
|
|
71
|
+
const table = dialect.escapedTableName(meta);
|
|
72
|
+
const byWhen = fires === 'eachRowWhen';
|
|
73
|
+
const of = byWhen && operation === 'UPDATE' && trigger.of?.length
|
|
69
74
|
? ` OF ${trigger.of.map((key) => dialect.escapedColumnName(meta, key)).join(', ')}`
|
|
70
75
|
: '';
|
|
71
|
-
const each =
|
|
72
|
-
const
|
|
73
|
-
const
|
|
76
|
+
const each = perStatement ? '' : '\nFOR EACH ROW';
|
|
77
|
+
const whenClause = filter && byWhen ? `\nWHEN (${filter})` : '';
|
|
78
|
+
const event = `${timing} ${operation}${of}`;
|
|
74
79
|
const header = features.layout === 'tableFirst'
|
|
75
|
-
? `CREATE TRIGGER ${id}\nON ${table} ${
|
|
76
|
-
: `CREATE TRIGGER ${id}\n${
|
|
80
|
+
? `CREATE TRIGGER ${id}\nON ${table} ${event}${each}${whenClause}\nAS`
|
|
81
|
+
: `CREATE TRIGGER ${id}\n${event} ON ${table}${each}${whenClause}`;
|
|
77
82
|
if (features.body !== 'function') {
|
|
78
83
|
return [`${header}\nBEGIN\n${opened}\nEND`];
|
|
79
84
|
}
|
|
@@ -98,16 +103,10 @@ const EVENT_PARTS = {
|
|
|
98
103
|
* PL/pgSQL, MariaDB MySQL's.
|
|
99
104
|
*/
|
|
100
105
|
function triggerBody(dialect, meta, trigger, before) {
|
|
101
|
-
|
|
102
|
-
if (before && !features.before) {
|
|
106
|
+
if (before && !dialect.features.triggers.before) {
|
|
103
107
|
throw new UqlUsageError(`${dialect.dialectName} has no BEFORE trigger, only AFTER and INSTEAD OF, so '${trigger.on}' cannot be ` +
|
|
104
108
|
'rendered there. Use the matching after event, which sees the row already written.');
|
|
105
109
|
}
|
|
106
|
-
if (trigger.where && features.rows === 'set') {
|
|
107
|
-
throw new UqlUsageError(`${dialect.dialectName} fires a trigger once per statement, over the rows it touched, so no condition ` +
|
|
108
|
-
`can read one row: '${meta.entity.name}' cannot state a trigger 'where' there. Guard inside the body ` +
|
|
109
|
-
'instead, where `inserted` and `deleted` can be read as tables.');
|
|
110
|
-
}
|
|
111
110
|
const { run } = trigger;
|
|
112
111
|
const body = typeof run === 'function' ? run : (run[dialect.dialectName] ?? run[dialect.dialectFamily]);
|
|
113
112
|
if (!body) {
|
|
@@ -117,15 +116,14 @@ function triggerBody(dialect, meta, trigger, before) {
|
|
|
117
116
|
return body;
|
|
118
117
|
}
|
|
119
118
|
/**
|
|
120
|
-
* The one condition
|
|
121
|
-
*
|
|
119
|
+
* The rows a trigger fires for, as one condition on every engine: any watched column that moved, and
|
|
120
|
+
* whatever `where` asks. Empty where it names neither.
|
|
122
121
|
*/
|
|
123
|
-
function
|
|
122
|
+
function triggerFilter(dialect, meta, trigger, rows, names) {
|
|
124
123
|
const moved = movedColumns(dialect, meta, trigger.of ?? [], names);
|
|
125
|
-
const watched = moved && (source ? `EXISTS (SELECT 1 ${source})` : moved);
|
|
126
124
|
return [
|
|
127
|
-
...(
|
|
128
|
-
...(trigger.where ? condition(dialect, meta, trigger.where, rows, names, Boolean(
|
|
125
|
+
...(moved ? [moved] : []),
|
|
126
|
+
...(trigger.where ? condition(dialect, meta, trigger.where, rows, names, Boolean(moved)) : []),
|
|
129
127
|
].join(' AND ');
|
|
130
128
|
}
|
|
131
129
|
/** The PL/pgSQL function holding a body. `OR REPLACE`, since a dropped table leaves its function behind. */
|
|
@@ -135,12 +133,12 @@ function plpgsqlFunction(id, block) {
|
|
|
135
133
|
}
|
|
136
134
|
/** What the engine calls the rows it hands a trigger: records on a row-based engine, tables on a set-based one. */
|
|
137
135
|
function rowNames(dialect) {
|
|
138
|
-
return dialect.features.triggers.
|
|
136
|
+
return dialect.features.triggers.fires === 'eachStatement'
|
|
139
137
|
? { $new: 'inserted', $old: 'deleted' }
|
|
140
138
|
: { $new: 'NEW', $old: 'OLD' };
|
|
141
139
|
}
|
|
142
140
|
/**
|
|
143
|
-
* The `where`
|
|
141
|
+
* The `where` filter as the terms an `AND` joins. A callback writes its own off the rows; a predicate
|
|
144
142
|
* renders a term per row it names, spelled verbatim because `NEW` is a record the engine declares. Each
|
|
145
143
|
* is an `operand` wherever another term sits beside it, bracketing itself if compound, as `$where` does.
|
|
146
144
|
*/
|
|
@@ -203,7 +201,7 @@ function stampBody(dialect, meta, key, value, newRow) {
|
|
|
203
201
|
const stamped = dialect.compileDdl(value, entity);
|
|
204
202
|
const differs = raw(({ ctx, escapedPrefix }) => ctx.append(dialect.neExpr(`${escapedPrefix}${dialect.escapedColumnName(meta, key)}`, stamped)));
|
|
205
203
|
const where = { $and: [...keyed, differs] };
|
|
206
|
-
return
|
|
204
|
+
return written({ kind: 'update', entity, set: { [key]: value }, where });
|
|
207
205
|
}
|
|
208
206
|
/** A dollar quote the body does not contain, so no `$$` in it - a literal, a comment - ends the function early. */
|
|
209
207
|
function dollarQuote(body) {
|
|
@@ -228,23 +226,14 @@ function movedColumns(dialect, meta, of, { $new: newName, $old: oldName }) {
|
|
|
228
226
|
return differs.length > 1 ? `(${differs.join(' OR ')})` : differs.join('');
|
|
229
227
|
}
|
|
230
228
|
/**
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* watched column having moved, so the body writes for those rows alone, as a per-row engine fires for them.
|
|
234
|
-
* None on a row-based engine, whose body reads `NEW` and `OLD` bare.
|
|
229
|
+
* What a set-based engine's writes read: `inserted` on an insert, `deleted` on a delete, and on an update
|
|
230
|
+
* both, joined on the whole key, narrowed by `filter` to the rows a per-row engine would fire for.
|
|
235
231
|
*/
|
|
236
|
-
function rowsFrom(dialect, meta, operation,
|
|
237
|
-
if (dialect.features.triggers.rows !== 'set') {
|
|
238
|
-
return undefined;
|
|
239
|
-
}
|
|
240
|
-
const { $new, $old } = rowNames(dialect);
|
|
241
|
-
if (operation !== 'UPDATE') {
|
|
242
|
-
return `FROM ${operation === 'INSERT' ? $new : $old}`;
|
|
243
|
-
}
|
|
232
|
+
function rowsFrom(dialect, meta, operation, { $new, $old }, filter) {
|
|
244
233
|
const keyed = meta.ids.map((id) => {
|
|
245
234
|
const column = dialect.escapedColumnName(meta, id);
|
|
246
235
|
return `${$new}.${column} = ${$old}.${column}`;
|
|
247
236
|
});
|
|
248
|
-
const
|
|
249
|
-
return
|
|
237
|
+
const from = operation === 'UPDATE' ? `${$new} JOIN ${$old} ON ${keyed.join(' AND ')}` : operation === 'INSERT' ? $new : $old;
|
|
238
|
+
return { from, where: filter || undefined };
|
|
250
239
|
}
|
package/dist/mongo/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export { MongoSchemaGenerator } from '../migrate/generator/mongoSchemaGenerator.js';
|
|
2
1
|
export * from './mongoDialect.js';
|
|
3
2
|
export * from './mongodbQuerier.js';
|
|
4
3
|
export * from './mongodbQuerierPool.js';
|
|
4
|
+
export * from './mongoIntrospector.js';
|
|
5
|
+
export * from './mongoMigrationStorage.js';
|
|
6
|
+
export { isMongoQuerier, type MongoQuerier } from './mongoQuerier.js';
|
|
7
|
+
export * from './mongoSchemaGenerator.js';
|
|
5
8
|
export * from './textLanguage.js';
|
package/dist/mongo/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export { MongoSchemaGenerator } from '../migrate/generator/mongoSchemaGenerator.js';
|
|
2
1
|
export * from './mongoDialect.js';
|
|
3
2
|
export * from './mongodbQuerier.js';
|
|
4
3
|
export * from './mongodbQuerierPool.js';
|
|
4
|
+
export * from './mongoIntrospector.js';
|
|
5
|
+
export * from './mongoMigrationStorage.js';
|
|
6
|
+
export { isMongoQuerier } from './mongoQuerier.js';
|
|
7
|
+
export * from './mongoSchemaGenerator.js';
|
|
5
8
|
export * from './textLanguage.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { IndexFacet } from '
|
|
2
|
-
import { SchemaAST } from '
|
|
3
|
-
import {
|
|
1
|
+
import type { IndexFacet } from '../schema/indexDifferences.js';
|
|
2
|
+
import { SchemaAST } from '../schema/schemaAST.js';
|
|
3
|
+
import type { InstalledTriggers, QuerierPool, SchemaIntrospector, TableSchema } from '../type/index.js';
|
|
4
4
|
/**
|
|
5
5
|
* MongoDB schema introspector.
|
|
6
6
|
* MongoDB doesn't have a fixed schema, so this primarily focuses on collections and indexes.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { isMongoQuerier
|
|
4
|
-
import {
|
|
1
|
+
import { createTableNode, SchemaAST } from '../schema/schemaAST.js';
|
|
2
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
3
|
+
import { isMongoQuerier } from './mongoQuerier.js';
|
|
4
|
+
import { textConfigOf } from './textLanguage.js';
|
|
5
5
|
/** What a server without Atlas Search answers a search index command with. */
|
|
6
6
|
const SEARCH_NOT_ENABLED = 31082;
|
|
7
7
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { MigrationStorage,
|
|
1
|
+
import type { MigrationStorage, QuerierPool } from '../type/index.js';
|
|
2
|
+
import { type MongoQuerier } from './mongoQuerier.js';
|
|
2
3
|
/**
|
|
3
4
|
* Stores migration state in a MongoDB collection, named as the SQL table would be.
|
|
4
5
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { DEFAULT_MIGRATIONS_TABLE } from '../migrate/storage/databaseStorage.js';
|
|
2
|
+
import { withMongoQuerierForMigrations } from './mongoQuerier.js';
|
|
3
3
|
/**
|
|
4
4
|
* Stores migration state in a MongoDB collection, named as the SQL table would be.
|
|
5
5
|
*/
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Db } from 'mongodb';
|
|
2
|
+
import { type Querier, type QuerierPool } from '../type/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Extended querier interface for MongoDB execution.
|
|
5
|
+
*/
|
|
6
|
+
export interface MongoQuerier extends Querier {
|
|
7
|
+
/**
|
|
8
|
+
* The MongoDB database instance.
|
|
9
|
+
*/
|
|
10
|
+
readonly db: Db;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Type guard for a querier over a MongoDB database. A handle alone does not tell: the SQLite, D1 and
|
|
14
|
+
* Turso queriers carry a `db` of their own.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isMongoQuerier(querier: Querier): querier is MongoQuerier;
|
|
17
|
+
/** Runs `task` on a migration querier over MongoDB. `requiredBy` names the caller in the error. */
|
|
18
|
+
export declare function withMongoQuerierForMigrations<T>(pool: QuerierPool, requiredBy: string, task: (querier: MongoQuerier) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { withQuerierOfKind } from '../migrate/acquireQuerierForMigrations.js';
|
|
2
|
+
import { isSqlQuerier } from '../type/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Type guard for a querier over a MongoDB database. A handle alone does not tell: the SQLite, D1 and
|
|
5
|
+
* Turso queriers carry a `db` of their own.
|
|
6
|
+
*/
|
|
7
|
+
export function isMongoQuerier(querier) {
|
|
8
|
+
return 'db' in querier && !isSqlQuerier(querier);
|
|
9
|
+
}
|
|
10
|
+
/** Runs `task` on a migration querier over MongoDB. `requiredBy` names the caller in the error. */
|
|
11
|
+
export function withMongoQuerierForMigrations(pool, requiredBy, task) {
|
|
12
|
+
return withQuerierOfKind(pool, isMongoQuerier, `${requiredBy} requires a MongoDB querier`, task);
|
|
13
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { ForeignKeyAction, TableNode } from '
|
|
3
|
-
import { type CreateSchemaOptions, type EntityWhereMeta, type IndexSchema, type NamingStrategy, type SchemaDiff, type SchemaGenerator, type Type } from '
|
|
4
|
-
import
|
|
1
|
+
import type { AnyMigrationOperation } from '../migrate/builder/types.js';
|
|
2
|
+
import type { ForeignKeyAction, TableNode } from '../schema/types.js';
|
|
3
|
+
import { type CreateSchemaOptions, type EntityWhereMeta, type IndexSchema, type NamingStrategy, type SchemaDiff, type SchemaGenerator, type Type } from '../type/index.js';
|
|
4
|
+
import { MongoDialect } from './mongoDialect.js';
|
|
5
5
|
export declare class MongoSchemaGenerator extends MongoDialect implements SchemaGenerator {
|
|
6
6
|
protected readonly defaultForeignKeyAction?: ForeignKeyAction | undefined;
|
|
7
7
|
constructor(namingStrategy?: NamingStrategy, defaultForeignKeyAction?: ForeignKeyAction | undefined);
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { getMeta } from '
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { renderIndexDefinition } from './definitionToNode.js';
|
|
14
|
-
import { indexNodeToSchema } from './indexNodeToSchema.js';
|
|
1
|
+
import { getMeta } from '../entity/index.js';
|
|
2
|
+
import { assertIndexFeatures, assertIndexType } from '../migrate/ddl/indexDdl.js';
|
|
3
|
+
import { renderIndexDefinition } from '../migrate/generator/definitionToNode.js';
|
|
4
|
+
import { indexNodeToSchema } from '../migrate/generator/indexNodeToSchema.js';
|
|
5
|
+
import { assertIndexPredicate, refusedIndexPredicate } from '../migrate/indexPredicate.js';
|
|
6
|
+
import { sides } from '../migrate/schemaChange.js';
|
|
7
|
+
import { indexChanges } from '../schema/indexDifferences.js';
|
|
8
|
+
import { QueryRaw, } from '../type/index.js';
|
|
9
|
+
import { indexDistance, unsupportedVectorMetric } from '../type/vector.js';
|
|
10
|
+
import { declaredIndexes, declaredIndexName, renderIndexColumn } from '../util/ddlExpression.util.js';
|
|
11
|
+
import { fulltextConfig, fulltextWeights } from '../util/dialect.util.js';
|
|
12
|
+
import { UqlUsageError } from '../util/uqlError.js';
|
|
15
13
|
import { serializeMongoCommand } from './mongoCommand.js';
|
|
14
|
+
import { MongoDialect } from './mongoDialect.js';
|
|
15
|
+
import { textLanguage } from './textLanguage.js';
|
|
16
16
|
/** The index types a key spec can say, a plain key or `'text'`, and Atlas's vector search index. */
|
|
17
17
|
const MONGO_INDEX_TYPES = new Set(['btree', 'fulltext', 'vectorSearch']);
|
|
18
18
|
/** A key spec's one feature beyond its keys: a partial filter. */
|
|
@@ -2,7 +2,8 @@ import type { Document, MongoClient } from 'mongodb';
|
|
|
2
2
|
import { AbstractQuerier } from '../querier/index.js';
|
|
3
3
|
import type { EntityData, ExtraOptions, PrimaryKey, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryPage, QueryGroupMap, QueryOptions, QuerySearch, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
|
|
4
4
|
import type { MongoDialect } from './mongoDialect.js';
|
|
5
|
-
|
|
5
|
+
import type { MongoQuerier } from './mongoQuerier.js';
|
|
6
|
+
export declare class MongodbQuerier extends AbstractQuerier implements MongoQuerier {
|
|
6
7
|
readonly dialect: MongoDialect;
|
|
7
8
|
readonly conn: MongoClient;
|
|
8
9
|
readonly extra?: ExtraOptions | undefined;
|
package/dist/type/dialect.d.ts
CHANGED
|
@@ -186,16 +186,12 @@ export interface TriggerFeatures {
|
|
|
186
186
|
*/
|
|
187
187
|
readonly body: 'function' | 'inline';
|
|
188
188
|
/**
|
|
189
|
-
* How it
|
|
190
|
-
*
|
|
191
|
-
*
|
|
189
|
+
* How it fires, and so how it keeps to the rows `of` and `where` select: once per row, behind
|
|
190
|
+
* `UPDATE OF` and a `WHEN` (`'eachRowWhen'`) or, with no usable `WHEN` (the MySQL family, CockroachDB),
|
|
191
|
+
* an `IF c THEN ... END IF;` around the body (`'eachRowIf'`); or once per statement (`'eachStatement'`,
|
|
192
|
+
* SQL Server), narrowing the `inserted` and `deleted` tables its writes read.
|
|
192
193
|
*/
|
|
193
|
-
readonly
|
|
194
|
-
/**
|
|
195
|
-
* Whether it fires once per row, with a row on each side, or once per statement over the set it
|
|
196
|
-
* touched. SQL Server is the only one here that is set-based, reading `inserted` and `deleted`.
|
|
197
|
-
*/
|
|
198
|
-
readonly rows: 'row' | 'set';
|
|
194
|
+
readonly fires: 'eachRowWhen' | 'eachRowIf' | 'eachStatement';
|
|
199
195
|
/**
|
|
200
196
|
* Where a trigger's name is unique, and so what a `DROP` has to name: per table on the Postgres
|
|
201
197
|
* family, which spells `DROP TRIGGER x ON t`, and per schema everywhere else, which spells
|
|
@@ -226,6 +222,14 @@ export interface TriggerFeatures {
|
|
|
226
222
|
*/
|
|
227
223
|
readonly before: boolean;
|
|
228
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* The rows a set-based trigger's writes read: the tables `from` names, `inserted` and the like, narrowed
|
|
227
|
+
* by `where` to the ones the trigger selects.
|
|
228
|
+
*/
|
|
229
|
+
export type TriggerRows = {
|
|
230
|
+
readonly from: string;
|
|
231
|
+
readonly where?: string;
|
|
232
|
+
};
|
|
229
233
|
/** Where DDL's SQL sits: the row a trigger's predicate reads, as its prefix, and a set-based body's rows. */
|
|
230
234
|
export type DdlRenderOptions = Pick<QueryComparisonOptions, 'escapedPrefix' | 'operand'> & Pick<QueryRawRenderOptions, 'rows'>;
|
|
231
235
|
/**
|
|
@@ -276,8 +280,8 @@ export interface SqlQueryDialect {
|
|
|
276
280
|
update<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryRenderOptions): void;
|
|
277
281
|
/** An upsert of one record or many by their conflict paths. */
|
|
278
282
|
upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[]): void;
|
|
279
|
-
/** A write in a trigger's body; `rows`
|
|
280
|
-
triggerWrite(ctx: QueryContext, write: TriggerWrite, rows?:
|
|
283
|
+
/** A write in a trigger's body; `rows` are what a set-based engine's body reads, narrowed to the ones it fires for. */
|
|
284
|
+
triggerWrite(ctx: QueryContext, write: TriggerWrite, rows?: TriggerRows): void;
|
|
281
285
|
/** A delete of the records the query matches, a soft delete where the entity has one. */
|
|
282
286
|
delete<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, opts?: QueryRenderOptions): void;
|
|
283
287
|
/**
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import type { AbstractDialect } from '../dialect/abstractDialect.js';
|
|
1
2
|
import type { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* The dialects the migrator runs on, which `dialectName` tells apart: every SQL engine, and MongoDB, named
|
|
5
|
+
* by its `dialectName` alone so these types reach no `mongodb` declaration.
|
|
6
|
+
*/
|
|
7
|
+
export type MigratorDialect = AbstractSqlDialect | (AbstractDialect & {
|
|
8
|
+
readonly dialectName: 'mongodb';
|
|
9
|
+
});
|
package/dist/type/querier.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { Db } from 'mongodb';
|
|
2
1
|
import type { AbstractSqlDialect } from '../dialect/index.js';
|
|
3
2
|
import type { SqlDialectName } from './dialect.js';
|
|
4
3
|
import type { FieldKey, HookEvent, RelationKey } from './entity.js';
|
|
@@ -116,20 +115,6 @@ export interface SqlQuerier extends Querier {
|
|
|
116
115
|
* Type guard to check if a querier supports raw SQL execution
|
|
117
116
|
*/
|
|
118
117
|
export declare function isSqlQuerier(querier: Querier): querier is SqlQuerier;
|
|
119
|
-
/**
|
|
120
|
-
* Extended querier interface for MongoDB execution.
|
|
121
|
-
*/
|
|
122
|
-
export interface MongoQuerier extends Querier {
|
|
123
|
-
/**
|
|
124
|
-
* The MongoDB database instance.
|
|
125
|
-
*/
|
|
126
|
-
readonly db: Db;
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Type guard for a querier over a MongoDB database. A handle alone does not tell: the SQLite, D1 and
|
|
130
|
-
* Turso queriers carry a `db` of their own.
|
|
131
|
-
*/
|
|
132
|
-
export declare function isMongoQuerier(querier: Querier): querier is MongoQuerier;
|
|
133
118
|
/**
|
|
134
119
|
* Context passed to global querier listeners.
|
|
135
120
|
*/
|
package/dist/type/querier.js
CHANGED
|
@@ -8,10 +8,3 @@ export function isSqlQuerier(querier) {
|
|
|
8
8
|
q.dialect !== undefined &&
|
|
9
9
|
typeof q.dialect.escapeIdChar === 'string');
|
|
10
10
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Type guard for a querier over a MongoDB database. A handle alone does not tell: the SQLite, D1 and
|
|
13
|
-
* Turso queriers carry a `db` of their own.
|
|
14
|
-
*/
|
|
15
|
-
export function isMongoQuerier(querier) {
|
|
16
|
-
return 'db' in querier && !isSqlQuerier(querier);
|
|
17
|
-
}
|
package/dist/type/queryRaw.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { QueryContext, RelationAggregateSpec, SqlQueryDialect } from './dialect.js';
|
|
1
|
+
import type { QueryContext, RelationAggregateSpec, SqlQueryDialect, TriggerRows } from './dialect.js';
|
|
2
2
|
import type { Type } from './utility.js';
|
|
3
3
|
/** What a `raw` callback receives. See {@link QueryRawFn}. */
|
|
4
4
|
export type QueryRawRenderOptions = {
|
|
@@ -16,10 +16,10 @@ export type QueryRawRenderOptions = {
|
|
|
16
16
|
*/
|
|
17
17
|
entity?: Type<unknown>;
|
|
18
18
|
/**
|
|
19
|
-
* The
|
|
20
|
-
*
|
|
19
|
+
* The rows a set-based trigger's writes read. Absent where the body reads `NEW` and `OLD` bare, and
|
|
20
|
+
* outside a trigger.
|
|
21
21
|
*/
|
|
22
|
-
rows?:
|
|
22
|
+
rows?: TriggerRows;
|
|
23
23
|
};
|
|
24
24
|
/** {@link QueryRawRenderOptions} as the callers along the way fill them in, every one still optional. */
|
|
25
25
|
export type QueryRawFnOptions = Partial<QueryRawRenderOptions>;
|
|
@@ -83,3 +83,10 @@ export declare class RelationAggregate<V = unknown, Storable extends boolean = b
|
|
|
83
83
|
/** What it reads, kept beside the SQL so a read decodes the value the way the target's field does. */
|
|
84
84
|
spec: RelationAggregateSpec, value: QueryRawFn);
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* A write `insertInto`, `updateTable` or `deleteFrom` renders, or several joined in one `raw`. It reads a
|
|
88
|
+
* set-based trigger's rows through {@link QueryRawRenderOptions.rows}, which is how `of` and `where` narrow
|
|
89
|
+
* them there; SQL of its own reads `inserted` and `deleted` whole.
|
|
90
|
+
*/
|
|
91
|
+
export declare class TriggerWriteRaw extends QueryRaw {
|
|
92
|
+
}
|
package/dist/type/queryRaw.js
CHANGED
|
@@ -57,3 +57,10 @@ export class RelationAggregate extends QueryRaw {
|
|
|
57
57
|
this.spec = spec;
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* A write `insertInto`, `updateTable` or `deleteFrom` renders, or several joined in one `raw`. It reads a
|
|
62
|
+
* set-based trigger's rows through {@link QueryRawRenderOptions.rows}, which is how `of` and `where` narrow
|
|
63
|
+
* them there; SQL of its own reads `inserted` and `deleted` whole.
|
|
64
|
+
*/
|
|
65
|
+
export class TriggerWriteRaw extends QueryRaw {
|
|
66
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FieldKey, JsonFieldPaths, JsonFieldPathValue, RelationKey, RelationTarget } from './entity.js';
|
|
1
|
+
import type { FieldKey, FieldKeyOf, JsonFieldPaths, JsonFieldPathValue, RelationKey, RelationTarget } from './entity.js';
|
|
2
2
|
import type { QuerySelect } from './query.js';
|
|
3
3
|
import type { QueryRaw, RawFor } from './queryRaw.js';
|
|
4
4
|
import type { AtLeastOne, ExpandScalar, IsMany, QueryComparableScalar, Scalar } from './utility.js';
|
|
@@ -12,9 +12,9 @@ export type QueryTextSearchOptions<E> = {
|
|
|
12
12
|
*/
|
|
13
13
|
$value: string;
|
|
14
14
|
/**
|
|
15
|
-
* the fields to search, `{ title: true, body: true }`, in the order a MySQL `FULLTEXT` index lists them.
|
|
15
|
+
* the string fields to search, `{ title: true, body: true }`, in the order a MySQL `FULLTEXT` index lists them.
|
|
16
16
|
*/
|
|
17
|
-
$fields?: QuerySelect<E
|
|
17
|
+
$fields?: QuerySelect<E, FieldKeyOf<E, string>>;
|
|
18
18
|
/**
|
|
19
19
|
* The language the search is parsed in (e.g. `'english'`, or `'simple'` for no stemming), else that of
|
|
20
20
|
* the fulltext index over its fields: the Postgres family's text-search config, MongoDB's `$language`.
|
|
@@ -28,18 +28,28 @@ export type QueryTextSearchOptions<E> = {
|
|
|
28
28
|
* reported on its key: ids go through `{ id: 1 }` or the by-id methods.
|
|
29
29
|
*/
|
|
30
30
|
export type QueryWhere<E, Raw = QueryRaw, K extends keyof E = FieldKey<E> | RelationKey<E>> = QueryWhereRootOperator<E, Raw> & {
|
|
31
|
-
[P in K]?: P extends FieldKey<E> ? QueryWhereFieldValue<E[P], Raw> :
|
|
31
|
+
[P in K]?: P extends FieldKey<E> ? QueryWhereFieldValue<E[P], Raw> : QueryWhereRelation<RelationTarget<E[P]>, Raw>;
|
|
32
32
|
} & ([JsonFieldPaths<E>] extends [never] ? unknown : {
|
|
33
33
|
[P in JsonFieldPaths<E>]?: QueryWhereFieldValue<JsonFieldPathValue<E, P>, Raw>;
|
|
34
34
|
});
|
|
35
35
|
/**
|
|
36
|
-
* Filter a to-many relation by its row count
|
|
36
|
+
* Filter a to-many relation by its row count, and by nothing beside it: the target's keys and the root
|
|
37
|
+
* operators are refused, as the engine refuses them at run time.
|
|
37
38
|
* @example { users: { $size: 2 } }
|
|
38
39
|
* @example { users: { $size: { $gte: 2 } } }
|
|
39
40
|
*/
|
|
40
|
-
export type QueryRelationSizeFilter = {
|
|
41
|
+
export type QueryRelationSizeFilter<T = object> = {
|
|
41
42
|
readonly $size: number | QuerySizeComparisonOps;
|
|
43
|
+
} & {
|
|
44
|
+
readonly [K in (keyof T & string) | keyof QueryWhereRootOperator<object>]?: never;
|
|
42
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* A relation key's filter: a where over the target's rows, or its row count, never both. A target keyed
|
|
48
|
+
* by an index signature declares no keys to refuse, so there the run-time check alone answers a mix.
|
|
49
|
+
*/
|
|
50
|
+
export type QueryWhereRelation<T, Raw = QueryRaw> = string extends keyof T ? QueryWhere<T, Raw> | QueryRelationSizeFilter : (QueryWhere<T, Raw> & {
|
|
51
|
+
readonly $size?: never;
|
|
52
|
+
}) | QueryRelationSizeFilter<T>;
|
|
43
53
|
export type QueryWhereRootOperator<E, Raw = QueryRaw> = {
|
|
44
54
|
/**
|
|
45
55
|
* joins query clauses with a logical `AND`, returns records that match all the clauses.
|
package/dist/util/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export * from './ddlExpression.util.js';
|
|
|
7
7
|
export * from './logger.js';
|
|
8
8
|
export * from './object.util.js';
|
|
9
9
|
export * from './raw.js';
|
|
10
|
-
export
|
|
10
|
+
export { deleteFrom, insertInto, updateTable } from './triggerWrite.js';
|
|
11
11
|
export * from './rowKey.util.js';
|
|
12
12
|
export * from './relationQuery.util.js';
|
|
13
13
|
export * from './sql.util.js';
|
package/dist/util/index.js
CHANGED
|
@@ -7,7 +7,7 @@ export * from './ddlExpression.util.js';
|
|
|
7
7
|
export * from './logger.js';
|
|
8
8
|
export * from './object.util.js';
|
|
9
9
|
export * from './raw.js';
|
|
10
|
-
export
|
|
10
|
+
export { deleteFrom, insertInto, updateTable } from './triggerWrite.js';
|
|
11
11
|
export * from './rowKey.util.js';
|
|
12
12
|
export * from './relationQuery.util.js';
|
|
13
13
|
export * from './sql.util.js';
|
package/dist/util/raw.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getMeta } from '../entity/metadata/definition.js';
|
|
2
|
-
import { ColumnRef, QueryRaw, RAW_TEXT, RelationAggregate, } from '../type/index.js';
|
|
2
|
+
import { ColumnRef, QueryRaw, RAW_TEXT, RelationAggregate, TriggerWriteRaw, } from '../type/index.js';
|
|
3
3
|
import { aggregateOf, isInlinedExpression } from './field.util.js';
|
|
4
4
|
import { entityName, hasKeys } from './object.util.js';
|
|
5
5
|
import { UqlUsageError } from './uqlError.js';
|
|
@@ -7,7 +7,9 @@ export function raw(value, ...rest) {
|
|
|
7
7
|
if (!isTemplateStrings(value)) {
|
|
8
8
|
return new QueryRaw(value);
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
// Writes joined by whitespace alone are still only writes, so a set-based trigger narrows each one.
|
|
11
|
+
const writes = rest.length > 0 && rest.every((v) => v instanceof TriggerWriteRaw) && value.every((part) => !part.trim());
|
|
12
|
+
return new (writes ? TriggerWriteRaw : QueryRaw)((opts) => {
|
|
11
13
|
const { ctx } = opts;
|
|
12
14
|
ctx.append(value[0]);
|
|
13
15
|
rest.forEach((interpolated, i) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type EntityPredicate, type QueryRaw, type TriggerWrite, type Type, type UpdatePayload, type WritableKey, type WriteRow } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* A row inserted by a trigger's body, into any table: `insertInto(PostAudit, { postId: newRow.id })`.
|
|
4
4
|
* Each value is a literal or SQL, a row's ref most often, and every engine renders it, SQL Server's
|
|
@@ -13,3 +13,5 @@ export declare function updateTable<E extends object>(entity: Type<E>, q: {
|
|
|
13
13
|
export declare function deleteFrom<E extends object>(entity: Type<E>, q: {
|
|
14
14
|
readonly $where: EntityPredicate<E>;
|
|
15
15
|
}): QueryRaw;
|
|
16
|
+
/** A write in a trigger's body, as every helper here and a stamp render one. */
|
|
17
|
+
export declare function written(write: TriggerWrite): QueryRaw;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TriggerWriteRaw, } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* A row inserted by a trigger's body, into any table: `insertInto(PostAudit, { postId: newRow.id })`.
|
|
4
4
|
* Each value is a literal or SQL, a row's ref most often, and every engine renders it, SQL Server's
|
|
@@ -15,6 +15,7 @@ export function updateTable(entity, q, set) {
|
|
|
15
15
|
export function deleteFrom(entity, q) {
|
|
16
16
|
return written({ kind: 'delete', entity, where: q.$where });
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
/** A write in a trigger's body, as every helper here and a stamp render one. */
|
|
19
|
+
export function written(write) {
|
|
20
|
+
return new TriggerWriteRaw(({ ctx, dialect, rows }) => dialect.triggerWrite(ctx, write, rows));
|
|
20
21
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
4
|
"description": "JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.90.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|
|
@@ -156,7 +156,7 @@
|
|
|
156
156
|
"pg-query-stream": "^4.17.0",
|
|
157
157
|
"rxjs": "^7.8.2",
|
|
158
158
|
"sqlite-vec": "^0.1.9",
|
|
159
|
-
"ws": "^8.
|
|
159
|
+
"ws": "^8.22.0"
|
|
160
160
|
},
|
|
161
161
|
"author": "Roger Padilla",
|
|
162
162
|
"repository": {
|
package/skills/uql-orm/SKILL.md
CHANGED
|
@@ -84,7 +84,7 @@ export class Post {
|
|
|
84
84
|
an update must carry the version it read (a compile error otherwise), and one against a row someone else moved on throws `UqlOptimisticLockError` (kind `optimisticLock`, HTTP 409). Its updates name one row by its id; save and upsert are refused.
|
|
85
85
|
- `@Field({ computed })` is a value the database produces, on a `readonly` property: SQL over the row, ``(u) => raw`${u.first} || ' ' || ${u.last}` ``, or a relation aggregate, `(order) => order.items.count()`.
|
|
86
86
|
`stored: true` makes the SQL a generated column; `stored: ['insert', 'update']` makes it a stamp, a trigger writing it on those events whoever writes the row (``computed: raw`CURRENT_TIMESTAMP` ``), where `onUpdate` covers only uql's own writes.
|
|
87
|
-
- `@Trigger({ on: 'afterUpdate', of: (post) => [post.status], where: { $old: { status: 'draft' } }, run })` is a trigger the database fires (`defineEntity`'s `triggers` or `defineTrigger` without decorators); `where` holds a `$where` predicate per row it names, or SQL off the rows. `run` takes `(newRow, oldRow)`, each only where the event has it (no `oldRow` on insert, no `newRow` on delete), and returns the body: `insertInto(Audit, { postId: newRow.id })`, `updateTable(Audit, { $where: { postId: newRow.id } }, { status: newRow.status })` or `deleteFrom(Audit, { $where: { postId: oldRow.id } })`, typed by the entity written and rendered on every engine (no `onInsert`/`onUpdate` fills, so an insert names each field uql fills on insert unless its column has a `defaultValue`; `$where` reads the entity's own fields; no entity filters, security ones included, so a soft-delete entity is hard-deleted; an update or delete naming no rows is refused; no `$inc`/`$mul`/`$push` on SQL Server), several joined in one `raw`. Anything else is `raw` SQL over the refs, one body for every engine or `{ postgres, mssql, ... }` where they differ (SQL Server fires per statement, reading `inserted`/`deleted` as tables, with no `before
|
|
87
|
+
- `@Trigger({ on: 'afterUpdate', of: (post) => [post.status], where: { $old: { status: 'draft' } }, run })` is a trigger the database fires (`defineEntity`'s `triggers` or `defineTrigger` without decorators); `where` holds a `$where` predicate per row it names, or SQL off the rows. `run` takes `(newRow, oldRow)`, each only where the event has it (no `oldRow` on insert, no `newRow` on delete), and returns the body: `insertInto(Audit, { postId: newRow.id })`, `updateTable(Audit, { $where: { postId: newRow.id } }, { status: newRow.status })` or `deleteFrom(Audit, { $where: { postId: oldRow.id } })`, typed by the entity written and rendered on every engine (no `onInsert`/`onUpdate` fills, so an insert names each field uql fills on insert unless its column has a `defaultValue`; `$where` reads the entity's own fields; no entity filters, security ones included, so a soft-delete entity is hard-deleted; an update or delete naming no rows is refused; no `$inc`/`$mul`/`$push` on SQL Server), several joined in one `raw`. Anything else is `raw` SQL over the refs, one body for every engine or `{ postgres, mssql, ... }` where they differ (SQL Server fires per statement, reading `inserted`/`deleted` as tables, with no `before*`; `of` and `where` narrow what the write helpers read there, so SQL of its own is refused beside them). MongoDB has none, and refuses a write to an entity declaring one.
|
|
88
88
|
- `defineEntity` defines the same entity without decorators: https://uql-orm.dev/entities/imperative.md
|
|
89
89
|
|
|
90
90
|
## Queries
|
|
File without changes
|