kysely 0.29.8 → 0.29.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://stand-with-ukraine.pp.ua)
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/kysely?style=flat&label=latest)](https://github.com/kysely-org/kysely/releases/latest)
4
- [![Socket Badge](https://badge.socket.dev/npm/package/kysely/0.29.8)](https://socket.dev/npm/package/kysely/overview/0.29.8)
4
+ [![Socket Badge](https://badge.socket.dev/npm/package/kysely/0.29.9)](https://socket.dev/npm/package/kysely/overview/0.29.9)
5
5
  [![Tests](https://github.com/kysely-org/kysely/actions/workflows/test.yml/badge.svg)](https://github.com/kysely-org/kysely)
6
6
  [![License](https://img.shields.io/github/license/kysely-org/kysely?style=flat)](https://github.com/kysely-org/kysely/blob/master/LICENSE)
7
7
  [![Issues](https://img.shields.io/github/issues-closed/kysely-org/kysely?logo=github)](https://github.com/kysely-org/kysely/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)
@@ -1,8 +1,9 @@
1
1
  /// <reference types="./pglite-driver.d.ts" />
2
+ import { AbortedTransactionError } from '../../driver/aborted-transaction-error.js';
2
3
  import { parseSavepointCommand } from '../../parser/savepoint-parser.js';
3
4
  import { waitOrAbort, } from '../../util/abort.js';
4
5
  import { Deferred } from '../../util/deferred.js';
5
- import { freeze, isFunction } from '../../util/object-utils.js';
6
+ import { freeze, isFunction, isObject } from '../../util/object-utils.js';
6
7
  import { createQueryId } from '../../util/query-id.js';
7
8
  import { extendStackTrace } from '../../util/stack-trace-utils.js';
8
9
  const PRIVATE_BEGIN_TRANSACTION_METHOD = Symbol();
@@ -102,11 +103,22 @@ class PGliteConnection {
102
103
  }
103
104
  async [PRIVATE_COMMIT_TRANSACTION_METHOD]() {
104
105
  this.#commitTransaction?.();
105
- await this.#transactionClosedPromise;
106
- this.#commitTransaction = undefined;
107
- this.#rollbackTransaction = undefined;
108
- this.#transaction = undefined;
109
- this.#transactionClosedPromise = undefined;
106
+ try {
107
+ await this.#transactionClosedPromise;
108
+ }
109
+ catch (error) {
110
+ // https://github.com/electric-sql/pglite/blob/bac163ecc92e968adda68864b74b7e3d65a68e97/packages/pglite/src/errors.ts#L11-L17
111
+ if (isObject(error) && 'code' in error && error.code === '25P02') {
112
+ throw new AbortedTransactionError({ cause: error });
113
+ }
114
+ throw error;
115
+ }
116
+ finally {
117
+ this.#commitTransaction = undefined;
118
+ this.#rollbackTransaction = undefined;
119
+ this.#transaction = undefined;
120
+ this.#transactionClosedPromise = undefined;
121
+ }
110
122
  }
111
123
  async [PRIVATE_ROLLBACK_TRANSACTION_METHOD]() {
112
124
  this.#rollbackTransaction?.();
@@ -5,7 +5,7 @@ declare const ABORTED_TRANSACTION_ERROR: unique symbol;
5
5
  export declare class AbortedTransactionError extends Error {
6
6
  name: string;
7
7
  readonly [ABORTED_TRANSACTION_ERROR] = true;
8
- constructor();
8
+ constructor(options?: ErrorOptions);
9
9
  }
10
10
  export declare function isAbortedTransactionError(error: unknown): error is AbortedTransactionError;
11
11
  export {};
@@ -7,8 +7,8 @@ const ABORTED_TRANSACTION_ERROR = Symbol.for('kysely.AbortedTransactionError');
7
7
  export class AbortedTransactionError extends Error {
8
8
  name = 'AbortedTransactionError';
9
9
  [ABORTED_TRANSACTION_ERROR] = true;
10
- constructor() {
11
- super('Transaction was rolled back instead of committed');
10
+ constructor(options) {
11
+ super('Transaction was rolled back instead of committed', options);
12
12
  }
13
13
  }
14
14
  export function isAbortedTransactionError(error) {
package/dist/kysely.d.ts CHANGED
@@ -477,6 +477,54 @@ export declare class Kysely<DB> extends QueryCreator<DB> implements QueryExecuto
477
477
  /**
478
478
  * Executes a given compiled query or query builder.
479
479
  *
480
+ * Returns a {@link QueryResult} containing `rows` and any metadata provided
481
+ * by the dialect, such as `numAffectedRows` or `insertId`.
482
+ * Unlike a query builder's `execute`, this method doesn't wrap write metadata
483
+ * in an {@link InsertResult}, {@link UpdateResult} or {@link DeleteResult}.
484
+ * Without `returning` or `output`, a write normally has an empty `rows` array.
485
+ *
486
+ * ### Examples
487
+ *
488
+ * ```ts
489
+ * const query = db
490
+ * .selectFrom('person')
491
+ * .select(['id', 'first_name'])
492
+ * .where('id', '=', 123)
493
+ * .compile()
494
+ *
495
+ * const { rows } = await db.executeQuery(query)
496
+ * console.log(rows[0]?.first_name)
497
+ * ```
498
+ *
499
+ * The generated SQL (PostgreSQL):
500
+ *
501
+ * ```sql
502
+ * select "id", "first_name"
503
+ * from "person"
504
+ * where "id" = $1
505
+ * ```
506
+ *
507
+ * Read metadata from a write query:
508
+ *
509
+ * ```ts
510
+ * const query = db
511
+ * .updateTable('person')
512
+ * .set({ first_name: 'Jennifer' })
513
+ * .where('id', '=', 123)
514
+ * .compile()
515
+ *
516
+ * const { numAffectedRows } = await db.executeQuery(query)
517
+ * console.log(numAffectedRows)
518
+ * ```
519
+ *
520
+ * The generated SQL (PostgreSQL):
521
+ *
522
+ * ```sql
523
+ * update "person"
524
+ * set "first_name" = $1
525
+ * where "id" = $2
526
+ * ```
527
+ *
480
528
  * See {@link https://github.com/kysely-org/kysely/blob/master/site/docs/recipes/0004-splitting-query-building-and-execution.md#execute-compiled-queries splitting build, compile and execute code recipe} for more information.
481
529
  */
482
530
  executeQuery<R>(query: CompiledQuery<R> | Compilable<R>, options?: AbortableQueryOptions): Promise<QueryResult<R>>;
package/dist/kysely.js CHANGED
@@ -539,6 +539,54 @@ export class Kysely extends QueryCreator {
539
539
  /**
540
540
  * Executes a given compiled query or query builder.
541
541
  *
542
+ * Returns a {@link QueryResult} containing `rows` and any metadata provided
543
+ * by the dialect, such as `numAffectedRows` or `insertId`.
544
+ * Unlike a query builder's `execute`, this method doesn't wrap write metadata
545
+ * in an {@link InsertResult}, {@link UpdateResult} or {@link DeleteResult}.
546
+ * Without `returning` or `output`, a write normally has an empty `rows` array.
547
+ *
548
+ * ### Examples
549
+ *
550
+ * ```ts
551
+ * const query = db
552
+ * .selectFrom('person')
553
+ * .select(['id', 'first_name'])
554
+ * .where('id', '=', 123)
555
+ * .compile()
556
+ *
557
+ * const { rows } = await db.executeQuery(query)
558
+ * console.log(rows[0]?.first_name)
559
+ * ```
560
+ *
561
+ * The generated SQL (PostgreSQL):
562
+ *
563
+ * ```sql
564
+ * select "id", "first_name"
565
+ * from "person"
566
+ * where "id" = $1
567
+ * ```
568
+ *
569
+ * Read metadata from a write query:
570
+ *
571
+ * ```ts
572
+ * const query = db
573
+ * .updateTable('person')
574
+ * .set({ first_name: 'Jennifer' })
575
+ * .where('id', '=', 123)
576
+ * .compile()
577
+ *
578
+ * const { numAffectedRows } = await db.executeQuery(query)
579
+ * console.log(numAffectedRows)
580
+ * ```
581
+ *
582
+ * The generated SQL (PostgreSQL):
583
+ *
584
+ * ```sql
585
+ * update "person"
586
+ * set "first_name" = $1
587
+ * where "id" = $2
588
+ * ```
589
+ *
542
590
  * See {@link https://github.com/kysely-org/kysely/blob/master/site/docs/recipes/0004-splitting-query-building-and-execution.md#execute-compiled-queries splitting build, compile and execute code recipe} for more information.
543
591
  */
544
592
  async executeQuery(query, options) {
@@ -2,11 +2,21 @@ import type { Migration, MigrationProvider } from './migrator.js';
2
2
  /**
3
3
  * Reads all migrations from a folder.
4
4
  *
5
+ * Loads `.js`, `.ts`, `.mjs`, `.mts`, `.cjs` and `.cts` files, excluding
6
+ * TypeScript declaration files. Loading TypeScript requires a runtime or
7
+ * loader that supports it. Subdirectories are not scanned.
8
+ *
9
+ * A module must export an `up` function, either as a named export or as part
10
+ * of a default export object. The filename without its extension is used as
11
+ * the migration name. See {@link FileMigrationProviderProps.onFileIgnored}
12
+ * to report files that aren't loaded.
13
+ *
5
14
  * ### Examples
6
15
  *
7
16
  * ```ts
8
17
  * import { promises as fs } from 'node:fs'
9
18
  * import path from 'node:path'
19
+ * import { FileMigrationProvider } from 'kysely/migration'
10
20
  *
11
21
  * new FileMigrationProvider({
12
22
  * fs,
@@ -43,9 +53,65 @@ export interface FileMigrationProviderPath {
43
53
  join(...path: string[]): string;
44
54
  }
45
55
  export interface FileMigrationProviderProps {
56
+ /**
57
+ * Provides the directory listing. For Node.js, use `promises` from `node:fs`.
58
+ */
46
59
  fs: FileMigrationProviderFS;
60
+ /**
61
+ * Overrides how migration modules are imported. Receives the joined file path
62
+ * and returns the module exports. Defaults to a dynamic `import`.
63
+ *
64
+ * ### Examples
65
+ *
66
+ * Use a file URL when importing absolute paths in Node.js:
67
+ *
68
+ * ```ts
69
+ * import { promises as fs } from 'node:fs'
70
+ * import path from 'node:path'
71
+ * import { pathToFileURL } from 'node:url'
72
+ * import { FileMigrationProvider } from 'kysely/migration'
73
+ *
74
+ * const provider = new FileMigrationProvider({
75
+ * fs,
76
+ * path,
77
+ * migrationFolder: path.resolve('migrations'),
78
+ * import: (filePath) => import(pathToFileURL(filePath).href),
79
+ * })
80
+ * ```
81
+ */
47
82
  import?(module: string): Promise<any>;
83
+ /**
84
+ * The path to the migration directory. Use an absolute path to avoid
85
+ * depending on the current working directory.
86
+ */
48
87
  migrationFolder: string;
88
+ /**
89
+ * Called when a directory entry is ignored.
90
+ *
91
+ * `Extension` means the filename doesn't have a supported extension.
92
+ * `NotMigration` means the imported module has no supported `up` export.
93
+ * Import errors are thrown instead of being reported through this callback.
94
+ *
95
+ * ### Examples
96
+ *
97
+ * ```ts
98
+ * import { promises as fs } from 'node:fs'
99
+ * import path from 'node:path'
100
+ * import { FileMigrationProvider } from 'kysely/migration'
101
+ *
102
+ * const provider = new FileMigrationProvider({
103
+ * fs,
104
+ * path,
105
+ * migrationFolder: path.resolve('migrations'),
106
+ * onFileIgnored(fileName, reason) {
107
+ * console.log(`Ignored ${fileName}: ${reason}`)
108
+ * },
109
+ * })
110
+ * ```
111
+ */
49
112
  onFileIgnored?(fileName: string, reason: 'Extension' | 'NotMigration'): void;
113
+ /**
114
+ * Provides path joining. For Node.js, use `node:path`.
115
+ */
50
116
  path: FileMigrationProviderPath;
51
117
  }
@@ -3,11 +3,21 @@ import { isFunction, isObject } from '../util/object-utils.js';
3
3
  /**
4
4
  * Reads all migrations from a folder.
5
5
  *
6
+ * Loads `.js`, `.ts`, `.mjs`, `.mts`, `.cjs` and `.cts` files, excluding
7
+ * TypeScript declaration files. Loading TypeScript requires a runtime or
8
+ * loader that supports it. Subdirectories are not scanned.
9
+ *
10
+ * A module must export an `up` function, either as a named export or as part
11
+ * of a default export object. The filename without its extension is used as
12
+ * the migration name. See {@link FileMigrationProviderProps.onFileIgnored}
13
+ * to report files that aren't loaded.
14
+ *
6
15
  * ### Examples
7
16
  *
8
17
  * ```ts
9
18
  * import { promises as fs } from 'node:fs'
10
19
  * import path from 'node:path'
20
+ * import { FileMigrationProvider } from 'kysely/migration'
11
21
  *
12
22
  * new FileMigrationProvider({
13
23
  * fs,
@@ -386,6 +386,16 @@ export interface MigrationInfo {
386
386
  /**
387
387
  * When was the migration executed.
388
388
  *
389
+ * In most cases, this is the time on the machine running the migrator when
390
+ * the migration was applied (as reported by `Date.now()`), not the database
391
+ * server's time.
392
+ * Execution timestamps are used to determine the order in which migrations
393
+ * were executed, so they never go backwards. If the machine clock is behind
394
+ * the latest recorded execution timestamp (e.g. it was stepped back by an NTP
395
+ * sync, it drifted from the clock of another machine running migrations, or
396
+ * it was ahead when a previous migration was applied), the migration is
397
+ * recorded 1 millisecond after the latest recorded execution timestamp instead.
398
+ *
389
399
  * If this is undefined, the migration hasn't been executed yet.
390
400
  */
391
401
  executedAt?: Date;
@@ -1,12 +1,14 @@
1
1
  /// <reference types="./migrator.d.ts" />
2
2
  import { NoopPlugin } from '../plugin/noop-plugin.js';
3
3
  import { WithSchemaPlugin } from '../plugin/with-schema/with-schema-plugin.js';
4
+ import { warnOnce } from '../util/log-once.js';
4
5
  import { freeze, getLast, isObject } from '../util/object-utils.js';
5
6
  export const DEFAULT_MIGRATION_TABLE = 'kysely_migration';
6
7
  export const DEFAULT_MIGRATION_LOCK_TABLE = 'kysely_migration_lock';
7
8
  export const DEFAULT_ALLOW_UNORDERED_MIGRATIONS = false;
8
9
  export const MIGRATION_LOCK_ID = 'migration_lock';
9
10
  export const NO_MIGRATIONS = freeze({ __noMigrations__: true });
11
+ const CLOCK_BEHIND_WARNING_THRESHOLD_MILLISECONDS = 1_000;
10
12
  /**
11
13
  * A class for running migrations.
12
14
  *
@@ -448,7 +450,7 @@ export class Migrator {
448
450
  }
449
451
  async #getState(db) {
450
452
  const migrations = await this.#resolveMigrations();
451
- const executedMigrations = await this.#getExecutedMigrations(db);
453
+ const { executedMigrations, maxExecutedAt } = await this.#getExecutedMigrations(db);
452
454
  this.#ensureNoMissingMigrations(migrations, executedMigrations);
453
455
  if (!this.#allowUnorderedMigrations) {
454
456
  this.#ensureMigrationsInOrder(migrations, executedMigrations);
@@ -458,6 +460,7 @@ export class Migrator {
458
460
  migrations,
459
461
  executedMigrations,
460
462
  lastMigration: getLast(executedMigrations),
463
+ maxExecutedAt,
461
464
  pendingMigrations,
462
465
  });
463
466
  }
@@ -476,22 +479,44 @@ export class Migrator {
476
479
  }));
477
480
  }
478
481
  async #getExecutedMigrations(db) {
479
- const executedMigrations = await db
482
+ const rows = await db
480
483
  .withPlugin(this.#schemaPlugin)
481
484
  .selectFrom(this.#migrationTable)
482
485
  .select(['name', 'timestamp'])
483
486
  .$narrowType()
484
487
  .execute();
488
+ if (rows.length === 0) {
489
+ return { executedMigrations: [], maxExecutedAt: 0 };
490
+ }
491
+ if (rows.length === 1) {
492
+ const [{ name, timestamp }] = rows;
493
+ return {
494
+ executedMigrations: [name],
495
+ maxExecutedAt: this.#getExecutedAt(name, timestamp),
496
+ };
497
+ }
485
498
  const nameComparator = this.#props.nameComparator || ((a, b) => a.localeCompare(b));
486
- return (executedMigrations
499
+ let maxExecutedAt = 0;
500
+ const executedMigrations = rows
487
501
  // https://github.com/kysely-org/kysely/issues/843
488
502
  .sort((a, b) => {
489
- if (a.timestamp === b.timestamp) {
503
+ const aExecutedAt = this.#getExecutedAt(a.name, a.timestamp);
504
+ const bExecutedAt = this.#getExecutedAt(b.name, b.timestamp);
505
+ maxExecutedAt = Math.max(maxExecutedAt, aExecutedAt, bExecutedAt);
506
+ if (aExecutedAt === bExecutedAt) {
490
507
  return nameComparator(a.name, b.name);
491
508
  }
492
- return (new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
509
+ return aExecutedAt - bExecutedAt;
493
510
  })
494
- .map((it) => it.name));
511
+ .map((it) => it.name);
512
+ return { executedMigrations, maxExecutedAt };
513
+ }
514
+ #getExecutedAt(name, timestamp) {
515
+ const executedAt = new Date(timestamp).getTime();
516
+ if (Number.isNaN(executedAt)) {
517
+ throw new Error(`corrupted migrations: previously executed migration ${name} has an invalid timestamp ${timestamp}`);
518
+ }
519
+ return executedAt;
495
520
  }
496
521
  #ensureNoMissingMigrations(migrations, executedMigrations) {
497
522
  // Ensure all executed migrations exist in the `migrations` list.
@@ -563,16 +588,18 @@ export class Migrator {
563
588
  status: 'NotExecuted',
564
589
  };
565
590
  });
591
+ let { maxExecutedAt } = state;
566
592
  for (let i = 0; i < results.length; i++) {
567
593
  const migration = state.pendingMigrations[i];
568
594
  try {
569
595
  await migration.up(db);
596
+ maxExecutedAt = this.#getNextExecutedAt(maxExecutedAt);
570
597
  await db
571
598
  .withPlugin(this.#schemaPlugin)
572
599
  .insertInto(this.#migrationTable)
573
600
  .values({
574
601
  name: migration.name,
575
- timestamp: new Date().toISOString(),
602
+ timestamp: new Date(maxExecutedAt).toISOString(),
576
603
  })
577
604
  .execute();
578
605
  results[i] = {
@@ -601,6 +628,17 @@ export class Migrator {
601
628
  }
602
629
  await qb.execute();
603
630
  }
631
+ // https://github.com/kysely-org/kysely/issues/2244
632
+ #getNextExecutedAt(maxExecutedAt) {
633
+ const now = Date.now();
634
+ if (now > maxExecutedAt) {
635
+ return now;
636
+ }
637
+ if (maxExecutedAt - now >= CLOCK_BEHIND_WARNING_THRESHOLD_MILLISECONDS) {
638
+ warnOnce('The machine clock is behind the latest migration `executedAt`. Recording migrations 1 millisecond after it instead.');
639
+ }
640
+ return maxExecutedAt + 1;
641
+ }
604
642
  }
605
643
  class MigrationResultSetError extends Error {
606
644
  #resultSet;
@@ -78,10 +78,7 @@ export class WithSchemaTransformer extends OperationNodeTransformer {
78
78
  return {
79
79
  ...super.transformSelectModifier({ ...node, of: undefined }, queryId),
80
80
  of: node.of?.map((item) => TableNode.is(item) && !item.table.schema
81
- ? {
82
- ...item,
83
- table: this.transformIdentifier(item.table.identifier, queryId),
84
- }
81
+ ? this.#transformTableWithoutSchema(item, queryId)
85
82
  : this.transformNode(item, queryId)),
86
83
  };
87
84
  }
@@ -89,12 +86,18 @@ export class WithSchemaTransformer extends OperationNodeTransformer {
89
86
  return SCHEMALESS_FUNCTIONS[node.func]
90
87
  ? node[argsKey].map((arg) => !TableNode.is(arg) || arg.table.schema
91
88
  ? this.transformNode(arg, queryId)
92
- : {
93
- ...arg,
94
- table: this.transformIdentifier(arg.table.identifier, queryId),
95
- })
89
+ : this.#transformTableWithoutSchema(arg, queryId))
96
90
  : this.transformNodeList(node[argsKey], queryId);
97
91
  }
92
+ #transformTableWithoutSchema(node, queryId) {
93
+ return {
94
+ ...node,
95
+ table: {
96
+ ...node.table,
97
+ identifier: this.transformIdentifier(node.table.identifier, queryId),
98
+ },
99
+ };
100
+ }
98
101
  #collectSchemableIds(node) {
99
102
  const schemableIds = new Set();
100
103
  if ('name' in node && node.name && SchemableIdentifierNode.is(node.name)) {
@@ -73,7 +73,7 @@ export declare class JSONPathBuilder<S, O = S> {
73
73
  * select "nicknames"->>'$[#-1]' as `last_nickname` from `person`
74
74
  * ```
75
75
  */
76
- at<I extends (any[] extends O ? number | 'last' | `#-${number}` : never), O2 = null | NonNullable<NonNullable<O>[keyof NonNullable<O> & number]>>(index: `${I}` extends `${any}.${any}` | `#--${any}` ? never : I): TraversedJSONPathBuilder<S, O2>;
76
+ at<I extends (any[] extends O ? number | 'last' | `#-${number}` : never)>(index: `${I}` extends `${any}.${any}` | `#--${any}` ? never : I): TraversedJSONPathBuilder<S, JSONArrayElementType<O>>;
77
77
  /**
78
78
  * Access a property of a JSON object.
79
79
  *
@@ -123,7 +123,7 @@ export declare class JSONPathBuilder<S, O = S> {
123
123
  * select "profile"->'addresses'->0->'city' as "city" from "person"
124
124
  * ```
125
125
  */
126
- key<K extends (any[] extends O ? never : O extends object ? keyof NonNullable<O> & string : never), O2 = undefined extends O ? null | NonNullable<NonNullable<O>[K]> : null extends O ? null | NonNullable<NonNullable<O>[K]> : string extends keyof NonNullable<O> ? null | NonNullable<NonNullable<O>[K]> : NonNullable<O>[K]>(key: K): TraversedJSONPathBuilder<S, O2>;
126
+ key<K extends (any[] extends O ? never : O extends object ? keyof NonNullable<O> & string : never)>(key: K): TraversedJSONPathBuilder<S, JSONObjectValueType<O, K>>;
127
127
  }
128
128
  export declare class TraversedJSONPathBuilder<S, O> extends JSONPathBuilder<S, O> implements AliasableExpression<O> {
129
129
  #private;
@@ -292,3 +292,6 @@ export declare class AliasedJSONPathBuilder<O, A extends string> implements Alia
292
292
  */
293
293
  toOperationNode(): AliasNode;
294
294
  }
295
+ type JSONArrayElementType<O> = null | NonNullable<NonNullable<O>[keyof NonNullable<O> & number]>;
296
+ type JSONObjectValueType<O, K extends keyof NonNullable<O>> = undefined extends O ? null | NonNullable<NonNullable<O>[K]> : null extends O ? null | NonNullable<NonNullable<O>[K]> : string extends keyof NonNullable<O> ? null | NonNullable<NonNullable<O>[K]> : NonNullable<O>[K];
297
+ export {};
@@ -1,13 +1,85 @@
1
1
  import { type RootOperationNode } from '../operation-node/root-operation-node.js';
2
2
  import { type QueryId } from '../util/query-id.js';
3
+ /**
4
+ * A SQL query compiled for a specific dialect, together with its parameters
5
+ * and operation node tree.
6
+ *
7
+ * The output type `O` describes a result row. It doesn't validate or convert
8
+ * values returned by the database driver.
9
+ *
10
+ * Query builders create compiled queries with {@link Compilable.compile | compile()},
11
+ * and {@link RawBuilder}s with `compile(db)`. Compiling doesn't execute the query.
12
+ *
13
+ * See {@link Kysely.executeQuery} for execution and {@link CompiledQuery.raw}
14
+ * for constructing a query from an existing SQL string.
15
+ *
16
+ * ### Examples
17
+ *
18
+ * ```ts
19
+ * const compiledQuery = db
20
+ * .selectFrom('person')
21
+ * .select('first_name')
22
+ * .where('id', '=', 123)
23
+ * .compile()
24
+ *
25
+ * console.log(compiledQuery.sql) // select "first_name" from "person" where "id" = $1
26
+ * console.log(compiledQuery.parameters) // [123]
27
+ * ```
28
+ */
3
29
  export interface CompiledQuery<O = unknown> {
30
+ /**
31
+ * The operation node tree used to compile the query, after query plugins have run.
32
+ */
4
33
  readonly query: RootOperationNode;
34
+ /**
35
+ * Identifies the query when passing data between plugin hooks.
36
+ */
5
37
  readonly queryId: QueryId;
38
+ /**
39
+ * The SQL string, with placeholders in the target dialect's syntax.
40
+ */
6
41
  readonly sql: string;
42
+ /**
43
+ * The parameter values in the order expected by the placeholders in {@link CompiledQuery.sql | sql}.
44
+ */
7
45
  readonly parameters: ReadonlyArray<unknown>;
8
46
  }
9
47
  export declare function isCompiledQuery<R>(thing: unknown): thing is CompiledQuery<R>;
10
48
  type CompiledQueryFactory = Readonly<{
49
+ /**
50
+ * Creates a compiled query from an existing SQL string and parameter values.
51
+ *
52
+ * The SQL is used as provided. This method doesn't escape SQL, generate
53
+ * placeholders, or check that the parameters match the placeholders.
54
+ * Use the target dialect's placeholder syntax, such as `$1` for PostgreSQL
55
+ * or `?` for MySQL and SQLite. For SQL built from interpolated values,
56
+ * prefer the {@link sql} template tag.
57
+ *
58
+ * ### Examples
59
+ *
60
+ * Execute a parameterized PostgreSQL query:
61
+ *
62
+ * ```ts
63
+ * import { CompiledQuery } from 'kysely'
64
+ *
65
+ * const result = await db.executeQuery<{ first_name: string }>(
66
+ * CompiledQuery.raw(
67
+ * 'select "first_name" from "person" where "id" = $1',
68
+ * [123],
69
+ * ),
70
+ * )
71
+ *
72
+ * const firstName: string | undefined = result.rows[0]?.first_name
73
+ * ```
74
+ *
75
+ * The generated SQL (PostgreSQL):
76
+ *
77
+ * ```sql
78
+ * select "first_name"
79
+ * from "person"
80
+ * where "id" = $1
81
+ * ```
82
+ */
11
83
  raw(sql: string, parameters?: unknown[]): Readonly<CompiledQuery>;
12
84
  }>;
13
85
  export declare const CompiledQuery: CompiledQueryFactory;
@@ -3,3 +3,8 @@
3
3
  * Logs a message and ensures it won't be logged again.
4
4
  */
5
5
  export declare function logOnce(message: string): void;
6
+ /**
7
+ * Use for system-level warnings.
8
+ * Logs a warning and ensures it won't be logged again.
9
+ */
10
+ export declare function warnOnce(message: string): void;
@@ -11,3 +11,14 @@ export function logOnce(message) {
11
11
  LOGGED_MESSAGES.add(message);
12
12
  console.log(message);
13
13
  }
14
+ /**
15
+ * Use for system-level warnings.
16
+ * Logs a warning and ensures it won't be logged again.
17
+ */
18
+ export function warnOnce(message) {
19
+ if (LOGGED_MESSAGES.has(message)) {
20
+ return;
21
+ }
22
+ LOGGED_MESSAGES.add(message);
23
+ console.warn(message);
24
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kysely",
3
- "version": "0.29.8",
3
+ "version": "0.29.9",
4
4
  "description": "Type safe SQL query builder",
5
5
  "repository": {
6
6
  "type": "git",
@@ -62,7 +62,7 @@
62
62
  ],
63
63
  "devDependencies": {
64
64
  "@arethetypeswrong/cli": "0.18.5",
65
- "@electric-sql/pglite": "0.5.13",
65
+ "@electric-sql/pglite": "0.5.16",
66
66
  "@types/better-sqlite3": "9.6.0",
67
67
  "@types/chai": "5.2.3",
68
68
  "@types/chai-as-promised": "8.0.2",
@@ -83,11 +83,11 @@
83
83
  "pathe": "2.0.3",
84
84
  "pg": "8.23.1",
85
85
  "pg-cursor": "2.22.1",
86
- "playwright": "1.64.1",
86
+ "playwright": "1.65.0",
87
87
  "prettier": "3.9.10",
88
88
  "prototype-pollution-vulnerable-lodash.merge-dont-upgrade": "npm:lodash.merge@4.6.1",
89
- "remeda": "2.57.0",
90
- "semver": "7.8.5",
89
+ "remeda": "2.59.0",
90
+ "semver": "7.8.6",
91
91
  "sinon": "22.1.0",
92
92
  "tarn": "3.1.2",
93
93
  "tedious": "20.3.8",