uql-orm 0.21.0 → 0.22.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 (142) hide show
  1. package/README.md +5 -4
  2. package/dist/bunSql/bunSqlCockroachDialect.d.ts +2 -2
  3. package/dist/bunSql/bunSqlCockroachDialect.js +4 -10
  4. package/dist/bunSql/bunSqlPostgresDialect.d.ts +2 -2
  5. package/dist/bunSql/bunSqlPostgresDialect.js +4 -10
  6. package/dist/cockroachdb/cockroachDialect.d.ts +20 -4
  7. package/dist/cockroachdb/cockroachDialect.js +33 -18
  8. package/dist/d1/d1Querier.d.ts +4 -3
  9. package/dist/d1/d1Querier.js +0 -3
  10. package/dist/d1/d1SqliteDialect.d.ts +7 -0
  11. package/dist/d1/d1SqliteDialect.js +9 -0
  12. package/dist/dialect/abstractDialect.d.ts +8 -3
  13. package/dist/dialect/abstractDialect.js +9 -4
  14. package/dist/dialect/abstractSqlDialect.d.ts +35 -7
  15. package/dist/dialect/abstractSqlDialect.js +76 -35
  16. package/dist/dialect/index.d.ts +1 -0
  17. package/dist/dialect/index.js +1 -0
  18. package/dist/dialect/indexSqlDialect.d.ts +57 -0
  19. package/dist/dialect/indexSqlDialect.js +101 -0
  20. package/dist/dialect/mysqlLikeSqlDialect.d.ts +16 -1
  21. package/dist/dialect/mysqlLikeSqlDialect.js +32 -1
  22. package/dist/dialect/pgLikeSqlDialect.d.ts +33 -15
  23. package/dist/dialect/pgLikeSqlDialect.js +90 -56
  24. package/dist/dialect/vectorCast.d.ts +21 -0
  25. package/dist/dialect/vectorCast.js +28 -0
  26. package/dist/dialect/vectorSqlDialect.d.ts +10 -3
  27. package/dist/dialect/vectorSqlDialect.js +19 -8
  28. package/dist/entity/decorator/index-decorator.d.ts +2 -9
  29. package/dist/entity/metadata/definition.d.ts +5 -4
  30. package/dist/entity/metadata/definition.js +4 -3
  31. package/dist/libsql/libsqlDialect.d.ts +12 -2
  32. package/dist/libsql/libsqlDialect.js +14 -2
  33. package/dist/libsql/libsqlQuerier.d.ts +9 -22
  34. package/dist/libsql/libsqlQuerier.js +9 -85
  35. package/dist/libsql/libsqlQuerierPool.d.ts +10 -8
  36. package/dist/libsql/libsqlQuerierPool.js +17 -15
  37. package/dist/maria/mariaDialect.d.ts +26 -4
  38. package/dist/maria/mariaDialect.js +54 -15
  39. package/dist/migrate/builder/migrationBuilder.d.ts +9 -29
  40. package/dist/migrate/builder/migrationBuilder.js +58 -76
  41. package/dist/migrate/builder/splitSqlStatements.d.ts +0 -2
  42. package/dist/migrate/builder/splitSqlStatements.js +0 -2
  43. package/dist/migrate/builder/tableBuilder.d.ts +12 -2
  44. package/dist/migrate/builder/tableBuilder.js +71 -119
  45. package/dist/migrate/builder/types.d.ts +30 -41
  46. package/dist/migrate/cli.d.ts +3 -2
  47. package/dist/migrate/cli.js +21 -48
  48. package/dist/migrate/codegen/entityCodeGenerator.d.ts +0 -4
  49. package/dist/migrate/codegen/entityCodeGenerator.js +5 -63
  50. package/dist/migrate/codegen/fieldOptionsSource.d.ts +10 -0
  51. package/dist/migrate/codegen/fieldOptionsSource.js +55 -0
  52. package/dist/migrate/codegen/index.d.ts +0 -2
  53. package/dist/migrate/codegen/index.js +0 -4
  54. package/dist/migrate/generator/indexNodeToSchema.d.ts +9 -0
  55. package/dist/migrate/generator/indexNodeToSchema.js +14 -0
  56. package/dist/migrate/generator/mongoCommand.d.ts +57 -0
  57. package/dist/migrate/generator/mongoCommand.js +26 -0
  58. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +9 -20
  59. package/dist/migrate/generator/mongoSchemaGenerator.js +40 -82
  60. package/dist/migrate/index.d.ts +0 -1
  61. package/dist/migrate/index.js +0 -1
  62. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +25 -10
  63. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +46 -32
  64. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +0 -1
  65. package/dist/migrate/introspection/baseSqlIntrospector.js +3 -12
  66. package/dist/migrate/introspection/mongoIntrospector.js +2 -2
  67. package/dist/migrate/introspection/mysqlIntrospector.d.ts +5 -8
  68. package/dist/migrate/introspection/mysqlIntrospector.js +4 -10
  69. package/dist/migrate/introspection/postgresIntrospector.d.ts +5 -8
  70. package/dist/migrate/introspection/postgresIntrospector.js +4 -10
  71. package/dist/migrate/introspection/sqliteIntrospector.d.ts +6 -5
  72. package/dist/migrate/introspection/sqliteIntrospector.js +26 -18
  73. package/dist/migrate/migrator.d.ts +23 -3
  74. package/dist/migrate/migrator.js +57 -69
  75. package/dist/migrate/schemaGenerator.d.ts +26 -37
  76. package/dist/migrate/schemaGenerator.js +69 -174
  77. package/dist/migrate/schemaGeneratorAsync.d.ts +2 -2
  78. package/dist/migrate/schemaGeneratorAsync.js +3 -3
  79. package/dist/migrate/storage/databaseStorage.js +4 -4
  80. package/dist/mongo/mongoDialect.d.ts +2 -0
  81. package/dist/mongo/mongoDialect.js +14 -3
  82. package/dist/mysql/mysqlDialect.d.ts +8 -0
  83. package/dist/mysql/mysqlDialect.js +13 -0
  84. package/dist/schema/canonicalType.d.ts +14 -2
  85. package/dist/schema/canonicalType.js +41 -31
  86. package/dist/schema/index.d.ts +1 -1
  87. package/dist/schema/schemaASTBuilder.d.ts +8 -1
  88. package/dist/schema/schemaASTBuilder.js +56 -47
  89. package/dist/schema/types.d.ts +10 -37
  90. package/dist/sqlite/abstractSqliteQuerier.d.ts +39 -0
  91. package/dist/sqlite/abstractSqliteQuerier.js +44 -0
  92. package/dist/sqlite/bunSqliteAdapter.bun.d.ts +26 -0
  93. package/dist/sqlite/bunSqliteAdapter.bun.js +25 -0
  94. package/dist/sqlite/hranaQuerier.d.ts +54 -0
  95. package/dist/sqlite/hranaQuerier.js +90 -0
  96. package/dist/sqlite/hranaQuerierPool.d.ts +21 -0
  97. package/dist/sqlite/hranaQuerierPool.js +25 -0
  98. package/dist/sqlite/index.d.ts +2 -1
  99. package/dist/sqlite/index.js +2 -1
  100. package/dist/sqlite/sqliteDialect.d.ts +18 -4
  101. package/dist/sqlite/sqliteDialect.js +24 -24
  102. package/dist/sqlite/sqliteQuerier.d.ts +16 -9
  103. package/dist/sqlite/sqliteQuerier.js +4 -27
  104. package/dist/sqlite/sqliteQuerierPool.d.ts +14 -4
  105. package/dist/sqlite/sqliteQuerierPool.js +16 -5
  106. package/dist/turso/index.d.ts +3 -0
  107. package/dist/turso/index.js +3 -0
  108. package/dist/turso/local.d.ts +3 -0
  109. package/dist/turso/local.js +3 -0
  110. package/dist/turso/tursoDialect.d.ts +15 -0
  111. package/dist/turso/tursoDialect.js +18 -0
  112. package/dist/turso/tursoLocalQuerier.d.ts +25 -0
  113. package/dist/turso/tursoLocalQuerier.js +20 -0
  114. package/dist/turso/tursoLocalQuerierPool.d.ts +32 -0
  115. package/dist/turso/tursoLocalQuerierPool.js +39 -0
  116. package/dist/turso/tursoQuerier.d.ts +10 -0
  117. package/dist/turso/tursoQuerier.js +10 -0
  118. package/dist/turso/tursoQuerierPool.d.ts +39 -0
  119. package/dist/turso/tursoQuerierPool.js +38 -0
  120. package/dist/type/config.d.ts +0 -6
  121. package/dist/type/dialect.d.ts +12 -4
  122. package/dist/type/dialect.js +7 -1
  123. package/dist/type/entity.d.ts +66 -5
  124. package/dist/type/migration.d.ts +32 -23
  125. package/dist/type/vector.d.ts +5 -2
  126. package/dist/util/index.d.ts +1 -0
  127. package/dist/util/index.js +1 -0
  128. package/dist/util/indexColumn.util.d.ts +6 -0
  129. package/dist/util/indexColumn.util.js +26 -0
  130. package/package.json +16 -3
  131. package/dist/migrate/codegen/entityMerger.d.ts +0 -114
  132. package/dist/migrate/codegen/entityMerger.js +0 -293
  133. package/dist/migrate/codegen/migrationCodeGenerator.d.ts +0 -61
  134. package/dist/migrate/codegen/migrationCodeGenerator.js +0 -355
  135. package/dist/migrate/generator/index.d.ts +0 -2
  136. package/dist/migrate/generator/index.js +0 -2
  137. package/dist/migrate/sync/index.d.ts +0 -6
  138. package/dist/migrate/sync/index.js +0 -6
  139. package/dist/migrate/sync/schemaSync.d.ts +0 -131
  140. package/dist/migrate/sync/schemaSync.js +0 -259
  141. package/dist/sqlite/betterSqlite3Dialect.d.ts +0 -9
  142. package/dist/sqlite/betterSqlite3Dialect.js +0 -9
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  <h3>The smartest TypeScript ORM</h3>
11
11
 
12
- <p>Type-safe to the leaf, serializable queries, no codegen, <a href="https://uql-orm.dev/benchmark">extremely fast</a>, and a unified API across Postgres, CockroachDB, MySQL, SQLite, MongoDB, MariaDB, and the edge.</p>
12
+ <p>Type-safe to the leaf, serializable queries, no codegen, <a href="https://uql-orm.dev/benchmark">extremely fast</a>, and unified API across Postgres, CockroachDB, MySQL, SQLite, MongoDB, MariaDB, and the edge.</p>
13
13
 
14
14
  <p>
15
15
  <a href="https://uql-orm.dev"><b>Website</b></a> ·
@@ -29,7 +29,7 @@
29
29
  ---
30
30
 
31
31
  ```sh
32
- npm install uql-orm pg # or mysql2, mariadb, better-sqlite3, mongodb, @libsql/client
32
+ npm install uql-orm pg # or mysql2, mariadb, better-sqlite3, mongodb, @tursodatabase/serverless, @libsql/client
33
33
  ```
34
34
 
35
35
  Decorators also need `reflect-metadata` ([setup](https://uql-orm.dev/getting-started)); the
@@ -49,12 +49,13 @@ from the browser to the server. The same object runs on every supported database
49
49
 
50
50
  ## Why UQL?
51
51
 
52
- - **The fastest.** Wins [all 8 categories](https://uql-orm.dev/benchmark) of our [open benchmark](https://github.com/rogerpadilla/ts-orm-benchmark), beating even query builders like Knex and Kysely: ~2.4× faster than the runner-up on average, over 4.1M ops/s on simple SELECTs.
52
+ - **The fastest.** Wins [all 8 categories](https://uql-orm.dev/benchmark) of our [open benchmark](https://github.com/rogerpadilla/ts-orm-benchmark), beating even query builders like Knex and Kysely: ~2.3× faster than the runner-up on average, close to 4M ops/s on simple SELECTs.
53
53
  - **Light.** Zero dependencies, under 1 MB installed, every dialect included.
54
54
  - **Queries are data, not method chains.** Plain JSON in, typed rows out. There's no DSL to learn and nothing to compile.
55
55
  - **Type-safe to the leaf.** Operators are gated per field type, and JSON/JSONB dot-paths resolve each path's value type, so `{ age: { $like: 'x' } }` or a typo'd path is a compile error instead of a runtime surprise.
56
56
  - **No codegen.** Entities are TypeScript classes, so your code *is* the schema. No `.prisma` file to regenerate, no generated client to keep in sync.
57
- - **One API everywhere.** PostgreSQL, CockroachDB, MySQL, MariaDB, SQLite, LibSQL/Turso, Neon, Cloudflare D1, Bun SQL, and MongoDB.
57
+ - **One API everywhere.** PostgreSQL, CockroachDB, MySQL, MariaDB, SQLite, Turso, LibSQL, Neon, Cloudflare D1, Bun SQL, and MongoDB.
58
+ - **First ORM with a native Turso driver.** Turso Cloud over pure `fetch()` with zero native dependencies, so it runs on Cloudflare Workers and Vercel Edge, plus the embedded Rust engine for local-first apps.
58
59
  - **The hard things are built in.** [Semantic and vector search](https://uql-orm.dev/ai-semantic-search), [non-bypassable multi-tenant filters](https://uql-orm.dev/multi-tenancy), [entity-first migrations](https://uql-orm.dev/migrations), [soft-delete with restore](https://uql-orm.dev/entities/soft-delete), [streaming](https://uql-orm.dev/querying/streaming), and [a REST API from your entities](https://uql-orm.dev/extensions-http).
59
60
 
60
61
  ## Get started
@@ -1,5 +1,5 @@
1
1
  import { CockroachDialect } from '../cockroachdb/cockroachDialect.js';
2
- import type { DialectOptions } from '../dialect/abstractDialect.js';
2
+ import type { DialectFeatures } from '../type/index.js';
3
3
  /**
4
4
  * CockroachDB Dialect specialization for the `bun:sql` driver, which routes CockroachDB
5
5
  * connections through its own Postgres wire-protocol implementation (see
@@ -8,5 +8,5 @@ import type { DialectOptions } from '../dialect/abstractDialect.js';
8
8
  * (verified directly against a live CockroachDB instance via `bun:sql`).
9
9
  */
10
10
  export declare class BunSqlCockroachDialect extends CockroachDialect {
11
- constructor(options?: DialectOptions);
11
+ protected readonly featureOverrides: Partial<DialectFeatures>;
12
12
  }
@@ -8,14 +8,8 @@ import { POSTGRES_WIRE_DRIVER_CAPABILITIES } from '../postgres/postgresWireDrive
8
8
  * (verified directly against a live CockroachDB instance via `bun:sql`).
9
9
  */
10
10
  export class BunSqlCockroachDialect extends CockroachDialect {
11
- constructor(options = {}) {
12
- super({
13
- ...options,
14
- driverCapabilities: {
15
- ...POSTGRES_WIRE_DRIVER_CAPABILITIES,
16
- explicitJsonCast: true,
17
- ...options.driverCapabilities,
18
- },
19
- });
20
- }
11
+ featureOverrides = {
12
+ ...POSTGRES_WIRE_DRIVER_CAPABILITIES,
13
+ explicitJsonCast: true,
14
+ };
21
15
  }
@@ -1,5 +1,5 @@
1
- import type { DialectOptions } from '../dialect/abstractDialect.js';
2
1
  import { PostgresDialect } from '../postgres/postgresDialect.js';
2
+ import type { DialectFeatures } from '../type/index.js';
3
3
  /**
4
4
  * Postgres Dialect specialization for the `bun:sql` driver.
5
5
  *
@@ -7,5 +7,5 @@ import { PostgresDialect } from '../postgres/postgresDialect.js';
7
7
  * reliably; `PgDialect` omits the text re-cast.
8
8
  */
9
9
  export declare class BunSqlPostgresDialect extends PostgresDialect {
10
- constructor(options?: DialectOptions);
10
+ protected readonly featureOverrides: Partial<DialectFeatures>;
11
11
  }
@@ -7,14 +7,8 @@ import { POSTGRES_WIRE_DRIVER_CAPABILITIES } from '../postgres/postgresWireDrive
7
7
  * reliably; `PgDialect` omits the text re-cast.
8
8
  */
9
9
  export class BunSqlPostgresDialect extends PostgresDialect {
10
- constructor(options = {}) {
11
- super({
12
- ...options,
13
- driverCapabilities: {
14
- ...POSTGRES_WIRE_DRIVER_CAPABILITIES,
15
- explicitJsonCast: true,
16
- ...options.driverCapabilities,
17
- },
18
- });
19
- }
10
+ featureOverrides = {
11
+ ...POSTGRES_WIRE_DRIVER_CAPABILITIES,
12
+ explicitJsonCast: true,
13
+ };
20
14
  }
@@ -1,6 +1,5 @@
1
- import type { DialectOptions } from '../dialect/abstractDialect.js';
2
1
  import { PgLikeSqlDialect } from '../dialect/pgLikeSqlDialect.js';
3
- import type { VectorDistance } from '../type/index.js';
2
+ import type { IndexFeature, IndexSchema, VectorDistance } from '../type/index.js';
4
3
  /**
5
4
  * CockroachDB Dialect.
6
5
  * Shares AST/quoting/JSONB/full-text-search/vector-search/upsert logic with Postgres via
@@ -14,6 +13,23 @@ import type { VectorDistance } from '../type/index.js';
14
13
  */
15
14
  export declare class CockroachDialect extends PgLikeSqlDialect {
16
15
  readonly dialectName = "cockroachdb";
17
- readonly vectorOpsClass: ReadonlyMap<VectorDistance, string> | undefined;
18
- constructor(options?: DialectOptions);
16
+ readonly vectorMetrics: ReadonlyMap<VectorDistance, {
17
+ op: string;
18
+ opsSuffix: string;
19
+ }>;
20
+ /**
21
+ * `NULLS FIRST/LAST` answers "unimplemented: this syntax" and `jsonb_path_ops` "operator class is
22
+ * not supported" (both verified on v26.2), so neither is offered here.
23
+ */
24
+ protected readonly indexFeatures: Set<IndexFeature>;
25
+ /**
26
+ * CockroachDB's vector index is native and has its own syntax: `CREATE VECTOR INDEX ... ("col"
27
+ * vector_cosine_ops)`, with no access-method keyword, and tuning knobs of its own names that UQL
28
+ * does not map. `type: 'vector'` is its trigger, the same generic value MariaDB's inline index uses.
29
+ */
30
+ private isNativeVectorIndex;
31
+ protected isVectorIndex(index: IndexSchema): boolean;
32
+ protected indexKeyword(index: IndexSchema): string;
33
+ protected indexAccessMethod(index: IndexSchema): string;
34
+ protected indexTuning(index: IndexSchema): string;
19
35
  }
@@ -12,24 +12,39 @@ import { PgLikeSqlDialect } from '../dialect/pgLikeSqlDialect.js';
12
12
  */
13
13
  export class CockroachDialect extends PgLikeSqlDialect {
14
14
  dialectName = 'cockroachdb';
15
- // CockroachDB only implements 3 of pgvector's 5 distance metrics. Verified live (both `<+>`/`<~>`
16
- // query operators and `vector_l1_ops`/`bit_hamming_ops` index opclasses throw "unimplemented:
17
- // operator class ... is not supported") and confirmed in CockroachDB's own docs, "Known
18
- // limitations": https://www.cockroachlabs.com/docs/stable/vector-indexes - tracked upstream at
19
- // https://github.com/cockroachdb/cockroach/issues/147839. Re-check that issue before adding
20
- // `l1`/`hamming` here; they're omitted on purpose, not an oversight.
21
- vectorOpsClass = new Map([
22
- ['cosine', 'vector_cosine_ops'],
23
- ['l2', 'vector_l2_ops'],
24
- ['inner', 'vector_ip_ops'],
15
+ // CockroachDB implements 3 of pgvector's 4 metrics: `<+>` and `vector_l1_ops` throw
16
+ // "unimplemented: operator class ... is not supported" (verified live on v26.2), which its own docs
17
+ // list under "Known limitations": https://www.cockroachlabs.com/docs/stable/vector-indexes -
18
+ // tracked upstream at https://github.com/cockroachdb/cockroach/issues/147839. Re-check that issue
19
+ // before adding `l1` here; it is omitted on purpose, not an oversight.
20
+ vectorMetrics = new Map([
21
+ ['cosine', { op: '<=>', opsSuffix: 'cosine' }],
22
+ ['l2', { op: '<->', opsSuffix: 'l2' }],
23
+ ['inner', { op: '<#>', opsSuffix: 'ip' }],
25
24
  ]);
26
- constructor(options = {}) {
27
- super({
28
- ...options,
29
- driverCapabilities: {
30
- vectorIndexStyle: 'native',
31
- ...options.driverCapabilities,
32
- },
33
- });
25
+ /**
26
+ * `NULLS FIRST/LAST` answers "unimplemented: this syntax" and `jsonb_path_ops` "operator class is
27
+ * not supported" (both verified on v26.2), so neither is offered here.
28
+ */
29
+ indexFeatures = new Set(['expression', 'include']);
30
+ /**
31
+ * CockroachDB's vector index is native and has its own syntax: `CREATE VECTOR INDEX ... ("col"
32
+ * vector_cosine_ops)`, with no access-method keyword, and tuning knobs of its own names that UQL
33
+ * does not map. `type: 'vector'` is its trigger, the same generic value MariaDB's inline index uses.
34
+ */
35
+ isNativeVectorIndex(index) {
36
+ return index.type === 'vector';
37
+ }
38
+ isVectorIndex(index) {
39
+ return this.isNativeVectorIndex(index) || super.isVectorIndex(index);
40
+ }
41
+ indexKeyword(index) {
42
+ return this.isNativeVectorIndex(index) ? 'VECTOR INDEX' : super.indexKeyword(index);
43
+ }
44
+ indexAccessMethod(index) {
45
+ return this.isNativeVectorIndex(index) ? '' : super.indexAccessMethod(index);
46
+ }
47
+ indexTuning(index) {
48
+ return this.isNativeVectorIndex(index) ? '' : super.indexTuning(index);
34
49
  }
35
50
  }
@@ -35,11 +35,12 @@ export interface D1Database {
35
35
  batch<T = unknown>(statements: D1PreparedStatement[]): Promise<D1Result<T>[]>;
36
36
  exec(query: string): Promise<D1ExecResult>;
37
37
  }
38
+ /** The only part of a D1 binding the querier uses; a full {@link D1Database} satisfies it. */
39
+ export type D1Preparer = Pick<D1Database, 'prepare'>;
38
40
  export declare class D1Querier extends AbstractSqliteQuerier {
39
- readonly db: D1Database;
41
+ readonly db: D1Preparer;
40
42
  readonly extra?: ExtraOptions | undefined;
41
- constructor(db: D1Database, dialect: SqliteDialect, extra?: ExtraOptions | undefined);
43
+ constructor(db: D1Preparer, dialect: SqliteDialect, extra?: ExtraOptions | undefined);
42
44
  internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
43
45
  internalRun(query: string, values?: unknown[]): Promise<import("../type/query.js").QueryUpdateResult>;
44
- internalRelease(): Promise<void>;
45
46
  }
@@ -21,7 +21,4 @@ export class D1Querier extends AbstractSqliteQuerier {
21
21
  const changes = rows.length || res.meta?.changes || 0;
22
22
  return this.buildUpdateResult({ rows, changes, id: res.meta?.last_row_id });
23
23
  }
24
- async internalRelease() {
25
- // no-op
26
- }
27
24
  }
@@ -6,4 +6,11 @@ import { SqliteDialect } from '../sqlite/sqliteDialect.js';
6
6
  */
7
7
  export declare class D1SqliteDialect extends SqliteDialect {
8
8
  readonly maxBindValues: number;
9
+ /**
10
+ * D1 loads no extensions (its allowlist is FTS5, JSON and the math functions) and has no vector
11
+ * functions of its own, so the sqlite-vec names inherited from {@link SqliteDialect} would compile
12
+ * to SQL that only fails once it reaches the edge. `raw()` is no escape hatch either, hence a
13
+ * message that names the product that does the job.
14
+ */
15
+ protected appendVectorSort(): never;
9
16
  }
@@ -7,4 +7,13 @@ import { SqliteDialect } from '../sqlite/sqliteDialect.js';
7
7
  export class D1SqliteDialect extends SqliteDialect {
8
8
  // Cloudflare D1 caps bound parameters at 100 per query.
9
9
  maxBindValues = 100;
10
+ /**
11
+ * D1 loads no extensions (its allowlist is FTS5, JSON and the math functions) and has no vector
12
+ * functions of its own, so the sqlite-vec names inherited from {@link SqliteDialect} would compile
13
+ * to SQL that only fails once it reaches the edge. `raw()` is no escape hatch either, hence a
14
+ * message that names the product that does the job.
15
+ */
16
+ appendVectorSort() {
17
+ throw new TypeError('Cloudflare D1 has no vector functions and cannot load sqlite-vec. Use Cloudflare Vectorize for vector search.');
18
+ }
10
19
  }
@@ -20,12 +20,17 @@ export declare abstract class AbstractDialect {
20
20
  * {@link DialectOptions.driverCapabilities} overrides. Each concrete dialect declares its own.
21
21
  */
22
22
  protected abstract readonly featureDefaults: DialectFeatures;
23
+ /**
24
+ * How this dialect differs from the family it extends, so a subclass states only its deltas rather
25
+ * than repeating {@link featureDefaults} in full or injecting them through its constructor.
26
+ */
27
+ protected readonly featureOverrides: Partial<DialectFeatures>;
23
28
  readonly namingStrategy: NamingStrategy | undefined;
24
29
  constructor(options?: DialectOptions);
25
30
  /**
26
- * Effective features: {@link featureDefaults} merged with any {@link DialectOptions.driverCapabilities}.
27
- * Computed lazily (and memoized) because `featureDefaults` is a subclass field, initialized only
28
- * after `super()` returns.
31
+ * Effective features: {@link featureDefaults}, then this dialect's {@link featureOverrides}, then
32
+ * any per-instance {@link DialectOptions.driverCapabilities}. Computed lazily (and memoized)
33
+ * because both are subclass fields, initialized only after `super()` returns.
29
34
  */
30
35
  get features(): DialectFeatures;
31
36
  /**
@@ -4,6 +4,11 @@ import { applyFilters, buildQueryWhereAsMap } from '../util/dialect.util.js';
4
4
  */
5
5
  export class AbstractDialect {
6
6
  options;
7
+ /**
8
+ * How this dialect differs from the family it extends, so a subclass states only its deltas rather
9
+ * than repeating {@link featureDefaults} in full or injecting them through its constructor.
10
+ */
11
+ featureOverrides = {};
7
12
  namingStrategy;
8
13
  #features;
9
14
  constructor(options = {}) {
@@ -11,12 +16,12 @@ export class AbstractDialect {
11
16
  this.namingStrategy = options.namingStrategy;
12
17
  }
13
18
  /**
14
- * Effective features: {@link featureDefaults} merged with any {@link DialectOptions.driverCapabilities}.
15
- * Computed lazily (and memoized) because `featureDefaults` is a subclass field, initialized only
16
- * after `super()` returns.
19
+ * Effective features: {@link featureDefaults}, then this dialect's {@link featureOverrides}, then
20
+ * any per-instance {@link DialectOptions.driverCapabilities}. Computed lazily (and memoized)
21
+ * because both are subclass fields, initialized only after `super()` returns.
17
22
  */
18
23
  get features() {
19
- this.#features ??= { ...this.featureDefaults, ...this.options.driverCapabilities };
24
+ this.#features ??= { ...this.featureDefaults, ...this.featureOverrides, ...this.options.driverCapabilities };
20
25
  return this.#features;
21
26
  }
22
27
  /**
@@ -1,6 +1,8 @@
1
- import { type EntityMeta, type FieldKey, type FieldOptions, type IsolationLevel, type JsonColumnType, type JsonUpdateOp, type Query, type QueryAggMap, type QueryAggregate, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryDialect, type QueryExclude, type QueryGroupMap, type QueryHavingMap, type QueryOptions, type QueryPager, type QueryPopulate, QueryRaw, type QueryRawFnOptions, type QuerySearch, type QuerySelect, type QuerySelectOptions, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOperatorMap, type QueryWhereMap, type QueryWhereOptions, type RelationOptions, type SqlDialectName, type SqlQueryDialect, type Type, type UpdatePayload } from '../type/index.js';
2
- import { VectorSqlDialect } from './vectorSqlDialect.js';
3
- export declare abstract class AbstractSqlDialect extends VectorSqlDialect implements QueryDialect, SqlQueryDialect {
1
+ import { type EntityMeta, type FieldKey, type FieldOptions, type IsolationLevel, type JsonColumnType, type JsonUpdateOp, type Query, type QueryAggMap, type QueryAggregate, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryDialect, type QueryExclude, type QueryGroupMap, type QueryHavingMap, type QueryOptions, type QueryPager, type QueryPopulate, QueryRaw, type QueryRawFnOptions, type QuerySearch, type QuerySelect, type QuerySelectOptions, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOperatorMap, type QueryWhereMap, type QueryWhereOptions, type RelationOptions, type SqlDialectName, type SqlQueryDialect, type Type, type UpdatePayload } from '../type/index.js';
2
+ import { IndexSqlDialect } from './indexSqlDialect.js';
3
+ /** How a column's values are bound: see {@link AbstractSqlDialect.persistKind}. */
4
+ type PersistKind = 'plain' | 'json' | 'vector';
5
+ export declare abstract class AbstractSqlDialect extends IndexSqlDialect implements QueryDialect, SqlQueryDialect {
4
6
  abstract readonly dialectName: SqlDialectName;
5
7
  abstract readonly escapeIdChar: '"' | '`';
6
8
  abstract readonly serialPrimaryKey: string;
@@ -47,6 +49,18 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
47
49
  returningId<E>(entity: Type<E>): string;
48
50
  search<E>(ctx: QueryContext, entity: Type<E>, q?: Query<E>, opts?: QueryOptions): void;
49
51
  selectFields<E>(ctx: QueryContext, entity: Type<E>, select: QuerySelectValue<E> | undefined, opts?: QuerySelectOptions, exclude?: QueryExclude<E>): void;
52
+ /**
53
+ * The expression a scalar field is read through, the plain column by default. MariaDB reads a
54
+ * vector column back with `VEC_ToText`, since selecting it raw yields its binary form.
55
+ */
56
+ protected selectFieldExpr(escapedColumn: string, _field: FieldOptions): string;
57
+ /**
58
+ * The `$text` full-text predicate, which every engine spells differently: `MATCH ... AGAINST`
59
+ * (MySQL family), `to_tsvector @@ websearch_to_tsquery` (Postgres-wire), an FTS5 `MATCH` against
60
+ * the table itself (SQLite). No portable form exists, so a dialect without one says so here rather
61
+ * than inheriting another engine's syntax.
62
+ */
63
+ protected appendTextSearch<E>(_ctx: QueryContext, _entity: Type<E>, _meta: EntityMeta<E>, _search: QueryTextSearchOptions<E>): void;
50
64
  select<E>(ctx: QueryContext, entity: Type<E>, select: QuerySelectValue<E> | undefined, exclude?: QueryExclude<E>, populate?: QueryPopulate<E>, opts?: QueryOptions, distinct?: boolean, sort?: QuerySortMap<E>): void;
51
65
  private resolveRelationAwarePrefix;
52
66
  protected selectRelationFields<E>(ctx: QueryContext, entity: Type<E>, select: QuerySelect<E> | undefined, populate: QueryPopulate<E> | undefined, opts?: {
@@ -195,6 +209,11 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
195
209
  protected havingCondition(ctx: QueryContext, expr: string, condition: QueryHavingMap[string]): void;
196
210
  find<E>(ctx: QueryContext, entity: Type<E>, q?: Query<E>, opts?: QueryOptions): void;
197
211
  insert<E>(ctx: QueryContext, entity: Type<E>, payload: E | E[], opts?: QueryOptions): void;
212
+ /**
213
+ * `INSERT INTO ... VALUES (...)` and nothing more. The upsert builders extend this rather than
214
+ * {@link insert}: their own clause has to come before the `RETURNING`, not after it.
215
+ */
216
+ protected appendInsertValues<E>(ctx: QueryContext, entity: Type<E>, payload: E | E[], opts?: QueryOptions): void;
198
217
  /**
199
218
  * Emit the value for a column a payload record does not provide (the column list is the union
200
219
  * across all records). `DEFAULT` delegates to the database default; SQLite overrides this since
@@ -208,13 +227,21 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
208
227
  delete<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): void;
209
228
  escapeId(val: string, forbidQualified?: boolean, addDot?: boolean): string;
210
229
  /**
211
- * The single type dispatch for a persisted value. Dialects override {@link appendJsonValue} and
212
- * {@link appendVectorValue} rather than this, so the chain runs once per value - overriding this
213
- * and delegating back to `super` ran every check twice, measurably slowing every INSERT/UPDATE.
230
+ * Bind one persisted value, classifying its column on the spot. Dialects override
231
+ * {@link appendJsonValue} and {@link appendVectorValue} rather than this, so the chain runs once per
232
+ * value - overriding this and delegating back to `super` ran every check twice, measurably slowing
233
+ * every INSERT/UPDATE.
214
234
  */
215
235
  protected formatPersistableValue(ctx: QueryContext, field: FieldOptions | undefined, value: unknown): void;
236
+ /**
237
+ * How a column's values are written. A function of the column, not of the value, so a bulk insert
238
+ * classifies each column once instead of re-deciding per row: a 20-row, 6-column insert asked
239
+ * `isJsonType` and `isVectorFieldType` 120 times to get the same six answers.
240
+ */
241
+ protected persistKind(field: FieldOptions | undefined): PersistKind;
242
+ /** The one type dispatch for a persisted value, over a column kind decided by the caller. */
243
+ private writePersistableValue;
216
244
  protected appendJsonValue(ctx: QueryContext, value: unknown, _type: JsonColumnType): void;
217
- protected appendVectorValue(ctx: QueryContext, value: readonly unknown[]): void;
218
245
  /**
219
246
  * Reads `operand` as a JSON value. Passing the `?` placeholder yields the cast for a bound
220
247
  * parameter, and passing an expression re-reads text as JSON - the same SQL either way, which is
@@ -332,3 +359,4 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
332
359
  protected numericCast(expr: string): string;
333
360
  toString(): string;
334
361
  }
362
+ export {};
@@ -1,12 +1,13 @@
1
1
  import { getMeta } from '../entity/index.js';
2
2
  import { QueryRaw, RAW_ALIAS, RAW_VALUE, } from '../type/index.js';
3
- import { asSelectMap, buildQueryWhereAsMap, buildSortMap, escapeSqlId, fillOnFields, filterFieldKeys, flatObject, getFieldKeys, getInsertFieldKeys, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, hasMultipleKeys, isJsonType, isJsonUpdateOp, isOperatorObject, isOperatorOnlyObject, isPopulatingRelations, isVectorSearch, normalizeScalarFieldSelection, parseGroupMap, parseRelationAtKey, parseRelationSize, raw, someValue, withoutSoftDeleteFilter, } from '../util/index.js';
3
+ import { asSelectMap, buildQueryWhereAsMap, buildSortMap, escapeSqlId, fillOnFields, filterFieldKeys, flatObject, getInsertFieldKeys, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, hasMultipleKeys, isJsonType, isJsonUpdateOp, isOperatorObject, isOperatorOnlyObject, isPopulatingRelations, isVectorSearch, normalizeScalarFieldSelection, parseGroupMap, parseRelationAtKey, parseRelationSize, raw, someValue, withoutSoftDeleteFilter, } from '../util/index.js';
4
4
  import { escapeAnsiSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
5
+ import { IndexSqlDialect } from './indexSqlDialect.js';
5
6
  import { buildElemMatchConditions } from './jsonArrayElemMatchUtils.js';
6
7
  import { JSON_ELEM_ALIAS_PREFIX, jsonElemExists } from './jsonSql.js';
7
8
  import { SqlQueryContext } from './queryContext.js';
8
- import { VectorSqlDialect } from './vectorSqlDialect.js';
9
- export class AbstractSqlDialect extends VectorSqlDialect {
9
+ import { isVectorFieldType } from './vectorCast.js';
10
+ export class AbstractSqlDialect extends IndexSqlDialect {
10
11
  isolationLevelStrategy = 'inline';
11
12
  alterColumnStrategy = 'single-statement';
12
13
  alterColumnSyntax = 'ALTER COLUMN';
@@ -135,7 +136,6 @@ export class AbstractSqlDialect extends VectorSqlDialect {
135
136
  const field = meta.fields[key];
136
137
  if (!field)
137
138
  return;
138
- const columnName = this.resolveColumnName(key, field);
139
139
  if (field.virtual) {
140
140
  this.getRawValue(ctx, {
141
141
  value: raw(field.virtual[RAW_VALUE], key),
@@ -143,17 +143,35 @@ export class AbstractSqlDialect extends VectorSqlDialect {
143
143
  escapedPrefix,
144
144
  autoPrefixAlias: opts.autoPrefixAlias,
145
145
  });
146
+ return;
146
147
  }
147
- else {
148
- ctx.append(escapedPrefix + this.escapeId(columnName));
149
- }
150
- if (!field.virtual && (columnName !== key || opts.autoPrefixAlias)) {
151
- const aliasStr = prefix + key;
152
- ctx.append(' ' + this.escapeId(aliasStr, true));
148
+ const columnName = this.resolveColumnName(key, field);
149
+ const column = escapedPrefix + this.escapeId(columnName);
150
+ const expr = this.selectFieldExpr(column, field);
151
+ ctx.append(expr);
152
+ // An expression needs the alias too, or the row comes back keyed by the expression text.
153
+ if (expr !== column || columnName !== key || opts.autoPrefixAlias) {
154
+ ctx.append(' ' + this.escapeId(prefix + key, true));
153
155
  }
154
156
  }
155
157
  });
156
158
  }
159
+ /**
160
+ * The expression a scalar field is read through, the plain column by default. MariaDB reads a
161
+ * vector column back with `VEC_ToText`, since selecting it raw yields its binary form.
162
+ */
163
+ selectFieldExpr(escapedColumn, _field) {
164
+ return escapedColumn;
165
+ }
166
+ /**
167
+ * The `$text` full-text predicate, which every engine spells differently: `MATCH ... AGAINST`
168
+ * (MySQL family), `to_tsvector @@ websearch_to_tsquery` (Postgres-wire), an FTS5 `MATCH` against
169
+ * the table itself (SQLite). No portable form exists, so a dialect without one says so here rather
170
+ * than inheriting another engine's syntax.
171
+ */
172
+ appendTextSearch(_ctx, _entity, _meta, _search) {
173
+ throw new TypeError(`${this.dialectName} does not support $text full-text search`);
174
+ }
157
175
  select(ctx, entity, select, exclude, populate, opts = {}, distinct, sort) {
158
176
  const meta = getMeta(entity);
159
177
  const tableName = this.resolveTableName(entity, meta);
@@ -296,16 +314,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
296
314
  return;
297
315
  }
298
316
  if (key === '$text') {
299
- const search = val;
300
- const searchFields = search.$fields ?? getFieldKeys(meta.fields);
301
- const fields = searchFields.map((fKey) => {
302
- const field = meta.fields[fKey];
303
- const columnName = this.resolveColumnName(fKey, field);
304
- return this.escapeId(columnName);
305
- });
306
- ctx.append(`MATCH(${fields.join(', ')}) AGAINST(`);
307
- ctx.addValue(search.$value);
308
- ctx.append(')');
317
+ this.appendTextSearch(ctx, entity, meta, val);
309
318
  return;
310
319
  }
311
320
  if (key === '$and' || key === '$or' || key === '$not' || key === '$nor') {
@@ -911,6 +920,18 @@ export class AbstractSqlDialect extends VectorSqlDialect {
911
920
  this.search(ctx, entity, q, opts);
912
921
  }
913
922
  insert(ctx, entity, payload, opts) {
923
+ this.appendInsertValues(ctx, entity, payload, opts);
924
+ // Every engine whose ids come back from the statement itself wants the same clause, so it is
925
+ // appended once here instead of in an identical `insert` override per dialect.
926
+ if (this.insertIdSource === 'returning') {
927
+ ctx.append(` ${this.returningId(entity)}`);
928
+ }
929
+ }
930
+ /**
931
+ * `INSERT INTO ... VALUES (...)` and nothing more. The upsert builders extend this rather than
932
+ * {@link insert}: their own clause has to come before the `RETURNING`, not after it.
933
+ */
934
+ appendInsertValues(ctx, entity, payload, opts) {
914
935
  const meta = getMeta(entity);
915
936
  const payloads = fillOnFields(meta, payload, 'onInsert');
916
937
  const keys = getInsertFieldKeys(meta, payloads);
@@ -919,10 +940,13 @@ export class AbstractSqlDialect extends VectorSqlDialect {
919
940
  const width = keys.length;
920
941
  const fields = new Array(width);
921
942
  const columns = new Array(width);
943
+ const kinds = new Array(width);
922
944
  for (let i = 0; i < width; i++) {
923
945
  const key = keys[i];
924
- fields[i] = meta.fields[key];
946
+ const field = meta.fields[key];
947
+ fields[i] = field;
925
948
  columns[i] = this.escapedColumnName(meta, key);
949
+ kinds[i] = this.persistKind(field);
926
950
  }
927
951
  const tableName = this.resolveTableName(entity, meta);
928
952
  ctx.append(`INSERT INTO ${this.escapeId(tableName)} (${columns.join(', ')}) VALUES (`);
@@ -939,8 +963,12 @@ export class AbstractSqlDialect extends VectorSqlDialect {
939
963
  if (value === undefined) {
940
964
  this.appendDefaultInsertValue(ctx, fields[i]);
941
965
  }
966
+ else if (kinds[i] === 'plain' && !(value instanceof QueryRaw)) {
967
+ // The overwhelmingly common case in a bulk insert, so it binds without a dispatch.
968
+ ctx.addValue(value);
969
+ }
942
970
  else {
943
- this.formatPersistableValue(ctx, fields[i], value);
971
+ this.writePersistableValue(ctx, kinds[i], fields[i], value);
944
972
  }
945
973
  }
946
974
  }
@@ -983,13 +1011,13 @@ export class AbstractSqlDialect extends VectorSqlDialect {
983
1011
  const updateCtx = this.createContext();
984
1012
  const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload, (name) => `VALUES(${name})`);
985
1013
  if (update) {
986
- this.insert(ctx, entity, payload);
1014
+ this.appendInsertValues(ctx, entity, payload);
987
1015
  ctx.append(` ON DUPLICATE KEY UPDATE ${update}`);
988
1016
  ctx.pushValue(...updateCtx.values);
989
1017
  }
990
1018
  else {
991
1019
  const insertCtx = this.createContext();
992
- this.insert(insertCtx, entity, payload);
1020
+ this.appendInsertValues(insertCtx, entity, payload);
993
1021
  ctx.append(insertCtx.sql.replace(/^INSERT/, 'INSERT IGNORE'));
994
1022
  ctx.pushValue(...insertCtx.values);
995
1023
  }
@@ -1045,22 +1073,38 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1045
1073
  return escapeSqlId(val, this.escapeIdChar, forbidQualified, addDot);
1046
1074
  }
1047
1075
  /**
1048
- * The single type dispatch for a persisted value. Dialects override {@link appendJsonValue} and
1049
- * {@link appendVectorValue} rather than this, so the chain runs once per value - overriding this
1050
- * and delegating back to `super` ran every check twice, measurably slowing every INSERT/UPDATE.
1076
+ * Bind one persisted value, classifying its column on the spot. Dialects override
1077
+ * {@link appendJsonValue} and {@link appendVectorValue} rather than this, so the chain runs once per
1078
+ * value - overriding this and delegating back to `super` ran every check twice, measurably slowing
1079
+ * every INSERT/UPDATE.
1051
1080
  */
1052
1081
  formatPersistableValue(ctx, field, value) {
1082
+ this.writePersistableValue(ctx, this.persistKind(field), field, value);
1083
+ }
1084
+ /**
1085
+ * How a column's values are written. A function of the column, not of the value, so a bulk insert
1086
+ * classifies each column once instead of re-deciding per row: a 20-row, 6-column insert asked
1087
+ * `isJsonType` and `isVectorFieldType` 120 times to get the same six answers.
1088
+ */
1089
+ persistKind(field) {
1090
+ const type = field?.type;
1091
+ if (isJsonType(type)) {
1092
+ return 'json';
1093
+ }
1094
+ return isVectorFieldType(type) ? 'vector' : 'plain';
1095
+ }
1096
+ /** The one type dispatch for a persisted value, over a column kind decided by the caller. */
1097
+ writePersistableValue(ctx, kind, field, value) {
1053
1098
  if (value instanceof QueryRaw) {
1054
1099
  this.getRawValue(ctx, { value });
1055
1100
  return;
1056
1101
  }
1057
- const type = field?.type;
1058
- if (isJsonType(type)) {
1059
- this.appendJsonValue(ctx, value, type);
1102
+ if (kind === 'json') {
1103
+ this.appendJsonValue(ctx, value, field?.type);
1060
1104
  return;
1061
1105
  }
1062
- if (type === 'vector' && Array.isArray(value)) {
1063
- this.appendVectorValue(ctx, value);
1106
+ if (kind === 'vector' && Array.isArray(value)) {
1107
+ this.appendVectorValue(ctx, value, field);
1064
1108
  return;
1065
1109
  }
1066
1110
  ctx.addValue(value);
@@ -1068,9 +1112,6 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1068
1112
  appendJsonValue(ctx, value, _type) {
1069
1113
  ctx.addValue(value == null ? null : JSON.stringify(value));
1070
1114
  }
1071
- appendVectorValue(ctx, value) {
1072
- ctx.addValue(`[${value.join(',')}]`);
1073
- }
1074
1115
  /**
1075
1116
  * Reads `operand` as a JSON value. Passing the `?` placeholder yields the cast for a bound
1076
1117
  * parameter, and passing an expression re-reads text as JSON - the same SQL either way, which is
@@ -4,5 +4,6 @@ export { PostgresDialect } from '../postgres/postgresDialect.js';
4
4
  export { SqliteDialect } from '../sqlite/sqliteDialect.js';
5
5
  export * from './abstractDialect.js';
6
6
  export * from './abstractSqlDialect.js';
7
+ export * from './indexSqlDialect.js';
7
8
  export * from './mysqlLikeSqlDialect.js';
8
9
  export * from './queryContext.js';
@@ -4,5 +4,6 @@ export { PostgresDialect } from '../postgres/postgresDialect.js';
4
4
  export { SqliteDialect } from '../sqlite/sqliteDialect.js';
5
5
  export * from './abstractDialect.js';
6
6
  export * from './abstractSqlDialect.js';
7
+ export * from './indexSqlDialect.js';
7
8
  export * from './mysqlLikeSqlDialect.js';
8
9
  export * from './queryContext.js';