uql-orm 0.21.0 → 0.23.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 (214) hide show
  1. package/README.md +5 -4
  2. package/dist/browser/uql-browser.min.js.map +3 -3
  3. package/dist/bunSql/bunSqlCockroachDialect.d.ts +2 -2
  4. package/dist/bunSql/bunSqlCockroachDialect.js +4 -10
  5. package/dist/bunSql/bunSqlPostgresDialect.d.ts +2 -2
  6. package/dist/bunSql/bunSqlPostgresDialect.js +4 -10
  7. package/dist/cockroachdb/cockroachDialect.d.ts +20 -4
  8. package/dist/cockroachdb/cockroachDialect.js +33 -18
  9. package/dist/d1/d1Querier.d.ts +4 -3
  10. package/dist/d1/d1Querier.js +0 -3
  11. package/dist/d1/d1SqliteDialect.d.ts +7 -0
  12. package/dist/d1/d1SqliteDialect.js +9 -0
  13. package/dist/dialect/abstractDialect.d.ts +8 -3
  14. package/dist/dialect/abstractDialect.js +9 -4
  15. package/dist/dialect/abstractSqlDialect.d.ts +49 -8
  16. package/dist/dialect/abstractSqlDialect.js +95 -47
  17. package/dist/dialect/index.d.ts +1 -0
  18. package/dist/dialect/index.js +1 -0
  19. package/dist/dialect/indexSqlDialect.d.ts +57 -0
  20. package/dist/dialect/indexSqlDialect.js +101 -0
  21. package/dist/dialect/mysqlLikeSqlDialect.d.ts +26 -1
  22. package/dist/dialect/mysqlLikeSqlDialect.js +57 -1
  23. package/dist/dialect/pgLikeSqlDialect.d.ts +37 -22
  24. package/dist/dialect/pgLikeSqlDialect.js +97 -68
  25. package/dist/dialect/vectorCast.d.ts +21 -0
  26. package/dist/dialect/vectorCast.js +28 -0
  27. package/dist/dialect/vectorSqlDialect.d.ts +10 -3
  28. package/dist/dialect/vectorSqlDialect.js +19 -8
  29. package/dist/entity/decorator/bag.d.ts +35 -0
  30. package/dist/entity/decorator/bag.js +54 -0
  31. package/dist/entity/decorator/entity.d.ts +28 -2
  32. package/dist/entity/decorator/entity.js +40 -2
  33. package/dist/entity/decorator/members.d.ts +51 -0
  34. package/dist/entity/decorator/members.js +51 -0
  35. package/dist/entity/index.d.ts +3 -1
  36. package/dist/entity/index.js +3 -1
  37. package/dist/entity/metadata/definition.d.ts +23 -8
  38. package/dist/entity/metadata/definition.js +56 -100
  39. package/dist/http/contract.js +1 -2
  40. package/dist/http/handler.js +5 -26
  41. package/dist/http/query.js +1 -1
  42. package/dist/libsql/libsqlDialect.d.ts +12 -2
  43. package/dist/libsql/libsqlDialect.js +14 -2
  44. package/dist/libsql/libsqlQuerier.d.ts +9 -22
  45. package/dist/libsql/libsqlQuerier.js +9 -85
  46. package/dist/libsql/libsqlQuerierPool.d.ts +10 -8
  47. package/dist/libsql/libsqlQuerierPool.js +17 -15
  48. package/dist/maria/mariaDialect.d.ts +26 -4
  49. package/dist/maria/mariaDialect.js +54 -15
  50. package/dist/migrate/acquireQuerierForMigrations.d.ts +13 -1
  51. package/dist/migrate/acquireQuerierForMigrations.js +28 -0
  52. package/dist/migrate/builder/migrationBuilder.d.ts +9 -29
  53. package/dist/migrate/builder/migrationBuilder.js +58 -76
  54. package/dist/migrate/builder/splitSqlStatements.d.ts +0 -2
  55. package/dist/migrate/builder/splitSqlStatements.js +0 -2
  56. package/dist/migrate/builder/tableBuilder.d.ts +12 -2
  57. package/dist/migrate/builder/tableBuilder.js +71 -119
  58. package/dist/migrate/builder/types.d.ts +30 -41
  59. package/dist/migrate/cli-config.js +24 -8
  60. package/dist/migrate/cli.d.ts +3 -2
  61. package/dist/migrate/cli.js +26 -49
  62. package/dist/migrate/codegen/entityCodeGenerator.d.ts +0 -4
  63. package/dist/migrate/codegen/entityCodeGenerator.js +8 -67
  64. package/dist/migrate/codegen/fieldOptionsSource.d.ts +10 -0
  65. package/dist/migrate/codegen/fieldOptionsSource.js +55 -0
  66. package/dist/migrate/codegen/index.d.ts +0 -3
  67. package/dist/migrate/codegen/index.js +0 -6
  68. package/dist/migrate/drift/driftDetector.d.ts +10 -1
  69. package/dist/migrate/drift/driftDetector.js +22 -5
  70. package/dist/migrate/generator/indexNodeToSchema.d.ts +9 -0
  71. package/dist/migrate/generator/indexNodeToSchema.js +14 -0
  72. package/dist/migrate/generator/mongoCommand.d.ts +57 -0
  73. package/dist/migrate/generator/mongoCommand.js +26 -0
  74. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +9 -20
  75. package/dist/migrate/generator/mongoSchemaGenerator.js +40 -82
  76. package/dist/migrate/index.d.ts +0 -1
  77. package/dist/migrate/index.js +0 -1
  78. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +30 -11
  79. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +61 -57
  80. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +0 -1
  81. package/dist/migrate/introspection/baseSqlIntrospector.js +3 -12
  82. package/dist/migrate/introspection/mongoIntrospector.js +9 -15
  83. package/dist/migrate/introspection/mysqlIntrospector.d.ts +5 -8
  84. package/dist/migrate/introspection/mysqlIntrospector.js +4 -10
  85. package/dist/migrate/introspection/postgresIntrospector.d.ts +5 -8
  86. package/dist/migrate/introspection/postgresIntrospector.js +4 -10
  87. package/dist/migrate/introspection/sqliteIntrospector.d.ts +6 -5
  88. package/dist/migrate/introspection/sqliteIntrospector.js +26 -18
  89. package/dist/migrate/migrator.d.ts +23 -3
  90. package/dist/migrate/migrator.js +101 -150
  91. package/dist/migrate/schemaGenerator.d.ts +26 -37
  92. package/dist/migrate/schemaGenerator.js +69 -174
  93. package/dist/migrate/schemaGeneratorAsync.d.ts +2 -2
  94. package/dist/migrate/schemaGeneratorAsync.js +3 -3
  95. package/dist/migrate/storage/databaseStorage.d.ts +2 -0
  96. package/dist/migrate/storage/databaseStorage.js +12 -27
  97. package/dist/mongo/mongoDialect.d.ts +2 -0
  98. package/dist/mongo/mongoDialect.js +14 -3
  99. package/dist/mongo/mongodbQuerier.js +171 -229
  100. package/dist/mysql/mysqlDialect.d.ts +8 -0
  101. package/dist/mysql/mysqlDialect.js +13 -0
  102. package/dist/nestjs/uqlContextInterceptor.js +58 -22
  103. package/dist/nestjs/uqlModule.d.ts +2 -11
  104. package/dist/nestjs/uqlModule.js +93 -44
  105. package/dist/postgres/postgresDialect.js +2 -2
  106. package/dist/querier/abstractQuerier.d.ts +28 -0
  107. package/dist/querier/abstractQuerier.js +47 -22
  108. package/dist/querier/abstractQuerierPool.d.ts +3 -0
  109. package/dist/querier/abstractQuerierPool.js +5 -3
  110. package/dist/querier/abstractSqlQuerier.d.ts +1 -3
  111. package/dist/querier/abstractSqlQuerier.js +48 -96
  112. package/dist/querier/index.d.ts +3 -1
  113. package/dist/querier/index.js +3 -1
  114. package/dist/querier/querierContext.browser.d.ts +12 -0
  115. package/dist/querier/querierContext.browser.js +18 -0
  116. package/dist/querier/querierContext.d.ts +22 -0
  117. package/dist/querier/querierContext.js +42 -0
  118. package/dist/querier/queryError.d.ts +22 -0
  119. package/dist/querier/queryError.js +20 -0
  120. package/dist/querier/transactional.d.ts +26 -0
  121. package/dist/querier/transactional.js +43 -0
  122. package/dist/schema/canonicalType.d.ts +14 -2
  123. package/dist/schema/canonicalType.js +41 -31
  124. package/dist/schema/index.d.ts +1 -1
  125. package/dist/schema/schemaASTBuilder.d.ts +9 -2
  126. package/dist/schema/schemaASTBuilder.js +58 -49
  127. package/dist/schema/types.d.ts +10 -58
  128. package/dist/sqlite/abstractSqliteQuerier.d.ts +46 -0
  129. package/dist/sqlite/abstractSqliteQuerier.js +44 -0
  130. package/dist/sqlite/bunSqliteAdapter.bun.d.ts +26 -0
  131. package/dist/sqlite/bunSqliteAdapter.bun.js +25 -0
  132. package/dist/sqlite/hranaQuerier.d.ts +54 -0
  133. package/dist/sqlite/hranaQuerier.js +68 -0
  134. package/dist/sqlite/hranaQuerierPool.d.ts +21 -0
  135. package/dist/sqlite/hranaQuerierPool.js +25 -0
  136. package/dist/sqlite/index.d.ts +3 -1
  137. package/dist/sqlite/index.js +3 -1
  138. package/dist/sqlite/localSqliteQuerierPool.d.ts +31 -0
  139. package/dist/sqlite/localSqliteQuerierPool.js +34 -0
  140. package/dist/sqlite/nodeSqliteAdapter.d.ts +35 -0
  141. package/dist/sqlite/nodeSqliteAdapter.js +28 -0
  142. package/dist/sqlite/nodeSqliteQuerierPool.d.ts +28 -0
  143. package/dist/sqlite/nodeSqliteQuerierPool.js +29 -0
  144. package/dist/sqlite/sqliteDialect.d.ts +18 -5
  145. package/dist/sqlite/sqliteDialect.js +23 -36
  146. package/dist/sqlite/sqliteQuerier.d.ts +16 -9
  147. package/dist/sqlite/sqliteQuerier.js +4 -27
  148. package/dist/sqlite/sqliteQuerierPool.d.ts +11 -14
  149. package/dist/sqlite/sqliteQuerierPool.js +16 -24
  150. package/dist/turso/index.d.ts +3 -0
  151. package/dist/turso/index.js +3 -0
  152. package/dist/turso/local.d.ts +3 -0
  153. package/dist/turso/local.js +3 -0
  154. package/dist/turso/tursoDialect.d.ts +15 -0
  155. package/dist/turso/tursoDialect.js +18 -0
  156. package/dist/turso/tursoLocalQuerier.d.ts +25 -0
  157. package/dist/turso/tursoLocalQuerier.js +20 -0
  158. package/dist/turso/tursoLocalQuerierPool.d.ts +32 -0
  159. package/dist/turso/tursoLocalQuerierPool.js +39 -0
  160. package/dist/turso/tursoQuerier.d.ts +10 -0
  161. package/dist/turso/tursoQuerier.js +10 -0
  162. package/dist/turso/tursoQuerierPool.d.ts +39 -0
  163. package/dist/turso/tursoQuerierPool.js +38 -0
  164. package/dist/type/config.d.ts +0 -6
  165. package/dist/type/dialect.d.ts +12 -4
  166. package/dist/type/dialect.js +7 -1
  167. package/dist/type/entity.d.ts +154 -26
  168. package/dist/type/migration.d.ts +32 -23
  169. package/dist/type/querier.d.ts +6 -0
  170. package/dist/type/vector.d.ts +5 -2
  171. package/dist/util/hook.util.js +1 -1
  172. package/dist/util/index.d.ts +1 -0
  173. package/dist/util/index.js +1 -0
  174. package/dist/util/indexColumn.util.d.ts +6 -0
  175. package/dist/util/indexColumn.util.js +26 -0
  176. package/package.json +21 -16
  177. package/dist/entity/decorator/field.d.ts +0 -2
  178. package/dist/entity/decorator/field.js +0 -7
  179. package/dist/entity/decorator/filter.d.ts +0 -7
  180. package/dist/entity/decorator/filter.js +0 -11
  181. package/dist/entity/decorator/hook.d.ts +0 -7
  182. package/dist/entity/decorator/hook.js +0 -14
  183. package/dist/entity/decorator/id.d.ts +0 -2
  184. package/dist/entity/decorator/id.js +0 -7
  185. package/dist/entity/decorator/index-decorator.d.ts +0 -29
  186. package/dist/entity/decorator/index-decorator.js +0 -26
  187. package/dist/entity/decorator/index.d.ts +0 -8
  188. package/dist/entity/decorator/index.js +0 -8
  189. package/dist/entity/decorator/relation.d.ts +0 -8
  190. package/dist/entity/decorator/relation.js +0 -19
  191. package/dist/migrate/codegen/entityMerger.d.ts +0 -114
  192. package/dist/migrate/codegen/entityMerger.js +0 -293
  193. package/dist/migrate/codegen/migrationCodeGenerator.d.ts +0 -61
  194. package/dist/migrate/codegen/migrationCodeGenerator.js +0 -355
  195. package/dist/migrate/codegen/smartRelationDetector.d.ts +0 -47
  196. package/dist/migrate/codegen/smartRelationDetector.js +0 -134
  197. package/dist/migrate/generator/index.d.ts +0 -2
  198. package/dist/migrate/generator/index.js +0 -2
  199. package/dist/migrate/sync/index.d.ts +0 -6
  200. package/dist/migrate/sync/index.js +0 -6
  201. package/dist/migrate/sync/schemaSync.d.ts +0 -131
  202. package/dist/migrate/sync/schemaSync.js +0 -259
  203. package/dist/querier/decorator/index.d.ts +0 -4
  204. package/dist/querier/decorator/index.js +0 -4
  205. package/dist/querier/decorator/injectQuerier.d.ts +0 -3
  206. package/dist/querier/decorator/injectQuerier.js +0 -33
  207. package/dist/querier/decorator/log.d.ts +0 -32
  208. package/dist/querier/decorator/log.js +0 -52
  209. package/dist/querier/decorator/serialized.d.ts +0 -6
  210. package/dist/querier/decorator/serialized.js +0 -13
  211. package/dist/querier/decorator/transactional.d.ts +0 -6
  212. package/dist/querier/decorator/transactional.js +0 -48
  213. package/dist/sqlite/betterSqlite3Dialect.d.ts +0 -9
  214. package/dist/sqlite/betterSqlite3Dialect.js +0 -9
@@ -0,0 +1,42 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ /**
3
+ * The querier `@Transactional()` opened for the current async flow.
4
+ *
5
+ * Separate from the `UqlContext` storage in `context/context.ts` on purpose: that one is remapped to a
6
+ * synchronous browser shim which cannot propagate across `await`, and a querier handle must. Nothing in
7
+ * a browser bundle opens a transaction, so this stays server-only and `verify-dist` keeps it out of the
8
+ * browser-facing graph.
9
+ */
10
+ const querierStorage = new AsyncLocalStorage();
11
+ /** Runs `callback` with `querier` as the ambient one, for the whole async flow beneath it. */
12
+ export function withQuerierContext(querier, callback) {
13
+ return querierStorage.run(querier, callback);
14
+ }
15
+ /**
16
+ * The querier of the enclosing `@Transactional()` method.
17
+ *
18
+ * This is what replaced `@InjectQuerier()`: the standard decorator spec has no parameter decorators, so
19
+ * the querier can no longer be injected into an argument and is read from the ambient flow instead.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * class UserService {
24
+ * @Transactional()
25
+ * async register(data: Partial<User>) {
26
+ * await currentQuerier().insertOne(User, data);
27
+ * }
28
+ * }
29
+ * ```
30
+ */
31
+ export function currentQuerier() {
32
+ const querier = querierStorage.getStore();
33
+ if (!querier) {
34
+ throw new TypeError('currentQuerier() found no active querier. Call it inside a @Transactional() method, or take a querier ' +
35
+ 'from the pool yourself with `await using querier = await pool.getQuerier()`.');
36
+ }
37
+ return querier;
38
+ }
39
+ /** The ambient querier, or `undefined` outside a transaction. For callers that can work without one. */
40
+ export function currentQuerierIfAny() {
41
+ return querierStorage.getStore();
42
+ }
@@ -0,0 +1,22 @@
1
+ import type { LoggerWrapper } from '../util/logger.js';
2
+ /**
3
+ * A driver error enriched with context by {@link enrichError}. `query` is always attached; `values`
4
+ * only when the querier's logger is configured to surface them (see {@link enrichError}) - they can
5
+ * carry sensitive data (PII, tokens, etc.) and would otherwise leak into whatever error-tracking
6
+ * pipeline (Sentry, console.error, ...) serializes the error, without the developer opting in.
7
+ */
8
+ export interface QueryError extends Error {
9
+ query?: string;
10
+ values?: unknown[];
11
+ }
12
+ /**
13
+ * Tags `err` with the query it failed on (as {@link QueryError}) and hands it back for the caller to
14
+ * throw. `values` is only attached when `logger?.willLogValues()` is true, i.e. the app already has
15
+ * query values surfacing somewhere (query-level or slow-query logging), so attaching them here does not
16
+ * introduce a new leak surface. Shared by every query call site (timed queries, streams, transaction
17
+ * statements) so the logic lives in one place.
18
+ *
19
+ * Returns rather than throws so `throw enrichError(...)` reads as the control flow it is, which also
20
+ * means callers need no `never` annotation and no unreachable-code suppression.
21
+ */
22
+ export declare function enrichError(err: unknown, logger: LoggerWrapper | undefined, query: string, values?: unknown[]): unknown;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Tags `err` with the query it failed on (as {@link QueryError}) and hands it back for the caller to
3
+ * throw. `values` is only attached when `logger?.willLogValues()` is true, i.e. the app already has
4
+ * query values surfacing somewhere (query-level or slow-query logging), so attaching them here does not
5
+ * introduce a new leak surface. Shared by every query call site (timed queries, streams, transaction
6
+ * statements) so the logic lives in one place.
7
+ *
8
+ * Returns rather than throws so `throw enrichError(...)` reads as the control flow it is, which also
9
+ * means callers need no `never` annotation and no unreachable-code suppression.
10
+ */
11
+ export function enrichError(err, logger, query, values) {
12
+ if (err instanceof Error) {
13
+ const queryError = err;
14
+ queryError.query ??= query;
15
+ if (values !== undefined && logger?.willLogValues()) {
16
+ queryError.values ??= values;
17
+ }
18
+ }
19
+ return err;
20
+ }
@@ -0,0 +1,26 @@
1
+ import type { IsolationLevel, QuerierPool } from '../type/index.js';
2
+ export type TransactionalOptions = {
3
+ /** `required` opens a transaction when none is active; `supported` joins one but never starts one. */
4
+ readonly propagation?: 'supported' | 'required';
5
+ readonly pool?: QuerierPool;
6
+ readonly isolationLevel?: IsolationLevel;
7
+ };
8
+ /**
9
+ * Wraps the method in a transaction and publishes its querier for {@link currentQuerier} to pick up.
10
+ *
11
+ * @remarks Replaces the `@InjectQuerier()` parameter that used to receive the querier. The standard
12
+ * decorator spec has no parameter decorators, and the separate TC39 proposal for them is still Stage 1,
13
+ * so the querier travels through async-local storage instead. A nested call joins the transaction
14
+ * already in flight rather than opening a second one.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * class UserService {
19
+ * @Transactional()
20
+ * async register(data: Partial<User>) {
21
+ * await currentQuerier().insertOne(User, data);
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ export declare function Transactional({ propagation, pool, isolationLevel }?: TransactionalOptions): <This, Args extends unknown[], R>(original: (this: This, ...args: Args) => Promise<R>, context: ClassMethodDecoratorContext<This>) => (this: This, ...args: Args) => Promise<R>;
@@ -0,0 +1,43 @@
1
+ import { getQuerierPool } from '../options.js';
2
+ import { currentQuerierIfAny, withQuerierContext } from './querierContext.js';
3
+ /**
4
+ * Wraps the method in a transaction and publishes its querier for {@link currentQuerier} to pick up.
5
+ *
6
+ * @remarks Replaces the `@InjectQuerier()` parameter that used to receive the querier. The standard
7
+ * decorator spec has no parameter decorators, and the separate TC39 proposal for them is still Stage 1,
8
+ * so the querier travels through async-local storage instead. A nested call joins the transaction
9
+ * already in flight rather than opening a second one.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * class UserService {
14
+ * @Transactional()
15
+ * async register(data: Partial<User>) {
16
+ * await currentQuerier().insertOne(User, data);
17
+ * }
18
+ * }
19
+ * ```
20
+ */
21
+ export function Transactional({ propagation = 'required', pool, isolationLevel } = {}) {
22
+ return (original, context) => {
23
+ // Checked at decoration time rather than on the first call: a synchronous method cannot be wrapped in
24
+ // a transaction, and finding that out at startup beats finding out mid-request.
25
+ if (original.constructor.name !== 'AsyncFunction') {
26
+ throw new TypeError(`@Transactional() needs an async method, but '${String(context.name)}' is not one.`);
27
+ }
28
+ return async function (...args) {
29
+ // Already inside a transactional flow: join it and let the outermost call own commit and release.
30
+ if (currentQuerierIfAny()) {
31
+ return original.apply(this, args);
32
+ }
33
+ // `withQuerier` releases; `transaction` commits or rolls back. `supported` joins a transaction but
34
+ // never starts one, so it takes only the first half.
35
+ return (pool ?? getQuerierPool()).withQuerier((querier) => {
36
+ const run = () => original.apply(this, args);
37
+ return withQuerierContext(querier, () => propagation === 'supported'
38
+ ? run()
39
+ : querier.transaction(run, isolationLevel ? { isolationLevel } : undefined));
40
+ });
41
+ };
42
+ };
43
+ }
@@ -7,15 +7,27 @@
7
7
  * - TypeScript types (for entity generation)
8
8
  */
9
9
  import type { AbstractDialect } from '../dialect/abstractDialect.js';
10
+ import type { VectorCast } from '../dialect/vectorCast.js';
10
11
  import type { ColumnType, FieldOptions } from '../type/entity.js';
11
12
  import type { CanonicalType, TypeCategory } from './types.js';
12
- /** Type guard for all vector-family categories. */
13
- export declare function isVectorCategory(category: TypeCategory): boolean;
13
+ /** Whether a category is one of the vector types, narrowing it to the cast pgvector names use. */
14
+ export declare function isVectorCategory(category: TypeCategory | undefined): category is VectorCast;
14
15
  /**
15
16
  * Parse a SQL type string into a canonical type.
16
17
  * Handles complex types like VARCHAR(255), DECIMAL(10,2), etc.
17
18
  */
18
19
  export declare function sqlToCanonical(sqlType: string): CanonicalType;
20
+ /**
21
+ * The canonical type for a column an engine reported, merging the metadata columns it reports beside
22
+ * the type name (`character_maximum_length` and friends) over whatever the name itself carried. This is
23
+ * the one place a SQL type string is parsed: everything downstream compares and renders canonical
24
+ * types, so no consumer has to know that `TINYINT(1)` means boolean on MySQL.
25
+ */
26
+ export declare function canonicalColumnType(sqlType: string, reported?: {
27
+ length?: number;
28
+ precision?: number;
29
+ scale?: number;
30
+ }): CanonicalType;
19
31
  /**
20
32
  * Convert a canonical type to a SQL type string for a specific dialect instance.
21
33
  */
@@ -9,7 +9,7 @@
9
9
  // ============================================================================
10
10
  // Vector Category Helpers
11
11
  // ============================================================================
12
- /** Type guard for all vector-family categories. */
12
+ /** Whether a category is one of the vector types, narrowing it to the cast pgvector names use. */
13
13
  export function isVectorCategory(category) {
14
14
  return category === 'vector' || category === 'halfvec' || category === 'sparsevec';
15
15
  }
@@ -86,7 +86,15 @@ const SQL_TO_CANONICAL = {
86
86
  halfvec: { category: 'halfvec' },
87
87
  sparsevec: { category: 'sparsevec' },
88
88
  };
89
- const PG_TYPE_MAP = {
89
+ /**
90
+ * pgvector is the only engine with three vector column types, so every other dialect maps all three
91
+ * canonical categories onto the single type it does have (see {@link MULTI_VECTOR_TYPE_DIALECTS}, the
92
+ * dialect-side half of the same fact).
93
+ */
94
+ function withVectorType(scalars, vector) {
95
+ return { ...scalars, vector, halfvec: vector, sparsevec: vector };
96
+ }
97
+ const PG_SCALAR_MAP = {
90
98
  integer: 'INTEGER',
91
99
  float: 'REAL',
92
100
  decimal: 'NUMERIC',
@@ -98,22 +106,18 @@ const PG_TYPE_MAP = {
98
106
  json: 'JSONB',
99
107
  uuid: 'UUID',
100
108
  blob: 'BYTEA',
109
+ };
110
+ const PG_TYPE_MAP = {
111
+ ...PG_SCALAR_MAP,
101
112
  vector: 'VECTOR',
102
113
  halfvec: 'HALFVEC',
103
114
  sparsevec: 'SPARSEVEC',
104
115
  };
105
- // CockroachDB's VECTOR type is native (no extension), but it has no HALFVEC/SPARSEVEC types at
106
- // all - verified live: `CREATE TABLE t (v HALFVEC(3))` is a syntax error, not just unsupported at
107
- // runtime. Falls back to VECTOR, same as MariaDB (see its own halfvec/sparsevec below).
108
- const CRDB_TYPE_MAP = {
109
- ...PG_TYPE_MAP,
110
- halfvec: 'VECTOR',
111
- sparsevec: 'VECTOR',
112
- };
113
116
  const CANONICAL_TO_SQL = {
114
117
  postgres: PG_TYPE_MAP,
115
- cockroachdb: CRDB_TYPE_MAP,
116
- mysql: {
118
+ // CockroachDB's VECTOR is native, no extension needed.
119
+ cockroachdb: withVectorType(PG_SCALAR_MAP, 'VECTOR'),
120
+ mysql: withVectorType({
117
121
  integer: 'INT',
118
122
  float: 'FLOAT',
119
123
  decimal: 'DECIMAL',
@@ -125,11 +129,11 @@ const CANONICAL_TO_SQL = {
125
129
  json: 'JSON',
126
130
  uuid: 'CHAR(36)',
127
131
  blob: 'BLOB',
128
- vector: 'JSON', // MySQL doesn't have native vector type
129
- halfvec: 'JSON',
130
- sparsevec: 'JSON',
131
- },
132
- sqlite: {
132
+ },
133
+ // MySQL 9.x does have a `VECTOR` type, but no distance function outside HeatWave and no vector
134
+ // index, so JSON keeps the column queryable with the JSON operators and needs no conversion.
135
+ 'JSON'),
136
+ sqlite: withVectorType({
133
137
  integer: 'INTEGER',
134
138
  float: 'REAL',
135
139
  decimal: 'REAL',
@@ -141,11 +145,8 @@ const CANONICAL_TO_SQL = {
141
145
  json: 'TEXT',
142
146
  uuid: 'TEXT',
143
147
  blob: 'BLOB',
144
- vector: 'TEXT',
145
- halfvec: 'TEXT',
146
- sparsevec: 'TEXT',
147
- },
148
- mariadb: {
148
+ }, 'TEXT'),
149
+ mariadb: withVectorType({
149
150
  integer: 'INT',
150
151
  float: 'FLOAT',
151
152
  decimal: 'DECIMAL',
@@ -157,12 +158,9 @@ const CANONICAL_TO_SQL = {
157
158
  json: 'JSON',
158
159
  uuid: 'CHAR(36)',
159
160
  blob: 'BLOB',
160
- vector: 'VECTOR',
161
- halfvec: 'VECTOR', // MariaDB only supports VECTOR; no native halfvec/sparsevec
162
- sparsevec: 'VECTOR',
163
- },
161
+ }, 'VECTOR'),
164
162
  // MongoDB uses BSON types, not SQL types. These are placeholders for compatibility.
165
- mongodb: {
163
+ mongodb: withVectorType({
166
164
  integer: 'int',
167
165
  float: 'double',
168
166
  decimal: 'decimal128',
@@ -174,10 +172,7 @@ const CANONICAL_TO_SQL = {
174
172
  json: 'object',
175
173
  uuid: 'binData',
176
174
  blob: 'binData',
177
- vector: 'array',
178
- halfvec: 'array',
179
- sparsevec: 'array',
180
- },
175
+ }, 'array'),
181
176
  };
182
177
  /**
183
178
  * Size variant modifiers for SQL types.
@@ -318,6 +313,21 @@ export function sqlToCanonical(sqlType) {
318
313
  }
319
314
  return result;
320
315
  }
316
+ /**
317
+ * The canonical type for a column an engine reported, merging the metadata columns it reports beside
318
+ * the type name (`character_maximum_length` and friends) over whatever the name itself carried. This is
319
+ * the one place a SQL type string is parsed: everything downstream compares and renders canonical
320
+ * types, so no consumer has to know that `TINYINT(1)` means boolean on MySQL.
321
+ */
322
+ export function canonicalColumnType(sqlType, reported = {}) {
323
+ const base = sqlToCanonical(sqlType);
324
+ return {
325
+ ...base,
326
+ length: reported.length ?? base.length,
327
+ precision: reported.precision ?? base.precision,
328
+ scale: reported.scale ?? base.scale,
329
+ };
330
+ }
321
331
  /**
322
332
  * Convert a canonical type to a SQL type string for a specific dialect instance.
323
333
  */
@@ -22,4 +22,4 @@ export type { BuildFromEntitiesOptions } from './schemaASTBuilder.js';
22
22
  export { SchemaASTBuilder } from './schemaASTBuilder.js';
23
23
  export type { DiffOptions } from './schemaASTDiffer.js';
24
24
  export { diffSchemas, SchemaASTDiffer } from './schemaASTDiffer.js';
25
- export type { CanonicalType, ColumnDiff, ColumnNode, ConflictResolution, DetectedRelation, Drift, DriftReport, DriftSeverity, DriftStatus, DriftType, ForeignKeyAction, IndexDiff, IndexNode, IndexSource, IndexSyncStatus, IndexType, RelationshipDiff, RelationshipNode, RelationshipSource, RelationshipType, SchemaAST as ISchemaAST, SchemaDiffResult, SizeVariant, SyncDirection, SyncOptions, TableDiff, TableNode, TypeCategory, ValidationError, ValidationErrorType, } from './types.js';
25
+ export type { CanonicalType, ColumnDiff, ColumnNode, Drift, DriftReport, DriftSeverity, DriftStatus, DriftType, ForeignKeyAction, IndexDiff, IndexNode, IndexSource, IndexSyncStatus, IndexType, RelationshipDiff, RelationshipNode, RelationshipSource, RelationshipType, SchemaAST as ISchemaAST, SchemaDiffResult, SizeVariant, TableDiff, TableNode, TypeCategory, ValidationError, ValidationErrorType, } from './types.js';
@@ -54,7 +54,7 @@ export declare class SchemaASTBuilder {
54
54
  * foreign key column whose type doesn't match the UUID primary key it
55
55
  * references, which Postgres (and most databases) reject outright.
56
56
  *
57
- * `field.typeInferred` (set by `defineField`, see entity/metadata/definition.ts)
57
+ * `field.typeFromReference` (set by `defineField`, see entity/metadata/definition.ts)
58
58
  * is what distinguishes "no type was given" from "the decorator explicitly set
59
59
  * a type" - including explicit constructor overrides like `type: BigInt`, which
60
60
  * a value-based check (e.g. `typeof field.type === 'string'`) would miss since
@@ -71,7 +71,14 @@ export declare class SchemaASTBuilder {
71
71
  */
72
72
  private addRelationshipsFromEntity;
73
73
  /**
74
- * Add indexes from field options.
74
+ * Add indexes from field options (`@Field({ index })`) and from `@Index([...])`, which have nothing
75
+ * in common beyond their target table.
75
76
  */
76
77
  private addIndexesFromEntity;
78
+ /**
79
+ * One `@Index([...])`. Its entries keep the authored form (expression, prefix length, order) with
80
+ * names resolved, so the generator renders exactly what was declared; `columns` is the resolvable
81
+ * subset, which is what diffing and introspection compare.
82
+ */
83
+ private addCompositeIndex;
77
84
  }
@@ -74,7 +74,7 @@ export class SchemaASTBuilder {
74
74
  * foreign key column whose type doesn't match the UUID primary key it
75
75
  * references, which Postgres (and most databases) reject outright.
76
76
  *
77
- * `field.typeInferred` (set by `defineField`, see entity/metadata/definition.ts)
77
+ * `field.typeFromReference` (set by `defineField`, see entity/metadata/definition.ts)
78
78
  * is what distinguishes "no type was given" from "the decorator explicitly set
79
79
  * a type" - including explicit constructor overrides like `type: BigInt`, which
80
80
  * a value-based check (e.g. `typeof field.type === 'string'`) would miss since
@@ -82,7 +82,7 @@ export class SchemaASTBuilder {
82
82
  * `columnType` remains the unambiguous, always-respected explicit override.
83
83
  */
84
84
  resolveColumnCanonicalType(field, seen = new Set()) {
85
- const hasExplicitType = !!field.columnType || !field.typeInferred;
85
+ const hasExplicitType = !!field.columnType || !field.typeFromReference;
86
86
  if (!hasExplicitType && field.references && !seen.has(field.references)) {
87
87
  seen.add(field.references);
88
88
  const referencedMeta = getMeta(field.references());
@@ -160,8 +160,12 @@ export class SchemaASTBuilder {
160
160
  const relatedTable = this.ast.getTable(relatedTableName);
161
161
  if (!relatedTable)
162
162
  continue;
163
- // Only create FK for owning side (m1 and owner side of 11)
164
- if (relation.cardinality === 'm1' || (relation.cardinality === '11' && relation.references)) {
163
+ // Only the owning side gets the FK. `mappedBy` marks the inverse side of a one-to-one, whose
164
+ // `references` describe how to join back (its own primary key against the owner's FK column) -
165
+ // reading those as a foreign key emitted a reversed constraint (`User(id) REFERENCES
166
+ // user_profile(creatorId)`), which SQLite rejects outright as a foreign key mismatch.
167
+ const ownsForeignKey = relation.cardinality === 'm1' || (relation.cardinality === '11' && !!relation.references && !relation.mappedBy);
168
+ if (ownsForeignKey) {
165
169
  const references = relation.references ?? [{ local: `${key}Id`, foreign: relatedMeta.id }];
166
170
  const localPropName = references[0].local;
167
171
  const foreignPropName = references[0].foreign;
@@ -192,67 +196,72 @@ export class SchemaASTBuilder {
192
196
  }
193
197
  }
194
198
  /**
195
- * Add indexes from field options.
199
+ * Add indexes from field options (`@Field({ index })`) and from `@Index([...])`, which have nothing
200
+ * in common beyond their target table.
196
201
  */
197
202
  addIndexesFromEntity(entity, meta, resolveTableName, resolveColumnName) {
198
203
  const tableName = resolveTableName(entity, meta);
199
204
  const table = this.ast.getTable(tableName);
200
205
  if (!table)
201
206
  return;
202
- // 1. Single column indexes from @Field({ index: true })
203
- const indexFields = meta.fields;
204
- for (const key of Object.keys(indexFields)) {
205
- const field = indexFields[key];
207
+ for (const key of Object.keys(meta.fields)) {
208
+ const field = meta.fields[key];
206
209
  if (!field?.index)
207
210
  continue;
208
- const columnName = resolveColumnName(key, field);
209
- const column = table.columns.get(columnName);
211
+ const column = table.columns.get(resolveColumnName(key, field));
210
212
  if (!column)
211
213
  continue;
212
- const indexName = typeof field.index === 'string' ? field.index : `idx_${tableName}_${columnName}`;
213
- const indexNode = {
214
- name: indexName,
214
+ this.ast.addIndex({
215
+ name: typeof field.index === 'string' ? field.index : `idx_${tableName}_${column.name}`,
215
216
  table,
216
217
  columns: [column],
217
218
  unique: field.unique ?? false,
218
219
  source: 'entity',
219
220
  syncStatus: 'entity_only',
220
- };
221
- this.ast.addIndex(indexNode);
221
+ });
222
222
  }
223
- // 2. Composite indexes from @Index([...])
224
- if (meta.indexes) {
225
- for (const idxMeta of meta.indexes) {
226
- const columns = [];
227
- for (const propName of idxMeta.columns) {
228
- const field = meta.fields[propName];
229
- if (!field)
230
- continue;
231
- const colName = resolveColumnName(propName, field);
232
- const column = table.columns.get(colName);
233
- if (column) {
234
- columns.push(column);
235
- }
236
- }
237
- if (columns.length > 0) {
238
- const indexName = idxMeta.name ?? `idx_${tableName}_${columns.map((c) => c.name).join('_')}`;
239
- const indexNode = {
240
- name: indexName,
241
- table,
242
- columns,
243
- unique: idxMeta.unique ?? false,
244
- type: idxMeta.type,
245
- where: idxMeta.where,
246
- distance: idxMeta.distance,
247
- m: idxMeta.m,
248
- efConstruction: idxMeta.efConstruction,
249
- lists: idxMeta.lists,
250
- source: 'entity',
251
- syncStatus: 'entity_only',
252
- };
253
- this.ast.addIndex(indexNode);
254
- }
255
- }
223
+ for (const idxMeta of meta.indexes ?? []) {
224
+ this.addCompositeIndex(table, meta, idxMeta, resolveColumnName);
256
225
  }
257
226
  }
227
+ /**
228
+ * One `@Index([...])`. Its entries keep the authored form (expression, prefix length, order) with
229
+ * names resolved, so the generator renders exactly what was declared; `columns` is the resolvable
230
+ * subset, which is what diffing and introspection compare.
231
+ */
232
+ addCompositeIndex(table, meta, idxMeta, resolveColumnName) {
233
+ // An entry survives if it is an expression (nothing to resolve) or names a column that exists;
234
+ // an index left with none is dropped, the same as one naming only unknown columns always was.
235
+ const entries = idxMeta.columns
236
+ .map((entry) => {
237
+ if (entry.expression)
238
+ return entry;
239
+ const field = meta.fields[entry.column];
240
+ const column = field && resolveColumnName(entry.column, field);
241
+ return column && table.columns.has(column) ? { ...entry, column } : undefined;
242
+ })
243
+ .filter((entry) => entry !== undefined);
244
+ if (!entries.length)
245
+ return;
246
+ const columns = entries
247
+ .map((entry) => (entry.expression ? undefined : table.columns.get(entry.column)))
248
+ .filter((column) => column !== undefined);
249
+ const named = columns.length ? columns.map((column) => column.name) : entries.map((_, at) => `expr${at}`);
250
+ this.ast.addIndex({
251
+ name: idxMeta.name ?? `idx_${table.name}_${named.join('_')}`,
252
+ table,
253
+ columns,
254
+ entries,
255
+ include: idxMeta.include,
256
+ unique: idxMeta.unique ?? false,
257
+ type: idxMeta.type,
258
+ where: idxMeta.where,
259
+ distance: idxMeta.distance,
260
+ m: idxMeta.m,
261
+ efConstruction: idxMeta.efConstruction,
262
+ lists: idxMeta.lists,
263
+ source: 'entity',
264
+ syncStatus: 'entity_only',
265
+ });
266
+ }
258
267
  }
@@ -4,7 +4,8 @@
4
4
  * A unified graph representation of database schema with relationships as first-class citizens.
5
5
  * Enables reliable diffing, smart relation detection, and dialect-agnostic schema operations.
6
6
  */
7
- import type { VectorIndexOptions } from '../type/vector.js';
7
+ import type { IndexColumnSchema } from '../type/entity.js';
8
+ import type { IndexSchema } from '../type/migration.js';
8
9
  /**
9
10
  * Type categories universal across SQL dialects.
10
11
  * These represent logical/semantic types, not specific SQL types.
@@ -148,24 +149,21 @@ export interface RelationshipNode {
148
149
  * Index node in the schema graph.
149
150
  * Represents a database index on one or more columns.
150
151
  */
151
- export type IndexNode = {
152
- /** Index name */
153
- readonly name: string;
152
+ export type IndexNode = Omit<IndexSchema, 'columns'> & {
154
153
  /** Reference to the table this index belongs to */
155
154
  readonly table: TableNode;
156
- /** Columns included in the index (order matters) */
155
+ /** Columns included in the index (order matters). Empty for an index over expressions only. */
157
156
  readonly columns: ColumnNode[];
158
- /** Whether this is a unique index */
159
- readonly unique: boolean;
160
- /** Index algorithm/type */
161
- readonly type?: IndexType;
162
- /** Partial index condition (WHERE clause) */
163
- readonly where?: string;
157
+ /**
158
+ * The index as authored, with names resolved: expressions, prefix lengths and per-column order that
159
+ * a `ColumnNode` cannot represent. The generator renders these; `columns` is what diffing compares.
160
+ */
161
+ readonly entries?: readonly IndexColumnSchema[];
164
162
  /** Where this index was defined */
165
163
  readonly source?: IndexSource;
166
164
  /** Current sync status */
167
165
  readonly syncStatus?: IndexSyncStatus;
168
- } & VectorIndexOptions;
166
+ };
169
167
  /**
170
168
  * Root of the schema graph.
171
169
  * Contains all tables, relationships, and provides graph operations.
@@ -258,27 +256,6 @@ export interface ValidationError {
258
256
  readonly relationship?: RelationshipNode;
259
257
  readonly tables?: TableNode[];
260
258
  }
261
- /**
262
- * A detected/inferred relationship with confidence score.
263
- */
264
- export interface DetectedRelation {
265
- readonly type: RelationshipType;
266
- readonly from: {
267
- readonly table: TableNode;
268
- readonly columns: ColumnNode[];
269
- };
270
- readonly to: {
271
- readonly table: TableNode;
272
- readonly columns: ColumnNode[];
273
- };
274
- readonly through?: TableNode;
275
- /** Confidence level (0-1) */
276
- readonly confidence: number;
277
- /** How this relation was detected */
278
- readonly source: RelationshipSource;
279
- /** Suggested constraint name */
280
- readonly suggestedName?: string;
281
- }
282
259
  /**
283
260
  * Severity level for schema drift issues.
284
261
  */
@@ -320,28 +297,3 @@ export interface DriftReport {
320
297
  readonly info: number;
321
298
  };
322
299
  }
323
- /**
324
- * Direction for schema synchronization.
325
- */
326
- export type SyncDirection = 'bidirectional' | 'entity-to-db' | 'db-to-entity';
327
- /**
328
- * Strategy for resolving conflicts in bidirectional sync.
329
- */
330
- export type ConflictResolution = 'prompt' | 'entity-wins' | 'db-wins' | 'skip';
331
- /**
332
- * Options for sync operations.
333
- */
334
- export interface SyncOptions {
335
- /** Direction of synchronization */
336
- readonly direction?: SyncDirection;
337
- /** How to resolve conflicts (for bidirectional) */
338
- readonly conflictResolution?: ConflictResolution;
339
- /** Prevent destructive changes (DROP, type narrowing) */
340
- readonly safe?: boolean;
341
- /** Include indexes in sync */
342
- readonly includeIndexes?: boolean;
343
- /** Include foreign keys in sync */
344
- readonly includeForeignKeys?: boolean;
345
- /** Dry run - report changes without applying */
346
- readonly dryRun?: boolean;
347
- }
@@ -1,3 +1,49 @@
1
1
  import { AbstractSqlQuerier } from '../querier/index.js';
2
+ /**
3
+ * Values every SQLite driver accepts as a bound parameter.
4
+ *
5
+ * @remarks No `boolean`: SQLite has no boolean storage class, and both `better-sqlite3` ("SQLite3 can
6
+ * only bind numbers, strings, bigints, buffers, and null") and `node:sqlite` reject one outright. The
7
+ * dialect already binds booleans as integers (`booleanLiteral: 'integer'`), so nothing reaches a
8
+ * driver as one; leaving `boolean` here only invited a runtime throw that is now a compile error.
9
+ */
10
+ export type SqliteBindValue = null | string | number | bigint | Uint8Array;
11
+ /** Header a SQLite driver returns for a statement without a `RETURNING` clause. */
12
+ export type SqliteRunResult = {
13
+ changes: number;
14
+ lastInsertRowid: number | bigint;
15
+ };
16
+ /** Bound parameters reach a driver as `unknown[]` from the compiler; every driver types them narrowly. */
17
+ export declare function toSqliteBindValues(values?: unknown[]): SqliteBindValue[];
18
+ /**
19
+ * A prepared statement from a driver with better-sqlite3 semantics. `better-sqlite3` and `bun:sqlite`
20
+ * answer synchronously, `@tursodatabase/database` with promises, and {@link PreparedSqliteQuerier}
21
+ * awaits either.
22
+ */
23
+ export type SqlitePreparedStatement = {
24
+ /** True for any statement returning rows, including one with a `RETURNING` clause. */
25
+ readonly reader: boolean;
26
+ all(...values: SqliteBindValue[]): unknown[] | Promise<unknown[]>;
27
+ run(...values: SqliteBindValue[]): SqliteRunResult | Promise<SqliteRunResult>;
28
+ iterate(...values: SqliteBindValue[]): Iterable<unknown> | AsyncIterable<unknown>;
29
+ };
2
30
  export declare abstract class AbstractSqliteQuerier extends AbstractSqlQuerier {
31
+ /**
32
+ * SQLite drivers hold a single shared handle rather than a connection from a pool, so releasing
33
+ * a querier returns nothing; it only asserts the unit of work was finished. Drivers owning a
34
+ * closable per-querier connection override this.
35
+ */
36
+ internalRelease(): Promise<void>;
37
+ }
38
+ /**
39
+ * Querier for the SQLite drivers that expose prepared statements: `better-sqlite3`, `bun:sqlite`
40
+ * (through `adaptBunSqlite`) and the embedded Turso engine. They differ only in whether preparing and
41
+ * stepping are synchronous, which `await` and `for await` absorb, so the read/write/stream logic -
42
+ * including the `reader` rule below, whose loss silently drops inserted ids - is written once.
43
+ */
44
+ export declare abstract class PreparedSqliteQuerier extends AbstractSqliteQuerier {
45
+ protected abstract prepare(query: string): SqlitePreparedStatement | Promise<SqlitePreparedStatement>;
46
+ internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
47
+ internalStream<T>(query: string, values?: unknown[]): AsyncGenerator<Awaited<T>, void, unknown>;
48
+ internalRun(query: string, values?: unknown[]): Promise<import("../type/query.js").QueryUpdateResult>;
3
49
  }