@nextlyhq/adapter-drizzle 0.0.2-alpha.62 → 0.0.2-alpha.65

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
@@ -2,7 +2,31 @@
2
2
 
3
3
  Internal Drizzle ORM utilities for Nextly's database adapters.
4
4
 
5
- > This package is part of Nextly's internals. It is installed automatically as a dependency of [`nextly`](../nextly). Choose [`@nextlyhq/adapter-postgres`](../adapter-postgres), [`@nextlyhq/adapter-mysql`](../adapter-mysql), or [`@nextlyhq/adapter-sqlite`](../adapter-sqlite) for your project.
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/@nextlyhq/adapter-drizzle"><img alt="npm" src="https://img.shields.io/npm/v/@nextlyhq%2Fadapter-drizzle?style=flat-square&label=npm&color=cb3837" /></a>
7
+ <a href="https://github.com/nextlyhq/nextly/blob/main/LICENSE.md"><img alt="License" src="https://img.shields.io/github/license/nextlyhq/nextly?style=flat-square&color=blue" /></a>
8
+ <a href="https://nextlyhq.com/docs"><img alt="Status" src="https://img.shields.io/badge/status-alpha-orange?style=flat-square" /></a>
9
+ </p>
10
+
11
+ > [!IMPORTANT]
12
+ > Nextly is in alpha. APIs may change before 1.0. Pin exact versions in production.
13
+
14
+ This package is part of Nextly's internals. It is installed automatically as a dependency of [`nextly`](../nextly). Choose [`@nextlyhq/adapter-postgres`](../adapter-postgres), [`@nextlyhq/adapter-mysql`](../adapter-mysql), or [`@nextlyhq/adapter-sqlite`](../adapter-sqlite) for your project.
15
+
16
+ ## Install
17
+
18
+ You do not install this directly. It arrives as a dependency of [`nextly`](../nextly)
19
+ and of each database adapter. Install the adapter for your database instead:
20
+
21
+ ```bash
22
+ pnpm add @nextlyhq/adapter-postgres pg
23
+ ```
24
+
25
+ ## Related packages
26
+
27
+ - [`@nextlyhq/adapter-postgres`](../adapter-postgres) — recommended for production
28
+ - [`@nextlyhq/adapter-mysql`](../adapter-mysql)
29
+ - [`@nextlyhq/adapter-sqlite`](../adapter-sqlite) — local demos
6
30
 
7
31
  ## License
8
32
 
@@ -1,5 +1,5 @@
1
1
  import { AnyRelations, SQL } from 'drizzle-orm';
2
- import { T as TransactionContext, S as SelectOptions, W as WhereClause, U as UpdateOptions, D as DeleteOptions, e as UpsertOptions, f as TransactionOptions, g as DatabaseCapabilities, P as PoolStats, I as InsertOptions, a as Migration, d as MigrationResult } from './migration-DhMuYiIm.cjs';
2
+ import { T as TransactionContext, S as SelectOptions, W as WhereClause, U as UpdateOptions, D as DeleteOptions, e as UpsertOptions, f as TransactionOptions, g as DatabaseCapabilities, P as PoolStats, C as CountOptions, I as InsertOptions, a as Migration, d as MigrationResult } from './migration-B6AmjCQ1.cjs';
3
3
  import { S as SupportedDialect, a as SqlParam, T as TableResolver } from './core-CVO7WYDj.cjs';
4
4
  import { T as TableDefinition, C as CreateTableOptions, D as DropTableOptions, A as AlterTableOperation, a as AlterTableOptions } from './schema-BDn8WfSL.cjs';
5
5
  import { D as DatabaseErrorKind, a as DatabaseError } from './error-BrdknH2s.cjs';
@@ -320,6 +320,23 @@ declare abstract class DrizzleAdapter {
320
320
  * for `"*"` or when nothing resolves, so callers fall back to all columns.
321
321
  */
322
322
  protected buildColumnProjection(tableObj: unknown, names: string[] | "*" | undefined): Record<string, unknown> | undefined;
323
+ /**
324
+ * The requested column names split into what this table has and what it does not.
325
+ *
326
+ * One resolution, reported both ways, because two callers need different halves
327
+ * of the same answer: a projection drops what it cannot find, while a distinct
328
+ * count has to REFUSE it. Resolving twice would let the two disagree about what
329
+ * "found" means — the SQL-name alias below is exactly the kind of detail a
330
+ * second implementation gets wrong — and the count would then reject a column
331
+ * the projection happily uses.
332
+ *
333
+ * Both spellings resolve: a caller may name the Drizzle property or the SQL
334
+ * column, and either maps to the same projection key.
335
+ */
336
+ protected resolveColumns(tableObj: unknown, names: readonly string[]): {
337
+ projection: Record<string, unknown>;
338
+ unresolved: string[];
339
+ };
323
340
  /**
324
341
  * Check if the adapter is currently connected.
325
342
  *
@@ -453,6 +470,51 @@ declare abstract class DrizzleAdapter {
453
470
  * });
454
471
  * ```
455
472
  */
473
+ /** The registered table object, or a refusal naming what is missing. */
474
+ private requireTableObject;
475
+ /**
476
+ * The columns a distinct count groups by, or `undefined` for a row count.
477
+ *
478
+ * 🔴 EVERY named column must resolve, not merely one of them. A `distinctOn`
479
+ * naming no column this table has would fall through to a row count, and one
480
+ * naming a valid column beside a misspelled or since-removed column would
481
+ * count distinct over a NARROWER key than the caller asked for. Both answer a
482
+ * different question than the one asked, silently; the second is the worse of
483
+ * the two, because a subset key groups more rows together and so UNDERCOUNTS,
484
+ * which reads as a plausible number rather than as a fault. Refused here,
485
+ * where the mistake was made, rather than surfacing as a wrong figure.
486
+ */
487
+ private countProjection;
488
+ /**
489
+ * `COUNT(*)` over a `SELECT DISTINCT` subquery.
490
+ *
491
+ * Its own function because it is a genuinely different query from the plain
492
+ * count rather than a variation on one, and because building both inline made
493
+ * the public method a chain of decisions the complexity gate objected to
494
+ * before a reader would have.
495
+ */
496
+ private countDistinctRows;
497
+ /** `COUNT(*)` over the table itself. */
498
+ private countAllRows;
499
+ /**
500
+ * How many rows match, or how many DISTINCT combinations of some columns do.
501
+ *
502
+ * The data layer had no count at all: collection totals go through
503
+ * `countEntries`, an access-controlled path built for collection tables, and
504
+ * nothing could count a system table. A caller wanting one selected the rows
505
+ * and measured the array, which transfers every row to learn a number and
506
+ * cannot be bounded without making the number wrong.
507
+ *
508
+ * 🔴 `distinctOn` compiles to `COUNT(*)` over a `SELECT DISTINCT` SUBQUERY,
509
+ * never to `COUNT(DISTINCT a, b)`. The inline form is not portable and fails
510
+ * in the direction that is hardest to notice -- MySQL accepts it, PostgreSQL
511
+ * needs a row constructor, and SQLite rejects it outright with "wrong number
512
+ * of arguments to function count()" -- so a query written against one engine
513
+ * is a syntax error on another. The subquery is the single form all three
514
+ * accept, and building it here rather than at each call site is what keeps
515
+ * the answer identical per dialect.
516
+ */
517
+ count(table: string, options?: CountOptions, executor?: unknown): Promise<number>;
456
518
  selectOne<T = unknown>(table: string, options?: SelectOptions, executor?: unknown): Promise<T | null>;
457
519
  /**
458
520
  * Insert a single record into a table.
@@ -1,5 +1,5 @@
1
1
  import { AnyRelations, SQL } from 'drizzle-orm';
2
- import { T as TransactionContext, S as SelectOptions, W as WhereClause, U as UpdateOptions, D as DeleteOptions, e as UpsertOptions, f as TransactionOptions, g as DatabaseCapabilities, P as PoolStats, I as InsertOptions, a as Migration, d as MigrationResult } from './migration-BnT96HFp.js';
2
+ import { T as TransactionContext, S as SelectOptions, W as WhereClause, U as UpdateOptions, D as DeleteOptions, e as UpsertOptions, f as TransactionOptions, g as DatabaseCapabilities, P as PoolStats, C as CountOptions, I as InsertOptions, a as Migration, d as MigrationResult } from './migration-R06fsTrz.js';
3
3
  import { S as SupportedDialect, a as SqlParam, T as TableResolver } from './core-CVO7WYDj.js';
4
4
  import { T as TableDefinition, C as CreateTableOptions, D as DropTableOptions, A as AlterTableOperation, a as AlterTableOptions } from './schema-BIQ0YQZ_.js';
5
5
  import { D as DatabaseErrorKind, a as DatabaseError } from './error-BrdknH2s.js';
@@ -320,6 +320,23 @@ declare abstract class DrizzleAdapter {
320
320
  * for `"*"` or when nothing resolves, so callers fall back to all columns.
321
321
  */
322
322
  protected buildColumnProjection(tableObj: unknown, names: string[] | "*" | undefined): Record<string, unknown> | undefined;
323
+ /**
324
+ * The requested column names split into what this table has and what it does not.
325
+ *
326
+ * One resolution, reported both ways, because two callers need different halves
327
+ * of the same answer: a projection drops what it cannot find, while a distinct
328
+ * count has to REFUSE it. Resolving twice would let the two disagree about what
329
+ * "found" means — the SQL-name alias below is exactly the kind of detail a
330
+ * second implementation gets wrong — and the count would then reject a column
331
+ * the projection happily uses.
332
+ *
333
+ * Both spellings resolve: a caller may name the Drizzle property or the SQL
334
+ * column, and either maps to the same projection key.
335
+ */
336
+ protected resolveColumns(tableObj: unknown, names: readonly string[]): {
337
+ projection: Record<string, unknown>;
338
+ unresolved: string[];
339
+ };
323
340
  /**
324
341
  * Check if the adapter is currently connected.
325
342
  *
@@ -453,6 +470,51 @@ declare abstract class DrizzleAdapter {
453
470
  * });
454
471
  * ```
455
472
  */
473
+ /** The registered table object, or a refusal naming what is missing. */
474
+ private requireTableObject;
475
+ /**
476
+ * The columns a distinct count groups by, or `undefined` for a row count.
477
+ *
478
+ * 🔴 EVERY named column must resolve, not merely one of them. A `distinctOn`
479
+ * naming no column this table has would fall through to a row count, and one
480
+ * naming a valid column beside a misspelled or since-removed column would
481
+ * count distinct over a NARROWER key than the caller asked for. Both answer a
482
+ * different question than the one asked, silently; the second is the worse of
483
+ * the two, because a subset key groups more rows together and so UNDERCOUNTS,
484
+ * which reads as a plausible number rather than as a fault. Refused here,
485
+ * where the mistake was made, rather than surfacing as a wrong figure.
486
+ */
487
+ private countProjection;
488
+ /**
489
+ * `COUNT(*)` over a `SELECT DISTINCT` subquery.
490
+ *
491
+ * Its own function because it is a genuinely different query from the plain
492
+ * count rather than a variation on one, and because building both inline made
493
+ * the public method a chain of decisions the complexity gate objected to
494
+ * before a reader would have.
495
+ */
496
+ private countDistinctRows;
497
+ /** `COUNT(*)` over the table itself. */
498
+ private countAllRows;
499
+ /**
500
+ * How many rows match, or how many DISTINCT combinations of some columns do.
501
+ *
502
+ * The data layer had no count at all: collection totals go through
503
+ * `countEntries`, an access-controlled path built for collection tables, and
504
+ * nothing could count a system table. A caller wanting one selected the rows
505
+ * and measured the array, which transfers every row to learn a number and
506
+ * cannot be bounded without making the number wrong.
507
+ *
508
+ * 🔴 `distinctOn` compiles to `COUNT(*)` over a `SELECT DISTINCT` SUBQUERY,
509
+ * never to `COUNT(DISTINCT a, b)`. The inline form is not portable and fails
510
+ * in the direction that is hardest to notice -- MySQL accepts it, PostgreSQL
511
+ * needs a row constructor, and SQLite rejects it outright with "wrong number
512
+ * of arguments to function count()" -- so a query written against one engine
513
+ * is a syntax error on another. The subquery is the single form all three
514
+ * accept, and building it here rather than at each call site is what keeps
515
+ * the answer identical per dialect.
516
+ */
517
+ count(table: string, options?: CountOptions, executor?: unknown): Promise<number>;
456
518
  selectOne<T = unknown>(table: string, options?: SelectOptions, executor?: unknown): Promise<T | null>;
457
519
  /**
458
520
  * Insert a single record into a table.
package/dist/index.cjs CHANGED
@@ -569,6 +569,23 @@ var DrizzleAdapter = class {
569
569
  if (names == null || names === "*" || !tableObj || typeof tableObj !== "object") {
570
570
  return void 0;
571
571
  }
572
+ const { projection } = this.resolveColumns(tableObj, names);
573
+ return Object.keys(projection).length ? projection : void 0;
574
+ }
575
+ /**
576
+ * The requested column names split into what this table has and what it does not.
577
+ *
578
+ * One resolution, reported both ways, because two callers need different halves
579
+ * of the same answer: a projection drops what it cannot find, while a distinct
580
+ * count has to REFUSE it. Resolving twice would let the two disagree about what
581
+ * "found" means — the SQL-name alias below is exactly the kind of detail a
582
+ * second implementation gets wrong — and the count would then reject a column
583
+ * the projection happily uses.
584
+ *
585
+ * Both spellings resolve: a caller may name the Drizzle property or the SQL
586
+ * column, and either maps to the same projection key.
587
+ */
588
+ resolveColumns(tableObj, names) {
572
589
  const cols = drizzleOrm.getColumns(tableObj);
573
590
  const byAnyName = {};
574
591
  for (const [jsName, col] of Object.entries(cols)) {
@@ -577,11 +594,13 @@ var DrizzleAdapter = class {
577
594
  if (typeof sqlName === "string") byAnyName[sqlName] = { jsName, col };
578
595
  }
579
596
  const projection = {};
597
+ const unresolved = [];
580
598
  for (const name of names) {
581
599
  const hit = byAnyName[name];
582
600
  if (hit) projection[hit.jsName] = hit.col;
601
+ else unresolved.push(name);
583
602
  }
584
- return Object.keys(projection).length ? projection : void 0;
603
+ return { projection, unresolved };
585
604
  }
586
605
  // ============================================================
587
606
  // Connection Status (Default implementations, can override)
@@ -808,6 +827,93 @@ var DrizzleAdapter = class {
808
827
  * });
809
828
  * ```
810
829
  */
830
+ /** The registered table object, or a refusal naming what is missing. */
831
+ requireTableObject(table) {
832
+ const tableObj = this.getTableObject(table);
833
+ if (tableObj) return tableObj;
834
+ throw this.createDatabaseError(
835
+ "query",
836
+ `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`,
837
+ void 0
838
+ );
839
+ }
840
+ /**
841
+ * The columns a distinct count groups by, or `undefined` for a row count.
842
+ *
843
+ * 🔴 EVERY named column must resolve, not merely one of them. A `distinctOn`
844
+ * naming no column this table has would fall through to a row count, and one
845
+ * naming a valid column beside a misspelled or since-removed column would
846
+ * count distinct over a NARROWER key than the caller asked for. Both answer a
847
+ * different question than the one asked, silently; the second is the worse of
848
+ * the two, because a subset key groups more rows together and so UNDERCOUNTS,
849
+ * which reads as a plausible number rather than as a fault. Refused here,
850
+ * where the mistake was made, rather than surfacing as a wrong figure.
851
+ */
852
+ countProjection(tableObj, table, options) {
853
+ const distinctOn = options?.distinctOn;
854
+ if (!distinctOn?.length) return void 0;
855
+ const { projection, unresolved } = this.resolveColumns(
856
+ tableObj,
857
+ distinctOn
858
+ );
859
+ if (unresolved.length > 0) {
860
+ throw this.createDatabaseError(
861
+ "query",
862
+ `distinctOn names ${unresolved.length === 1 ? "a column" : "columns"} that do not exist on "${table}": ${unresolved.join(", ")}`,
863
+ void 0
864
+ );
865
+ }
866
+ return projection;
867
+ }
868
+ /**
869
+ * `COUNT(*)` over a `SELECT DISTINCT` subquery.
870
+ *
871
+ * Its own function because it is a genuinely different query from the plain
872
+ * count rather than a variation on one, and because building both inline made
873
+ * the public method a chain of decisions the complexity gate objected to
874
+ * before a reader would have.
875
+ */
876
+ async countDistinctRows(db, tableObj, projection, where) {
877
+ const inner = db.selectDistinct(projection).from(tableObj).$dynamic();
878
+ const distinctRows = where ? inner.where(where) : inner;
879
+ const [row] = await db.select({ value: drizzleOrm.count() }).from(distinctRows.as("nextly_distinct"));
880
+ return Number(row?.value ?? 0);
881
+ }
882
+ /** `COUNT(*)` over the table itself. */
883
+ async countAllRows(db, tableObj, where) {
884
+ const base = db.select({ value: drizzleOrm.count() }).from(tableObj).$dynamic();
885
+ const [row] = await (where ? base.where(where) : base);
886
+ return Number(row?.value ?? 0);
887
+ }
888
+ /**
889
+ * How many rows match, or how many DISTINCT combinations of some columns do.
890
+ *
891
+ * The data layer had no count at all: collection totals go through
892
+ * `countEntries`, an access-controlled path built for collection tables, and
893
+ * nothing could count a system table. A caller wanting one selected the rows
894
+ * and measured the array, which transfers every row to learn a number and
895
+ * cannot be bounded without making the number wrong.
896
+ *
897
+ * 🔴 `distinctOn` compiles to `COUNT(*)` over a `SELECT DISTINCT` SUBQUERY,
898
+ * never to `COUNT(DISTINCT a, b)`. The inline form is not portable and fails
899
+ * in the direction that is hardest to notice -- MySQL accepts it, PostgreSQL
900
+ * needs a row constructor, and SQLite rejects it outright with "wrong number
901
+ * of arguments to function count()" -- so a query written against one engine
902
+ * is a syntax error on another. The subquery is the single form all three
903
+ * accept, and building it here rather than at each call site is what keeps
904
+ * the answer identical per dialect.
905
+ */
906
+ async count(table, options, executor) {
907
+ const tableObj = this.requireTableObject(table);
908
+ try {
909
+ const db = executor ?? this.getDrizzle();
910
+ const where = options?.where ? buildDrizzleWhere(tableObj, options.where) : void 0;
911
+ const projection = this.countProjection(tableObj, table, options);
912
+ return projection ? await this.countDistinctRows(db, tableObj, projection, where) : await this.countAllRows(db, tableObj, where);
913
+ } catch (error) {
914
+ throw this.handleQueryError(error, "count", table);
915
+ }
916
+ }
811
917
  async selectOne(table, options, executor) {
812
918
  const results = await this.select(
813
919
  table,