uql-orm 0.69.0 → 0.71.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.
Files changed (96) hide show
  1. package/README.md +2 -0
  2. package/dist/browser/querier/httpQuerier.d.ts +7 -7
  3. package/dist/browser/type/clientQuerier.d.ts +5 -5
  4. package/dist/browser/uql-browser.min.js +2 -2
  5. package/dist/browser/uql-browser.min.js.map +4 -4
  6. package/dist/cockroachdb/cockroachDialect.d.ts +6 -0
  7. package/dist/cockroachdb/cockroachDialect.js +10 -2
  8. package/dist/d1/d1SqliteDialect.d.ts +3 -0
  9. package/dist/d1/d1SqliteDialect.js +3 -1
  10. package/dist/dialect/abstractDialect.d.ts +8 -2
  11. package/dist/dialect/abstractDialect.js +17 -1
  12. package/dist/dialect/abstractSqlDialect.d.ts +21 -5
  13. package/dist/dialect/abstractSqlDialect.js +91 -46
  14. package/dist/dialect/aliases.d.ts +10 -7
  15. package/dist/dialect/aliases.js +10 -7
  16. package/dist/dialect/hydrateColumn.d.ts +3 -2
  17. package/dist/dialect/hydrateColumn.js +10 -1
  18. package/dist/dialect/mysqlLikeSqlDialect.js +5 -3
  19. package/dist/dialect/pgLikeSqlDialect.d.ts +17 -5
  20. package/dist/dialect/pgLikeSqlDialect.js +34 -15
  21. package/dist/dialect/queryJoins.d.ts +19 -1
  22. package/dist/dialect/queryJoins.js +54 -11
  23. package/dist/dialect/vectorCast.d.ts +2 -0
  24. package/dist/dialect/vectorCast.js +7 -0
  25. package/dist/dialect/vectorSqlDialect.d.ts +7 -3
  26. package/dist/dialect/vectorSqlDialect.js +15 -10
  27. package/dist/entity/decorator/members.d.ts +25 -11
  28. package/dist/entity/metadata/definition.d.ts +8 -3
  29. package/dist/libsql/libsqlDialect.d.ts +1 -1
  30. package/dist/libsql/libsqlDialect.js +3 -3
  31. package/dist/maria/mariaDialect.d.ts +3 -9
  32. package/dist/maria/mariaDialect.js +4 -13
  33. package/dist/maria/mariadbQuerier.js +9 -3
  34. package/dist/migrate/codegen/entityCodeGenerator.js +4 -2
  35. package/dist/migrate/ddl/index.d.ts +1 -0
  36. package/dist/migrate/ddl/index.js +4 -2
  37. package/dist/migrate/ddl/indexDdl.d.ts +2 -0
  38. package/dist/migrate/ddl/indexDdl.js +10 -2
  39. package/dist/migrate/ddl/pgIndexDdl.d.ts +3 -4
  40. package/dist/migrate/ddl/pgIndexDdl.js +11 -11
  41. package/dist/migrate/ddl/sqliteIndexDdl.d.ts +11 -0
  42. package/dist/migrate/ddl/sqliteIndexDdl.js +38 -0
  43. package/dist/migrate/generator/mongoCommand.d.ts +28 -0
  44. package/dist/migrate/generator/mongoCommand.js +8 -0
  45. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +2 -0
  46. package/dist/migrate/generator/mongoSchemaGenerator.js +50 -6
  47. package/dist/migrate/introspection/mongoIntrospector.js +33 -7
  48. package/dist/migrate/introspection/postgresIntrospector.js +19 -0
  49. package/dist/migrate/introspection/sqliteIntrospector.d.ts +6 -0
  50. package/dist/migrate/introspection/sqliteIntrospector.js +29 -3
  51. package/dist/migrate/migrator.d.ts +2 -1
  52. package/dist/migrate/migrator.js +5 -3
  53. package/dist/mongo/mongoDialect.d.ts +33 -27
  54. package/dist/mongo/mongoDialect.js +178 -114
  55. package/dist/mongo/mongodbQuerier.d.ts +0 -2
  56. package/dist/mongo/mongodbQuerier.js +14 -13
  57. package/dist/mssql/mssqlDialect.js +4 -2
  58. package/dist/postgres/postgresDialect.js +2 -2
  59. package/dist/querier/abstractQuerier.d.ts +16 -11
  60. package/dist/querier/abstractQuerier.js +28 -8
  61. package/dist/querier/abstractQuerierPool.d.ts +9 -9
  62. package/dist/querier/abstractSqlQuerier.d.ts +1 -1
  63. package/dist/querier/abstractSqlQuerier.js +3 -3
  64. package/dist/schema/canonicalType.js +10 -4
  65. package/dist/schema/indexDifferences.d.ts +5 -2
  66. package/dist/schema/indexDifferences.js +5 -0
  67. package/dist/schema/schemaASTBuilder.js +3 -2
  68. package/dist/sqlite/localSqliteQuerierPool.d.ts +10 -11
  69. package/dist/sqlite/localSqliteQuerierPool.js +8 -11
  70. package/dist/sqlite/nodeSqliteQuerierPool.d.ts +4 -2
  71. package/dist/sqlite/nodeSqliteQuerierPool.js +7 -4
  72. package/dist/sqlite/sqliteDialect.d.ts +9 -1
  73. package/dist/sqlite/sqliteDialect.js +43 -3
  74. package/dist/sqlite/sqliteQuerierPool.d.ts +6 -5
  75. package/dist/sqlite/sqliteQuerierPool.js +10 -7
  76. package/dist/turso/tursoDialect.d.ts +1 -1
  77. package/dist/turso/tursoDialect.js +6 -2
  78. package/dist/turso/tursoLocalDialect.d.ts +1 -1
  79. package/dist/turso/tursoLocalDialect.js +1 -1
  80. package/dist/turso/tursoLocalQuerierPool.d.ts +4 -5
  81. package/dist/turso/tursoLocalQuerierPool.js +3 -10
  82. package/dist/type/dialect.d.ts +11 -0
  83. package/dist/type/entity.d.ts +45 -5
  84. package/dist/type/migration.d.ts +14 -9
  85. package/dist/type/query.d.ts +6 -0
  86. package/dist/type/queryAggregate.d.ts +73 -42
  87. package/dist/type/queryAggregate.js +4 -21
  88. package/dist/type/universalQuerier.d.ts +9 -9
  89. package/dist/type/vector.d.ts +17 -0
  90. package/dist/type/vector.js +6 -0
  91. package/dist/util/ddlExpression.util.d.ts +2 -0
  92. package/dist/util/ddlExpression.util.js +5 -0
  93. package/dist/util/dialect.util.d.ts +17 -3
  94. package/dist/util/dialect.util.js +40 -6
  95. package/package.json +5 -3
  96. package/skills/uql-orm/SKILL.md +142 -0
@@ -188,6 +188,14 @@ export function findVectorSort(sort) {
188
188
  }
189
189
  return undefined;
190
190
  }
191
+ /** `$candidates`, checked: it can be spelled into a statement, and `/http` input is untyped. */
192
+ export function vectorCandidates(q) {
193
+ const candidates = q.$candidates;
194
+ if (candidates !== undefined && (!Number.isInteger(candidates) || candidates < 1)) {
195
+ throw new TypeError(`$candidates must be a positive integer, got ${JSON.stringify(candidates)}`);
196
+ }
197
+ return candidates;
198
+ }
191
199
  /**
192
200
  * Every index type that means "vector" to some engine: pgvector's two, the generic one MariaDB and
193
201
  * CockroachDB share, and Atlas's. Wider than {@link VECTOR_INDEX_TYPES}, which is the set whose DDL
@@ -344,22 +352,36 @@ export function parseGroupMap(group, select) {
344
352
  const entries = [];
345
353
  const groupMap = group ?? {};
346
354
  for (const alias of getKeys(groupMap)) {
347
- if (groupMap[alias]) {
348
- entries.push({ kind: 'key', alias });
355
+ const ref = groupMap[alias];
356
+ if (ref) {
357
+ entries.push({ kind: 'key', alias, path: ref === true ? [alias] : groupRefPath(alias, ref) });
349
358
  }
350
359
  }
351
360
  if (!select) {
352
361
  return entries;
353
362
  }
354
363
  for (const alias of getKeys(select)) {
355
- const fnEntry = select[alias];
356
- const key = getKeys(fnEntry)[0];
357
- // Flat DISTINCT ops (`$countDistinct`, ...) normalize to their base op + a `distinct` flag.
364
+ const { $where: where } = select[alias];
365
+ const call = select[alias];
366
+ const key = getKeys(call).find((name) => name !== '$where');
367
+ if (key === undefined) {
368
+ throw new TypeError(`aggregate '${alias}' names no op, only a $where`);
369
+ }
370
+ // `$countDistinct` normalizes to `$count` plus a `distinct` flag.
358
371
  const { op, distinct } = resolveAggregateOp(key);
359
- entries.push({ kind: 'fn', alias, op, fieldRef: aggregateFieldRef(alias, fnEntry[key]), distinct });
372
+ const fieldRef = aggregateFieldRef(alias, call[key]);
373
+ entries.push({ kind: 'fn', alias, op, fieldRef, distinct, ...(where && hasKeys(where) ? { where } : {}) });
360
374
  }
361
375
  return entries;
362
376
  }
377
+ /** The path a group key's `{ transaction: { orderId: true } }` names, one key at each level. */
378
+ function groupRefPath(alias, ref) {
379
+ const [key, ...rest] = isRecord(ref) ? getKeys(ref) : [];
380
+ if (!isRecord(ref) || key === undefined || rest.length) {
381
+ throw new TypeError(`$group '${alias}' names one field by the path to it: got ${JSON.stringify(ref)}`);
382
+ }
383
+ return ref[key] === true ? [key] : [key, ...groupRefPath(alias, ref[key])];
384
+ }
363
385
  /** The column an aggregate reads: `'*'`, or the one field its `{ field: true }` names. */
364
386
  function aggregateFieldRef(alias, arg) {
365
387
  const [field, ...rest] = arg === '*' ? [arg] : namedKeys(arg);
@@ -418,6 +440,18 @@ export function assertAggregateColumns(clauseMap, emitted, clause) {
418
440
  }
419
441
  }
420
442
  }
443
+ /** The text-search config a fulltext index builds with where it states none: language-neutral, no stemming. */
444
+ const DEFAULT_TEXT_CONFIG = 'simple';
445
+ /** The text-search config a fulltext index builds with, which a search it serves has to parse with too. */
446
+ export function fulltextConfig(index) {
447
+ return index.config ?? DEFAULT_TEXT_CONFIG;
448
+ }
449
+ /** The fulltext index over exactly `fields`, in order, which a search of them is served by. */
450
+ export function fulltextIndexOver(meta, fields) {
451
+ return meta.indexes?.find((index) => index.type === 'fulltext' &&
452
+ index.columns.length === fields.length &&
453
+ index.columns.every((entry, at) => entry.column === fields[at]));
454
+ }
421
455
  /**
422
456
  * The fields a `$text` searches: those it names, or else the columns of the entity's fulltext index,
423
457
  * the declaration MySQL's `MATCH` has to name exactly and a MongoDB text index already is. Refused
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "homepage": "https://uql-orm.dev",
4
4
  "description": "The JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
5
5
  "license": "MIT",
6
- "version": "0.69.0",
6
+ "version": "0.71.0",
7
7
  "type": "module",
8
8
  "engines": {
9
9
  "node": ">=24"
@@ -52,11 +52,12 @@
52
52
  },
53
53
  "files": [
54
54
  "dist",
55
+ "skills",
55
56
  "README.md"
56
57
  ],
57
58
  "scripts": {
58
- "prepack": "bun run build && bun run verify-dist.ts && cp ../../README.md .",
59
- "postpack": "rm README.md && npm pkg delete gitHead",
59
+ "prepack": "bun run build && bun run verify-dist.ts && cp ../../README.md . && cp -R ../../skills .",
60
+ "postpack": "rm -r README.md skills && npm pkg delete gitHead",
60
61
  "compile.browser": "bun build src/browser/index.ts --minify --sourcemap=linked --format=esm --target=browser --outdir=dist/browser --entry-naming 'uql-browser.min.[ext]'",
61
62
  "build": "bun run clean && tsc -b tsconfig.build.json && bun run compile.browser && bun run verify-dist.ts",
62
63
  "start": "tsc --watch",
@@ -167,6 +168,7 @@
167
168
  "url": "https://github.com/rogerpadilla/uql/issues"
168
169
  },
169
170
  "keywords": [
171
+ "tanstack-intent",
170
172
  "orm",
171
173
  "uql",
172
174
  "sql",
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: uql-orm
3
+ description: >
4
+ Write code with UQL (the uql-orm package), the TypeScript ORM whose queries are plain JSON objects,
5
+ on PostgreSQL, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, MongoDB, Turso, Neon, D1 and PGlite.
6
+ Use when a project imports uql-orm, or when defining entities, querying, populating relations,
7
+ writing transactions, raw SQL or migrations with it. UQL is not Prisma, Drizzle, TypeORM or MikroORM:
8
+ their APIs do not carry over.
9
+ ---
10
+
11
+ # UQL
12
+
13
+ Entities are classes; a query is a JSON object checked key by key against the entity; the same query runs
14
+ on every database UQL supports. There is no schema file, no generated client, and no query builder.
15
+
16
+ The full docs are Markdown at https://uql-orm.dev/llms.txt, one page per URL. Read the page for the task
17
+ before guessing an option: every page listed there is a `.md` URL.
18
+
19
+ ## Setup
20
+
21
+ ```sh
22
+ npm install uql-orm pg # or mysql2, mariadb, better-sqlite3, mongodb, @libsql/client, ...
23
+ ```
24
+
25
+ ESM only. Node 24+, Bun, Deno or an edge runtime, TypeScript 5.2+. Decorators are the TC39 standard:
26
+ never enable `experimentalDecorators` or `emitDecoratorMetadata`, never import `reflect-metadata`.
27
+ In `tsconfig.json`, `module` is `nodenext` or `preserve`, and `target` is a dated one (`es2022`+), not `esnext`.
28
+
29
+ ```ts
30
+ // uql.config.ts
31
+ import type { Config } from 'uql-orm';
32
+ import { PgQuerierPool } from 'uql-orm/postgres';
33
+ import { Post, User } from './entities.js';
34
+
35
+ export const pool = new PgQuerierPool({ connectionString: process.env.DATABASE_URL });
36
+
37
+ export default { pool, entities: [User, Post] } satisfies Config;
38
+ ```
39
+
40
+ Each driver has its own entry point: `uql-orm/postgres`, `uql-orm/mysql`, `uql-orm/maria`, `uql-orm/sqlite`,
41
+ `uql-orm/mongo`, `uql-orm/libsql`, `uql-orm/turso`, `uql-orm/neon`, `uql-orm/d1`, `uql-orm/pglite`,
42
+ `uql-orm/bunSql`, `uql-orm/mssql`, `uql-orm/cockroachdb`. Build the pool once per process and import it.
43
+
44
+ ## Entities
45
+
46
+ ```ts
47
+ import { Entity, Field, Id, ManyToOne, OneToMany } from 'uql-orm';
48
+
49
+ @Entity()
50
+ export class User {
51
+ @Id({ type: 'uuid', onInsert: () => crypto.randomUUID() })
52
+ id?: string;
53
+
54
+ @Field({ type: String, unique: true, nullable: false })
55
+ email?: string;
56
+
57
+ @OneToMany({ entity: () => Post, mappedBy: (post) => post.author })
58
+ posts?: Post[];
59
+ }
60
+
61
+ @Entity()
62
+ export class Post {
63
+ @Id({ type: Number })
64
+ id?: number;
65
+
66
+ @Field({ type: String })
67
+ title?: string | null;
68
+
69
+ @Field({ references: () => User })
70
+ authorId?: string | null;
71
+
72
+ @ManyToOne({ entity: () => User, references: (post) => post.authorId })
73
+ author?: User;
74
+ }
75
+ ```
76
+
77
+ - Every `@Field` states its `type` (`String`, `Number`, `Boolean`, `Date`, `BigInt`, or a column type such as
78
+ `'uuid'`, `'text'`, `'jsonb'`), except a foreign key, which takes `references` and inherits the target key's type.
79
+ - A column is nullable unless it says `nullable: false`, and its property must admit `null` to match:
80
+ `title?: string | null`. A property typed without `| null` on a nullable column is a compile error.
81
+ - Members are named by callbacks, never by strings: `mappedBy: (post) => post.author`, `references: (post) => post.authorId`.
82
+ - `@ManyToMany({ entity: () => Tag, through: () => PostTag })` names its junction entity.
83
+ - `defineEntity` defines the same entity without decorators: https://uql-orm.dev/entities/imperative.md
84
+
85
+ ## Queries
86
+
87
+ ```ts
88
+ import { pool } from './uql.config.js';
89
+ import { User } from './entities.js';
90
+
91
+ const users = await pool.findMany(User, {
92
+ $select: { id: true, email: true },
93
+ $where: { email: { $endsWith: '@uql-orm.dev' }, $or: [{ id: 'a' }, { id: 'b' }] },
94
+ $populate: { posts: { $select: { title: true }, $sort: { id: 'desc' }, $limit: 5 } },
95
+ $sort: { email: 'asc' },
96
+ $skip: 0,
97
+ $limit: 20,
98
+ });
99
+ ```
100
+
101
+ - The keys are `$select`, `$exclude`, `$where`, `$populate`, `$sort`, `$skip`, `$limit`.
102
+ - `$where` takes a value for equality or an operator map: `$eq`, `$ne`, `$lt`, `$lte`, `$gt`, `$gte`, `$in`,
103
+ `$nin`, `$between`, `$like`, `$ilike`, `$regex`, `$startsWith`, `$endsWith`, `$includes`, `$isNull`,
104
+ `$isNotNull`. `$and`, `$or`, `$not` and `$nor` combine clauses.
105
+ - A result is narrowed to what the query selected and populated: reading an unselected field is a compile error.
106
+ Name that shape with `QueryFindResult<User, 'id' | 'email'>` rather than widening the query.
107
+ - `$populate` loads relations in the same statement. Nothing is lazy: a relation not populated is not there.
108
+ - A query is plain data, so it can be built dynamically, stored, or sent from a browser to `uql-orm/http`.
109
+ - Methods: `findMany`, `findOne`, `findOneById`, `findManyAndCount`, `findManyStream`, `count`, `exists`,
110
+ `aggregate`, `insertOne`, `insertMany`, `updateOneById`, `updateMany`, `saveOne`, `saveMany`, `upsertOne`,
111
+ `upsertMany`, `deleteOneById`, `deleteMany`. Each takes the entity class first.
112
+ - `updateMany` and `deleteMany` with no `$where` throw; `{ unfiltered: true }` means the whole table.
113
+ - `raw()` embeds SQL anywhere a value or field goes; `pool.all(sql, values)` runs a raw `SELECT`.
114
+
115
+ ## Connections and transactions
116
+
117
+ Every method is on both the pool and a querier. A pool call acquires a connection for that call and releases it.
118
+ To run several operations on one connection, or atomically, hold a querier:
119
+
120
+ ```ts
121
+ await pool.transaction(async (querier) => {
122
+ const userId = await querier.insertOne(User, { email: 'ada@uql-orm.dev' });
123
+ await querier.insertOne(Post, { title: 'Hello', authorId: userId });
124
+ });
125
+ ```
126
+
127
+ Inside the callback, call `querier`, never `pool`: a `pool` call runs on another connection, outside the
128
+ transaction. A querier from `pool.getQuerier()` is yours to release: bind it with `await using`.
129
+
130
+ ## Migrations
131
+
132
+ `npx uql-migrate` reads `uql.config.ts`. `sync` creates what the entities imply (development only);
133
+ `generate:entities` writes the diff as a migration file to review; `up` applies migrations; `generate:from-db`
134
+ writes entity classes from an existing database; `drift:check` fails when the database no longer matches.
135
+
136
+ ## Where to read more
137
+
138
+ - Operators, per-dialect SQL: https://uql-orm.dev/querying/comparison-operators.md
139
+ - Relations and deep `$populate`: https://uql-orm.dev/querying/relations.md
140
+ - Every method's signature: https://uql-orm.dev/querying/methods.md
141
+ - Coming from Prisma, Drizzle, TypeORM or MikroORM: https://uql-orm.dev/switching-to-uql.md
142
+ - Breaking changes by version: https://uql-orm.dev/upgrade-guide.md