uql-orm 0.22.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 (104) hide show
  1. package/dist/browser/uql-browser.min.js.map +3 -3
  2. package/dist/dialect/abstractSqlDialect.d.ts +14 -1
  3. package/dist/dialect/abstractSqlDialect.js +21 -14
  4. package/dist/dialect/mysqlLikeSqlDialect.d.ts +11 -1
  5. package/dist/dialect/mysqlLikeSqlDialect.js +25 -0
  6. package/dist/dialect/pgLikeSqlDialect.d.ts +4 -7
  7. package/dist/dialect/pgLikeSqlDialect.js +7 -12
  8. package/dist/entity/decorator/bag.d.ts +35 -0
  9. package/dist/entity/decorator/bag.js +54 -0
  10. package/dist/entity/decorator/entity.d.ts +28 -2
  11. package/dist/entity/decorator/entity.js +40 -2
  12. package/dist/entity/decorator/members.d.ts +51 -0
  13. package/dist/entity/decorator/members.js +51 -0
  14. package/dist/entity/index.d.ts +3 -1
  15. package/dist/entity/index.js +3 -1
  16. package/dist/entity/metadata/definition.d.ts +23 -9
  17. package/dist/entity/metadata/definition.js +55 -100
  18. package/dist/http/contract.js +1 -2
  19. package/dist/http/handler.js +5 -26
  20. package/dist/http/query.js +1 -1
  21. package/dist/migrate/acquireQuerierForMigrations.d.ts +13 -1
  22. package/dist/migrate/acquireQuerierForMigrations.js +28 -0
  23. package/dist/migrate/cli-config.js +24 -8
  24. package/dist/migrate/cli.js +5 -1
  25. package/dist/migrate/codegen/entityCodeGenerator.js +3 -4
  26. package/dist/migrate/codegen/index.d.ts +0 -1
  27. package/dist/migrate/codegen/index.js +0 -2
  28. package/dist/migrate/drift/driftDetector.d.ts +10 -1
  29. package/dist/migrate/drift/driftDetector.js +22 -5
  30. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +5 -1
  31. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +17 -27
  32. package/dist/migrate/introspection/mongoIntrospector.js +7 -13
  33. package/dist/migrate/migrator.js +44 -81
  34. package/dist/migrate/storage/databaseStorage.d.ts +2 -0
  35. package/dist/migrate/storage/databaseStorage.js +8 -23
  36. package/dist/mongo/mongodbQuerier.js +171 -229
  37. package/dist/nestjs/uqlContextInterceptor.js +58 -22
  38. package/dist/nestjs/uqlModule.d.ts +2 -11
  39. package/dist/nestjs/uqlModule.js +93 -44
  40. package/dist/postgres/postgresDialect.js +2 -2
  41. package/dist/querier/abstractQuerier.d.ts +28 -0
  42. package/dist/querier/abstractQuerier.js +47 -22
  43. package/dist/querier/abstractQuerierPool.d.ts +3 -0
  44. package/dist/querier/abstractQuerierPool.js +5 -3
  45. package/dist/querier/abstractSqlQuerier.d.ts +1 -3
  46. package/dist/querier/abstractSqlQuerier.js +48 -96
  47. package/dist/querier/index.d.ts +3 -1
  48. package/dist/querier/index.js +3 -1
  49. package/dist/querier/querierContext.browser.d.ts +12 -0
  50. package/dist/querier/querierContext.browser.js +18 -0
  51. package/dist/querier/querierContext.d.ts +22 -0
  52. package/dist/querier/querierContext.js +42 -0
  53. package/dist/querier/queryError.d.ts +22 -0
  54. package/dist/querier/queryError.js +20 -0
  55. package/dist/querier/transactional.d.ts +26 -0
  56. package/dist/querier/transactional.js +43 -0
  57. package/dist/schema/index.d.ts +1 -1
  58. package/dist/schema/schemaASTBuilder.d.ts +1 -1
  59. package/dist/schema/schemaASTBuilder.js +2 -2
  60. package/dist/schema/types.d.ts +0 -21
  61. package/dist/sqlite/abstractSqliteQuerier.d.ts +9 -2
  62. package/dist/sqlite/hranaQuerier.js +20 -42
  63. package/dist/sqlite/index.d.ts +1 -0
  64. package/dist/sqlite/index.js +1 -0
  65. package/dist/sqlite/localSqliteQuerierPool.d.ts +31 -0
  66. package/dist/sqlite/localSqliteQuerierPool.js +34 -0
  67. package/dist/sqlite/nodeSqliteAdapter.d.ts +35 -0
  68. package/dist/sqlite/nodeSqliteAdapter.js +28 -0
  69. package/dist/sqlite/nodeSqliteQuerierPool.d.ts +28 -0
  70. package/dist/sqlite/nodeSqliteQuerierPool.js +29 -0
  71. package/dist/sqlite/sqliteDialect.d.ts +1 -2
  72. package/dist/sqlite/sqliteDialect.js +0 -13
  73. package/dist/sqlite/sqliteQuerierPool.d.ts +10 -23
  74. package/dist/sqlite/sqliteQuerierPool.js +9 -28
  75. package/dist/type/entity.d.ts +95 -28
  76. package/dist/type/querier.d.ts +6 -0
  77. package/dist/util/hook.util.js +1 -1
  78. package/package.json +8 -16
  79. package/dist/entity/decorator/field.d.ts +0 -2
  80. package/dist/entity/decorator/field.js +0 -7
  81. package/dist/entity/decorator/filter.d.ts +0 -7
  82. package/dist/entity/decorator/filter.js +0 -11
  83. package/dist/entity/decorator/hook.d.ts +0 -7
  84. package/dist/entity/decorator/hook.js +0 -14
  85. package/dist/entity/decorator/id.d.ts +0 -2
  86. package/dist/entity/decorator/id.js +0 -7
  87. package/dist/entity/decorator/index-decorator.d.ts +0 -22
  88. package/dist/entity/decorator/index-decorator.js +0 -26
  89. package/dist/entity/decorator/index.d.ts +0 -8
  90. package/dist/entity/decorator/index.js +0 -8
  91. package/dist/entity/decorator/relation.d.ts +0 -8
  92. package/dist/entity/decorator/relation.js +0 -19
  93. package/dist/migrate/codegen/smartRelationDetector.d.ts +0 -47
  94. package/dist/migrate/codegen/smartRelationDetector.js +0 -134
  95. package/dist/querier/decorator/index.d.ts +0 -4
  96. package/dist/querier/decorator/index.js +0 -4
  97. package/dist/querier/decorator/injectQuerier.d.ts +0 -3
  98. package/dist/querier/decorator/injectQuerier.js +0 -33
  99. package/dist/querier/decorator/log.d.ts +0 -32
  100. package/dist/querier/decorator/log.js +0 -52
  101. package/dist/querier/decorator/serialized.d.ts +0 -6
  102. package/dist/querier/decorator/serialized.js +0 -13
  103. package/dist/querier/decorator/transactional.d.ts +0 -6
  104. package/dist/querier/decorator/transactional.js +0 -48
@@ -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
+ }
@@ -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, DetectedRelation, 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';
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
@@ -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());
@@ -256,27 +256,6 @@ export interface ValidationError {
256
256
  readonly relationship?: RelationshipNode;
257
257
  readonly tables?: TableNode[];
258
258
  }
259
- /**
260
- * A detected/inferred relationship with confidence score.
261
- */
262
- export interface DetectedRelation {
263
- readonly type: RelationshipType;
264
- readonly from: {
265
- readonly table: TableNode;
266
- readonly columns: ColumnNode[];
267
- };
268
- readonly to: {
269
- readonly table: TableNode;
270
- readonly columns: ColumnNode[];
271
- };
272
- readonly through?: TableNode;
273
- /** Confidence level (0-1) */
274
- readonly confidence: number;
275
- /** How this relation was detected */
276
- readonly source: RelationshipSource;
277
- /** Suggested constraint name */
278
- readonly suggestedName?: string;
279
- }
280
259
  /**
281
260
  * Severity level for schema drift issues.
282
261
  */
@@ -1,6 +1,13 @@
1
1
  import { AbstractSqlQuerier } from '../querier/index.js';
2
- /** Values every SQLite driver accepts as a bound parameter. */
3
- export type SqliteBindValue = null | string | number | bigint | boolean | Uint8Array;
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;
4
11
  /** Header a SQLite driver returns for a statement without a `RETURNING` clause. */
5
12
  export type SqliteRunResult = {
6
13
  changes: number;
@@ -1,13 +1,3 @@
1
- var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- };
7
- var __metadata = (this && this.__metadata) || function (k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
- };
10
- import { Serialized } from '../querier/decorator/index.js';
11
1
  import { throwNoPendingTransaction, throwPendingTransaction } from '../util/index.js';
12
2
  import { AbstractSqliteQuerier } from './abstractSqliteQuerier.js';
13
3
  /**
@@ -44,24 +34,30 @@ export class HranaQuerier extends AbstractSqliteQuerier {
44
34
  return !!this.tx;
45
35
  }
46
36
  async beginTransaction(_opts) {
47
- if (this.tx) {
48
- throwPendingTransaction();
49
- }
50
- this.tx = await this.client.transaction('write');
37
+ return this.serialize(async () => {
38
+ if (this.tx) {
39
+ throwPendingTransaction();
40
+ }
41
+ this.tx = await this.client.transaction('write');
42
+ });
51
43
  }
52
44
  async commitTransaction() {
53
- if (!this.tx) {
54
- throwNoPendingTransaction();
55
- }
56
- await this.tx.commit();
57
- this.tx = undefined;
45
+ return this.serialize(async () => {
46
+ if (!this.tx) {
47
+ throwNoPendingTransaction();
48
+ }
49
+ await this.tx.commit();
50
+ this.tx = undefined;
51
+ });
58
52
  }
59
53
  async rollbackTransaction() {
60
- if (!this.tx) {
61
- throwNoPendingTransaction();
62
- }
63
- await this.tx.rollback();
64
- this.tx = undefined;
54
+ return this.serialize(async () => {
55
+ if (!this.tx) {
56
+ throwNoPendingTransaction();
57
+ }
58
+ await this.tx.rollback();
59
+ this.tx = undefined;
60
+ });
65
61
  }
66
62
  async internalRelease() {
67
63
  await super.internalRelease();
@@ -70,21 +66,3 @@ export class HranaQuerier extends AbstractSqliteQuerier {
70
66
  }
71
67
  }
72
68
  }
73
- __decorate([
74
- Serialized(),
75
- __metadata("design:type", Function),
76
- __metadata("design:paramtypes", [Object]),
77
- __metadata("design:returntype", Promise)
78
- ], HranaQuerier.prototype, "beginTransaction", null);
79
- __decorate([
80
- Serialized(),
81
- __metadata("design:type", Function),
82
- __metadata("design:paramtypes", []),
83
- __metadata("design:returntype", Promise)
84
- ], HranaQuerier.prototype, "commitTransaction", null);
85
- __decorate([
86
- Serialized(),
87
- __metadata("design:type", Function),
88
- __metadata("design:paramtypes", []),
89
- __metadata("design:returntype", Promise)
90
- ], HranaQuerier.prototype, "rollbackTransaction", null);
@@ -1,6 +1,7 @@
1
1
  export * from './abstractSqliteQuerier.js';
2
2
  export * from './hranaQuerier.js';
3
3
  export * from './hranaQuerierPool.js';
4
+ export * from './nodeSqliteQuerierPool.js';
4
5
  export * from './sqliteDialect.js';
5
6
  export * from './sqliteQuerier.js';
6
7
  export * from './sqliteQuerierPool.js';
@@ -1,6 +1,7 @@
1
1
  export * from './abstractSqliteQuerier.js';
2
2
  export * from './hranaQuerier.js';
3
3
  export * from './hranaQuerierPool.js';
4
+ export * from './nodeSqliteQuerierPool.js';
4
5
  export * from './sqliteDialect.js';
5
6
  export * from './sqliteQuerier.js';
6
7
  export * from './sqliteQuerierPool.js';
@@ -0,0 +1,31 @@
1
+ import { AbstractSqlQuerierPool } from '../querier/index.js';
2
+ import type { ExtraOptions } from '../type/index.js';
3
+ import { SqliteDialect } from './sqliteDialect.js';
4
+ import { type SqliteDatabase, SqliteQuerier } from './sqliteQuerier.js';
5
+ /** What every local SQLite pool accepts on top of its driver's own options. */
6
+ export type LocalSqlitePoolOptions = {
7
+ /**
8
+ * Paths of loadable extensions to install when the connection opens - e.g. what `sqlite-vec`'s
9
+ * `getLoadablePath()` returns, which vector search needs because SQLite itself has no vector
10
+ * functions.
11
+ */
12
+ extensions?: readonly string[];
13
+ };
14
+ /**
15
+ * Pool for a SQLite database opened in this process, whichever driver provides it.
16
+ *
17
+ * The handle is shared - SQLite gives one connection per file - but each acquisition gets its own
18
+ * querier, so transaction state stays per unit of work. Subclasses supply only {@link createDb}: the
19
+ * lifecycle, and loading the extensions on the way up, are the same for `better-sqlite3`, `bun:sqlite`
20
+ * and `node:sqlite`, and were written out once per pool before.
21
+ */
22
+ export declare abstract class AbstractLocalSqliteQuerierPool<O extends LocalSqlitePoolOptions> extends AbstractSqlQuerierPool<SqliteQuerier, SqliteDialect> {
23
+ readonly opts?: O | undefined;
24
+ private db?;
25
+ constructor(opts?: O | undefined, extra?: ExtraOptions);
26
+ /** Opens the driver's database. Extensions are loaded by the caller, not here. */
27
+ protected abstract createDb(): Promise<SqliteDatabase>;
28
+ getQuerier(): Promise<SqliteQuerier>;
29
+ private openDb;
30
+ end(): Promise<void>;
31
+ }
@@ -0,0 +1,34 @@
1
+ import { AbstractSqlQuerierPool } from '../querier/index.js';
2
+ import { SqliteDialect } from './sqliteDialect.js';
3
+ import { SqliteQuerier } from './sqliteQuerier.js';
4
+ /**
5
+ * Pool for a SQLite database opened in this process, whichever driver provides it.
6
+ *
7
+ * The handle is shared - SQLite gives one connection per file - but each acquisition gets its own
8
+ * querier, so transaction state stays per unit of work. Subclasses supply only {@link createDb}: the
9
+ * lifecycle, and loading the extensions on the way up, are the same for `better-sqlite3`, `bun:sqlite`
10
+ * and `node:sqlite`, and were written out once per pool before.
11
+ */
12
+ export class AbstractLocalSqliteQuerierPool extends AbstractSqlQuerierPool {
13
+ opts;
14
+ db;
15
+ constructor(opts, extra) {
16
+ super(new SqliteDialect({ namingStrategy: extra?.namingStrategy }), extra);
17
+ this.opts = opts;
18
+ }
19
+ async getQuerier() {
20
+ this.db ??= await this.openDb();
21
+ return new SqliteQuerier(this.db, this.dialect, this.extra);
22
+ }
23
+ async openDb() {
24
+ const db = await this.createDb();
25
+ for (const extension of this.opts?.extensions ?? []) {
26
+ db.loadExtension(extension);
27
+ }
28
+ return db;
29
+ }
30
+ async end() {
31
+ await this.db?.close();
32
+ this.db = undefined;
33
+ }
34
+ }
@@ -0,0 +1,35 @@
1
+ import type { SqliteBindValue } from './abstractSqliteQuerier.js';
2
+ import type { SqliteDatabase } from './sqliteQuerier.js';
3
+ /**
4
+ * A `node:sqlite` statement: better-sqlite3-shaped, except it describes columns instead of reporting
5
+ * `reader`, and types `changes` as possibly `bigint` where better-sqlite3 always answers a `number`.
6
+ */
7
+ type NodeSqliteStatement = {
8
+ columns(): readonly unknown[];
9
+ all(...values: SqliteBindValue[]): unknown[];
10
+ run(...values: SqliteBindValue[]): {
11
+ changes: number | bigint;
12
+ lastInsertRowid: number | bigint;
13
+ };
14
+ iterate(...values: SqliteBindValue[]): Iterable<unknown>;
15
+ };
16
+ /**
17
+ * Structural subset of `node:sqlite`'s `DatabaseSync`, declared locally for the same reason the rest
18
+ * of this folder declares its driver shapes: nothing here depends on `@types/node` being in scope.
19
+ */
20
+ export type NodeSqliteDatabase = {
21
+ prepare(sql: string): NodeSqliteStatement;
22
+ exec(sql: string): void;
23
+ loadExtension(path: string): void;
24
+ close(): void;
25
+ };
26
+ /**
27
+ * Presents a `node:sqlite` handle as a {@link SqliteDatabase}.
28
+ *
29
+ * @remarks Its statements expose no `reader`, so without deriving one every `RETURNING` statement
30
+ * would take the `run()` path, which discards returned rows, and inserts would report no ids.
31
+ * `columns()` is non-empty for exactly the statements better-sqlite3 marks as readers, including
32
+ * `INSERT ... RETURNING` and `DELETE ... RETURNING`.
33
+ */
34
+ export declare function adaptNodeSqlite(db: NodeSqliteDatabase): SqliteDatabase;
35
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Presents a `node:sqlite` handle as a {@link SqliteDatabase}.
3
+ *
4
+ * @remarks Its statements expose no `reader`, so without deriving one every `RETURNING` statement
5
+ * would take the `run()` path, which discards returned rows, and inserts would report no ids.
6
+ * `columns()` is non-empty for exactly the statements better-sqlite3 marks as readers, including
7
+ * `INSERT ... RETURNING` and `DELETE ... RETURNING`.
8
+ */
9
+ export function adaptNodeSqlite(db) {
10
+ return {
11
+ prepare: (sql) => {
12
+ const stmt = db.prepare(sql);
13
+ return {
14
+ reader: stmt.columns().length > 0,
15
+ all: (...values) => stmt.all(...values),
16
+ // `changes` is narrowed to `number` to match every other driver; a row count cannot exceed
17
+ // the safe-integer range, so nothing is lost. `lastInsertRowid` keeps its `bigint` arm.
18
+ run: (...values) => {
19
+ const { changes, lastInsertRowid } = stmt.run(...values);
20
+ return { changes: Number(changes), lastInsertRowid };
21
+ },
22
+ iterate: (...values) => stmt.iterate(...values),
23
+ };
24
+ },
25
+ loadExtension: (path) => db.loadExtension(path),
26
+ close: () => db.close(),
27
+ };
28
+ }
@@ -0,0 +1,28 @@
1
+ import type { ExtraOptions } from '../type/index.js';
2
+ import { AbstractLocalSqliteQuerierPool, type LocalSqlitePoolOptions } from './localSqliteQuerierPool.js';
3
+ import type { SqliteDatabase } from './sqliteQuerier.js';
4
+ /**
5
+ * The `DatabaseSync` options worth surfacing, plus the loadable extensions to install. Declared here
6
+ * rather than imported from `node:sqlite` so this module needs no ambient Node types; unknown keys
7
+ * are ignored by the driver, so the list only has to cover what callers actually set.
8
+ */
9
+ export type NodeSqlitePoolOptions = LocalSqlitePoolOptions & {
10
+ readonly readOnly?: boolean;
11
+ readonly enableForeignKeyConstraints?: boolean;
12
+ /** Milliseconds a locked database is retried before `SQLITE_BUSY`. */
13
+ readonly timeout?: number;
14
+ };
15
+ /**
16
+ * Pool backed by Node's built-in `node:sqlite`, so SQLite works with **no dependency at all** rather
17
+ * than requiring the `better-sqlite3` native build. Use {@link Sqlite3QuerierPool} instead when you
18
+ * want `better-sqlite3`, or are on Bun.
19
+ *
20
+ * @remarks `node:sqlite` needs no CLI flag from Node 22.13, and is still a release candidate
21
+ * (stability 1.2) as of Node 26, so `better-sqlite3` via {@link Sqlite3QuerierPool} remains the
22
+ * faster option for read-heavy work.
23
+ */
24
+ export declare class NodeSqliteQuerierPool extends AbstractLocalSqliteQuerierPool<NodeSqlitePoolOptions> {
25
+ readonly filename: string;
26
+ constructor(filename?: string, opts?: NodeSqlitePoolOptions, extra?: ExtraOptions);
27
+ protected createDb(): Promise<SqliteDatabase>;
28
+ }
@@ -0,0 +1,29 @@
1
+ import { AbstractLocalSqliteQuerierPool } from './localSqliteQuerierPool.js';
2
+ import { adaptNodeSqlite } from './nodeSqliteAdapter.js';
3
+ /**
4
+ * Pool backed by Node's built-in `node:sqlite`, so SQLite works with **no dependency at all** rather
5
+ * than requiring the `better-sqlite3` native build. Use {@link Sqlite3QuerierPool} instead when you
6
+ * want `better-sqlite3`, or are on Bun.
7
+ *
8
+ * @remarks `node:sqlite` needs no CLI flag from Node 22.13, and is still a release candidate
9
+ * (stability 1.2) as of Node 26, so `better-sqlite3` via {@link Sqlite3QuerierPool} remains the
10
+ * faster option for read-heavy work.
11
+ */
12
+ export class NodeSqliteQuerierPool extends AbstractLocalSqliteQuerierPool {
13
+ filename;
14
+ constructor(filename = ':memory:', opts, extra) {
15
+ super(opts, extra);
16
+ this.filename = filename;
17
+ }
18
+ async createDb() {
19
+ const { DatabaseSync } = await import('node:sqlite');
20
+ const { extensions, ...driverOpts } = this.opts ?? {};
21
+ const nodeDb = new DatabaseSync(this.filename, {
22
+ ...driverOpts,
23
+ // `node:sqlite` refuses `loadExtension` unless the database was opened with this on.
24
+ ...(extensions?.length ? { allowExtension: true } : undefined),
25
+ });
26
+ nodeDb.exec('PRAGMA journal_mode = WAL');
27
+ return adaptNodeSqlite(nodeDb);
28
+ }
29
+ }
@@ -1,5 +1,5 @@
1
1
  import { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
2
- import type { DialectFeatures, EntityMeta, FieldOptions, QueryConflictPaths, QueryContext, QuerySizeComparisonOps, QueryTextSearchOptions, Type, VectorDistance } from '../type/index.js';
2
+ import type { DialectFeatures, EntityMeta, FieldOptions, QueryContext, QuerySizeComparisonOps, QueryTextSearchOptions, Type, VectorDistance } from '../type/index.js';
3
3
  export declare class SqliteDialect extends AbstractSqlDialect {
4
4
  /** Default {@link DialectFeatures} for SQLite and SQLite-derived dialects. */
5
5
  protected readonly featureDefaults: DialectFeatures;
@@ -52,7 +52,6 @@ export declare class SqliteDialect extends AbstractSqlDialect {
52
52
  protected jsonElemRef(alias: string, field?: string, asJson?: boolean): string;
53
53
  protected getJsonPathScalarExpr(escapedColumn: string, jsonPathStr: string): string;
54
54
  protected numericCast(expr: string): string;
55
- upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[]): void;
56
55
  protected jsonCast(operand: string): string;
57
56
  /**
58
57
  * `json_replace` leaves an absent key (and a NULL column) untouched. Elements are read back
@@ -1,6 +1,5 @@
1
1
  import { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
2
2
  import { JSON_ELEM_ALIAS_PREFIX, JSON_PULL_ALIAS, jsonAssignCall, jsonElemExists, jsonPath, jsonRemoveCall, jsonSetTarget, } from '../dialect/jsonSql.js';
3
- import { getMeta } from '../entity/index.js';
4
3
  export class SqliteDialect extends AbstractSqlDialect {
5
4
  /** Default {@link DialectFeatures} for SQLite and SQLite-derived dialects. */
6
5
  featureDefaults = {
@@ -109,18 +108,6 @@ export class SqliteDialect extends AbstractSqlDialect {
109
108
  numericCast(expr) {
110
109
  return `CAST(${expr} AS REAL)`;
111
110
  }
112
- upsert(ctx, entity, conflictPaths, payload) {
113
- const meta = getMeta(entity);
114
- const updateCtx = this.createContext();
115
- const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload, (name) => `EXCLUDED.${name}`);
116
- const keysStr = this.getUpsertConflictPathsStr(meta, conflictPaths);
117
- const onConflict = update ? `DO UPDATE SET ${update}` : 'DO NOTHING';
118
- // Use the base (non-RETURNING) insert here: the appended RETURNING below would otherwise
119
- // be doubled by `this.insert`'s own.
120
- this.appendInsertValues(ctx, entity, payload);
121
- ctx.append(` ON CONFLICT (${keysStr}) ${onConflict} ${this.returningId(entity)}`);
122
- ctx.pushValue(...updateCtx.values);
123
- }
124
111
  jsonCast(operand) {
125
112
  return `json(${operand})`;
126
113
  }
@@ -1,28 +1,15 @@
1
1
  import type { Options } from 'better-sqlite3';
2
- import { AbstractSqlQuerierPool } from '../querier/index.js';
3
2
  import type { ExtraOptions } from '../type/index.js';
4
- import { SqliteDialect } from './sqliteDialect.js';
5
- import { SqliteQuerier } from './sqliteQuerier.js';
3
+ import { AbstractLocalSqliteQuerierPool, type LocalSqlitePoolOptions } from './localSqliteQuerierPool.js';
4
+ import type { SqliteDatabase } from './sqliteQuerier.js';
6
5
  /** Driver options, plus the loadable extensions to install on the connection. */
7
- export type Sqlite3PoolOptions = Options & {
8
- /**
9
- * Paths of loadable extensions to install when the connection opens - e.g. what `sqlite-vec`'s
10
- * `getLoadablePath()` returns, which vector search needs because SQLite itself has no vector
11
- * functions.
12
- */
13
- extensions?: readonly string[];
14
- };
15
- export declare class Sqlite3QuerierPool extends AbstractSqlQuerierPool<SqliteQuerier, SqliteDialect> {
6
+ export type Sqlite3PoolOptions = Options & LocalSqlitePoolOptions;
7
+ /**
8
+ * Pool for `better-sqlite3`, or `bun:sqlite` when running under Bun - the same file, through whichever
9
+ * driver the runtime provides.
10
+ */
11
+ export declare class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool<Sqlite3PoolOptions> {
16
12
  readonly filename: string | Buffer;
17
- readonly opts?: Sqlite3PoolOptions | undefined;
18
- private db?;
19
- constructor(filename?: string | Buffer, opts?: Sqlite3PoolOptions | undefined, extra?: ExtraOptions);
20
- /**
21
- * The database handle is shared (single connection), but each acquisition gets its own querier
22
- * so transaction state stays per unit of work.
23
- */
24
- getQuerier(): Promise<SqliteQuerier>;
25
- private openDb;
26
- private openDriverDb;
27
- end(): Promise<void>;
13
+ constructor(filename?: string | Buffer, opts?: Sqlite3PoolOptions, extra?: ExtraOptions);
14
+ protected createDb(): Promise<SqliteDatabase>;
28
15
  }