@nextlyhq/adapter-drizzle 0.0.2-alpha.60 → 0.0.2-alpha.64
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 +25 -1
- package/dist/{adapter-BG7MnOUw.d.cts → adapter-C6ENLDau.d.cts} +115 -3
- package/dist/{adapter-DjPoLjBK.d.ts → adapter-D4GM1VAZ.d.ts} +115 -3
- package/dist/index.cjs +213 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +214 -27
- package/dist/index.mjs.map +1 -1
- package/dist/{migration-BUc56kip.d.cts → migration-B6AmjCQ1.d.cts} +76 -4
- package/dist/{migration-Bj84e15B.d.ts → migration-R06fsTrz.d.ts} +76 -4
- package/dist/migrations.d.cts +3 -3
- package/dist/migrations.d.ts +3 -3
- package/dist/types/index.cjs +30 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.mjs +30 -1
- package/dist/types/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { D as DrizzleAdapter } from './adapter-
|
|
1
|
+
export { D as DrizzleAdapter, T as TransactionCrudDelegator, a as TransactionCrudForwarders } from './adapter-C6ENLDau.cjs';
|
|
2
2
|
export { T as TableResolver } from './core-CVO7WYDj.cjs';
|
|
3
3
|
import 'drizzle-orm';
|
|
4
|
-
import './migration-
|
|
4
|
+
import './migration-B6AmjCQ1.cjs';
|
|
5
5
|
import './schema-BDn8WfSL.cjs';
|
|
6
6
|
import './error-BrdknH2s.cjs';
|
|
7
7
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { D as DrizzleAdapter } from './adapter-
|
|
1
|
+
export { D as DrizzleAdapter, T as TransactionCrudDelegator, a as TransactionCrudForwarders } from './adapter-D4GM1VAZ.js';
|
|
2
2
|
export { T as TableResolver } from './core-CVO7WYDj.js';
|
|
3
3
|
import 'drizzle-orm';
|
|
4
|
-
import './migration-
|
|
4
|
+
import './migration-R06fsTrz.js';
|
|
5
5
|
import './schema-BIQ0YQZ_.js';
|
|
6
6
|
import './error-BrdknH2s.js';
|
|
7
7
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
-
import { getColumns, desc, asc, and, or, not,
|
|
1
|
+
import { getColumns, count, desc, asc, sql, and, or, not, notBetween, between, isNotNull, isNull, notInArray, inArray, ilike, like, lte, gte, lt, gt, ne, eq } from 'drizzle-orm';
|
|
2
2
|
|
|
3
3
|
// src/adapter.ts
|
|
4
|
+
function buildDrizzleOrderBy(columns, orderBy) {
|
|
5
|
+
if (!orderBy?.length) return [];
|
|
6
|
+
return orderBy.flatMap((spec) => {
|
|
7
|
+
const col = columns[spec.column];
|
|
8
|
+
if (!col) return [];
|
|
9
|
+
const direction = spec.direction === "desc" ? desc(col) : asc(col);
|
|
10
|
+
if (spec.nulls === void 0) return [direction];
|
|
11
|
+
const isNull2 = sql`${col} is null`;
|
|
12
|
+
return [spec.nulls === "last" ? asc(isNull2) : desc(isNull2), direction];
|
|
13
|
+
});
|
|
14
|
+
}
|
|
4
15
|
function buildDrizzleWhere(table, where) {
|
|
5
16
|
const columns = getColumns(table);
|
|
6
17
|
return processWhereClause(columns, where);
|
|
@@ -36,13 +47,26 @@ function processWhereClause(columns, where) {
|
|
|
36
47
|
parts.push(not(notCondition));
|
|
37
48
|
}
|
|
38
49
|
}
|
|
39
|
-
if (parts.length === 0)
|
|
50
|
+
if (parts.length === 0) {
|
|
51
|
+
if (namesABranch(where)) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Where clause produced no condition: ${JSON.stringify(where)}. Every branch resolved to nothing, which would match every row. Pass {} to mean "no filter".`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
40
58
|
if (parts.length === 1) return parts[0];
|
|
41
59
|
return and(...parts);
|
|
42
60
|
}
|
|
43
61
|
function isWhereCondition(item) {
|
|
44
62
|
return "column" in item && "op" in item;
|
|
45
63
|
}
|
|
64
|
+
function namesABranch(where) {
|
|
65
|
+
return Boolean(where.and?.length || where.or?.length || where.not);
|
|
66
|
+
}
|
|
67
|
+
function containsPattern(value) {
|
|
68
|
+
return `%${value.replace(/[!%_]/g, (character) => `!${character}`)}%`;
|
|
69
|
+
}
|
|
46
70
|
function buildCondition(columns, cond) {
|
|
47
71
|
const column = columns[cond.column];
|
|
48
72
|
if (!column) {
|
|
@@ -81,12 +105,37 @@ function buildCondition(columns, cond) {
|
|
|
81
105
|
case "NOT BETWEEN":
|
|
82
106
|
return notBetween(col, cond.value, cond.valueTo);
|
|
83
107
|
case "CONTAINS":
|
|
84
|
-
return
|
|
108
|
+
return sql`${col} like ${containsPattern(String(cond.value))} escape '!'`;
|
|
85
109
|
default:
|
|
86
110
|
throw new Error(`Unsupported operator: ${cond.op}`);
|
|
87
111
|
}
|
|
88
112
|
}
|
|
89
113
|
|
|
114
|
+
// src/transaction-forwarders.ts
|
|
115
|
+
function createTransactionForwarders(delegator, txDb) {
|
|
116
|
+
return {
|
|
117
|
+
select: async (table, options) => {
|
|
118
|
+
return delegator.select(table, options, txDb());
|
|
119
|
+
},
|
|
120
|
+
selectOne: async (table, options) => {
|
|
121
|
+
return delegator.selectOne(table, options, txDb());
|
|
122
|
+
},
|
|
123
|
+
update: async (table, data, where, options) => {
|
|
124
|
+
return delegator.update(table, data, where, options, txDb());
|
|
125
|
+
},
|
|
126
|
+
updateCount: async (table, data, where) => {
|
|
127
|
+
return delegator.updateCount(table, data, where, txDb());
|
|
128
|
+
},
|
|
129
|
+
delete: async (table, where, options) => {
|
|
130
|
+
return delegator.delete(table, where, options, txDb());
|
|
131
|
+
},
|
|
132
|
+
upsert: async (table, data, options) => {
|
|
133
|
+
return delegator.upsert(table, data, options, txDb());
|
|
134
|
+
},
|
|
135
|
+
getDrizzle: () => txDb()
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
90
139
|
// src/types/error.ts
|
|
91
140
|
function isDatabaseError(error) {
|
|
92
141
|
return typeof error === "object" && error !== null && "kind" in error && typeof error.kind === "string";
|
|
@@ -518,6 +567,23 @@ var DrizzleAdapter = class {
|
|
|
518
567
|
if (names == null || names === "*" || !tableObj || typeof tableObj !== "object") {
|
|
519
568
|
return void 0;
|
|
520
569
|
}
|
|
570
|
+
const { projection } = this.resolveColumns(tableObj, names);
|
|
571
|
+
return Object.keys(projection).length ? projection : void 0;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* The requested column names split into what this table has and what it does not.
|
|
575
|
+
*
|
|
576
|
+
* One resolution, reported both ways, because two callers need different halves
|
|
577
|
+
* of the same answer: a projection drops what it cannot find, while a distinct
|
|
578
|
+
* count has to REFUSE it. Resolving twice would let the two disagree about what
|
|
579
|
+
* "found" means — the SQL-name alias below is exactly the kind of detail a
|
|
580
|
+
* second implementation gets wrong — and the count would then reject a column
|
|
581
|
+
* the projection happily uses.
|
|
582
|
+
*
|
|
583
|
+
* Both spellings resolve: a caller may name the Drizzle property or the SQL
|
|
584
|
+
* column, and either maps to the same projection key.
|
|
585
|
+
*/
|
|
586
|
+
resolveColumns(tableObj, names) {
|
|
521
587
|
const cols = getColumns(tableObj);
|
|
522
588
|
const byAnyName = {};
|
|
523
589
|
for (const [jsName, col] of Object.entries(cols)) {
|
|
@@ -526,11 +592,13 @@ var DrizzleAdapter = class {
|
|
|
526
592
|
if (typeof sqlName === "string") byAnyName[sqlName] = { jsName, col };
|
|
527
593
|
}
|
|
528
594
|
const projection = {};
|
|
595
|
+
const unresolved = [];
|
|
529
596
|
for (const name of names) {
|
|
530
597
|
const hit = byAnyName[name];
|
|
531
598
|
if (hit) projection[hit.jsName] = hit.col;
|
|
599
|
+
else unresolved.push(name);
|
|
532
600
|
}
|
|
533
|
-
return
|
|
601
|
+
return { projection, unresolved };
|
|
534
602
|
}
|
|
535
603
|
// ============================================================
|
|
536
604
|
// Connection Status (Default implementations, can override)
|
|
@@ -702,12 +770,10 @@ var DrizzleAdapter = class {
|
|
|
702
770
|
}
|
|
703
771
|
}
|
|
704
772
|
if (options?.orderBy?.length) {
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
return o.direction === "desc" ? desc(col) : asc(col);
|
|
710
|
-
}).filter(Boolean);
|
|
773
|
+
const orderClauses = buildDrizzleOrderBy(
|
|
774
|
+
getColumns(tableObj),
|
|
775
|
+
options.orderBy
|
|
776
|
+
);
|
|
711
777
|
if (orderClauses.length) {
|
|
712
778
|
query = query.orderBy(...orderClauses);
|
|
713
779
|
}
|
|
@@ -759,6 +825,93 @@ var DrizzleAdapter = class {
|
|
|
759
825
|
* });
|
|
760
826
|
* ```
|
|
761
827
|
*/
|
|
828
|
+
/** The registered table object, or a refusal naming what is missing. */
|
|
829
|
+
requireTableObject(table) {
|
|
830
|
+
const tableObj = this.getTableObject(table);
|
|
831
|
+
if (tableObj) return tableObj;
|
|
832
|
+
throw this.createDatabaseError(
|
|
833
|
+
"query",
|
|
834
|
+
`Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`,
|
|
835
|
+
void 0
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* The columns a distinct count groups by, or `undefined` for a row count.
|
|
840
|
+
*
|
|
841
|
+
* 🔴 EVERY named column must resolve, not merely one of them. A `distinctOn`
|
|
842
|
+
* naming no column this table has would fall through to a row count, and one
|
|
843
|
+
* naming a valid column beside a misspelled or since-removed column would
|
|
844
|
+
* count distinct over a NARROWER key than the caller asked for. Both answer a
|
|
845
|
+
* different question than the one asked, silently; the second is the worse of
|
|
846
|
+
* the two, because a subset key groups more rows together and so UNDERCOUNTS,
|
|
847
|
+
* which reads as a plausible number rather than as a fault. Refused here,
|
|
848
|
+
* where the mistake was made, rather than surfacing as a wrong figure.
|
|
849
|
+
*/
|
|
850
|
+
countProjection(tableObj, table, options) {
|
|
851
|
+
const distinctOn = options?.distinctOn;
|
|
852
|
+
if (!distinctOn?.length) return void 0;
|
|
853
|
+
const { projection, unresolved } = this.resolveColumns(
|
|
854
|
+
tableObj,
|
|
855
|
+
distinctOn
|
|
856
|
+
);
|
|
857
|
+
if (unresolved.length > 0) {
|
|
858
|
+
throw this.createDatabaseError(
|
|
859
|
+
"query",
|
|
860
|
+
`distinctOn names ${unresolved.length === 1 ? "a column" : "columns"} that do not exist on "${table}": ${unresolved.join(", ")}`,
|
|
861
|
+
void 0
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
return projection;
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* `COUNT(*)` over a `SELECT DISTINCT` subquery.
|
|
868
|
+
*
|
|
869
|
+
* Its own function because it is a genuinely different query from the plain
|
|
870
|
+
* count rather than a variation on one, and because building both inline made
|
|
871
|
+
* the public method a chain of decisions the complexity gate objected to
|
|
872
|
+
* before a reader would have.
|
|
873
|
+
*/
|
|
874
|
+
async countDistinctRows(db, tableObj, projection, where) {
|
|
875
|
+
const inner = db.selectDistinct(projection).from(tableObj).$dynamic();
|
|
876
|
+
const distinctRows = where ? inner.where(where) : inner;
|
|
877
|
+
const [row] = await db.select({ value: count() }).from(distinctRows.as("nextly_distinct"));
|
|
878
|
+
return Number(row?.value ?? 0);
|
|
879
|
+
}
|
|
880
|
+
/** `COUNT(*)` over the table itself. */
|
|
881
|
+
async countAllRows(db, tableObj, where) {
|
|
882
|
+
const base = db.select({ value: count() }).from(tableObj).$dynamic();
|
|
883
|
+
const [row] = await (where ? base.where(where) : base);
|
|
884
|
+
return Number(row?.value ?? 0);
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* How many rows match, or how many DISTINCT combinations of some columns do.
|
|
888
|
+
*
|
|
889
|
+
* The data layer had no count at all: collection totals go through
|
|
890
|
+
* `countEntries`, an access-controlled path built for collection tables, and
|
|
891
|
+
* nothing could count a system table. A caller wanting one selected the rows
|
|
892
|
+
* and measured the array, which transfers every row to learn a number and
|
|
893
|
+
* cannot be bounded without making the number wrong.
|
|
894
|
+
*
|
|
895
|
+
* 🔴 `distinctOn` compiles to `COUNT(*)` over a `SELECT DISTINCT` SUBQUERY,
|
|
896
|
+
* never to `COUNT(DISTINCT a, b)`. The inline form is not portable and fails
|
|
897
|
+
* in the direction that is hardest to notice -- MySQL accepts it, PostgreSQL
|
|
898
|
+
* needs a row constructor, and SQLite rejects it outright with "wrong number
|
|
899
|
+
* of arguments to function count()" -- so a query written against one engine
|
|
900
|
+
* is a syntax error on another. The subquery is the single form all three
|
|
901
|
+
* accept, and building it here rather than at each call site is what keeps
|
|
902
|
+
* the answer identical per dialect.
|
|
903
|
+
*/
|
|
904
|
+
async count(table, options, executor) {
|
|
905
|
+
const tableObj = this.requireTableObject(table);
|
|
906
|
+
try {
|
|
907
|
+
const db = executor ?? this.getDrizzle();
|
|
908
|
+
const where = options?.where ? buildDrizzleWhere(tableObj, options.where) : void 0;
|
|
909
|
+
const projection = this.countProjection(tableObj, table, options);
|
|
910
|
+
return projection ? await this.countDistinctRows(db, tableObj, projection, where) : await this.countAllRows(db, tableObj, where);
|
|
911
|
+
} catch (error) {
|
|
912
|
+
throw this.handleQueryError(error, "count", table);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
762
915
|
async selectOne(table, options, executor) {
|
|
763
916
|
const results = await this.select(
|
|
764
917
|
table,
|
|
@@ -1303,11 +1456,11 @@ var DrizzleAdapter = class {
|
|
|
1303
1456
|
*/
|
|
1304
1457
|
async tableExists(tableName, schema) {
|
|
1305
1458
|
try {
|
|
1306
|
-
let
|
|
1459
|
+
let sql3;
|
|
1307
1460
|
const params = [];
|
|
1308
1461
|
switch (this.dialect) {
|
|
1309
1462
|
case "postgresql":
|
|
1310
|
-
|
|
1463
|
+
sql3 = `
|
|
1311
1464
|
SELECT EXISTS (
|
|
1312
1465
|
SELECT FROM information_schema.tables
|
|
1313
1466
|
WHERE table_schema = COALESCE($1, (
|
|
@@ -1321,7 +1474,7 @@ var DrizzleAdapter = class {
|
|
|
1321
1474
|
params.push(schema ?? null, tableName);
|
|
1322
1475
|
break;
|
|
1323
1476
|
case "mysql":
|
|
1324
|
-
|
|
1477
|
+
sql3 = `
|
|
1325
1478
|
SELECT COUNT(*) as count
|
|
1326
1479
|
FROM information_schema.tables
|
|
1327
1480
|
WHERE table_schema = DATABASE()
|
|
@@ -1330,7 +1483,7 @@ var DrizzleAdapter = class {
|
|
|
1330
1483
|
params.push(tableName);
|
|
1331
1484
|
break;
|
|
1332
1485
|
case "sqlite":
|
|
1333
|
-
|
|
1486
|
+
sql3 = `
|
|
1334
1487
|
SELECT COUNT(*) as count
|
|
1335
1488
|
FROM sqlite_master
|
|
1336
1489
|
WHERE type = 'table'
|
|
@@ -1346,7 +1499,7 @@ var DrizzleAdapter = class {
|
|
|
1346
1499
|
);
|
|
1347
1500
|
}
|
|
1348
1501
|
const results = await this.executeQuery(
|
|
1349
|
-
|
|
1502
|
+
sql3,
|
|
1350
1503
|
params
|
|
1351
1504
|
);
|
|
1352
1505
|
if (results.length === 0) {
|
|
@@ -1378,11 +1531,11 @@ var DrizzleAdapter = class {
|
|
|
1378
1531
|
*/
|
|
1379
1532
|
async listTables(schema) {
|
|
1380
1533
|
try {
|
|
1381
|
-
let
|
|
1534
|
+
let sql3;
|
|
1382
1535
|
const params = [];
|
|
1383
1536
|
switch (this.dialect) {
|
|
1384
1537
|
case "postgresql":
|
|
1385
|
-
|
|
1538
|
+
sql3 = `
|
|
1386
1539
|
SELECT table_name
|
|
1387
1540
|
FROM information_schema.tables
|
|
1388
1541
|
WHERE table_schema = $1
|
|
@@ -1392,7 +1545,7 @@ var DrizzleAdapter = class {
|
|
|
1392
1545
|
params.push(schema ?? "public");
|
|
1393
1546
|
break;
|
|
1394
1547
|
case "mysql":
|
|
1395
|
-
|
|
1548
|
+
sql3 = `
|
|
1396
1549
|
SELECT table_name AS table_name
|
|
1397
1550
|
FROM information_schema.tables
|
|
1398
1551
|
WHERE table_schema = DATABASE()
|
|
@@ -1401,7 +1554,7 @@ var DrizzleAdapter = class {
|
|
|
1401
1554
|
`;
|
|
1402
1555
|
break;
|
|
1403
1556
|
case "sqlite":
|
|
1404
|
-
|
|
1557
|
+
sql3 = `
|
|
1405
1558
|
SELECT name as table_name
|
|
1406
1559
|
FROM sqlite_master
|
|
1407
1560
|
WHERE type = 'table'
|
|
@@ -1417,7 +1570,7 @@ var DrizzleAdapter = class {
|
|
|
1417
1570
|
);
|
|
1418
1571
|
}
|
|
1419
1572
|
const results = await this.executeQuery(
|
|
1420
|
-
|
|
1573
|
+
sql3,
|
|
1421
1574
|
params
|
|
1422
1575
|
);
|
|
1423
1576
|
return results.map((row) => row.table_name);
|
|
@@ -1473,30 +1626,64 @@ var DrizzleAdapter = class {
|
|
|
1473
1626
|
});
|
|
1474
1627
|
}
|
|
1475
1628
|
/**
|
|
1476
|
-
*
|
|
1629
|
+
* Helper to create transaction context CRUD forwarding methods.
|
|
1630
|
+
*
|
|
1631
|
+
* @param txDb - Thunk returning the transaction-bound Drizzle executor
|
|
1632
|
+
* @returns Object providing forwarded CRUD and getDrizzle methods
|
|
1633
|
+
*
|
|
1634
|
+
* @protected
|
|
1635
|
+
*/
|
|
1636
|
+
createTransactionForwarders(txDb) {
|
|
1637
|
+
return createTransactionForwarders(this, txDb);
|
|
1638
|
+
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Classify an error into a DatabaseError.
|
|
1477
1641
|
*
|
|
1478
1642
|
* @remarks
|
|
1479
|
-
*
|
|
1480
|
-
* Subclasses can override to add dialect-specific error classification.
|
|
1643
|
+
* Subclasses can override this method to add dialect-specific error code classification.
|
|
1481
1644
|
*
|
|
1482
1645
|
* @param error - Original error
|
|
1483
|
-
* @param
|
|
1484
|
-
* @param table - Table name
|
|
1646
|
+
* @param sql - SQL statement that caused the error (optional)
|
|
1485
1647
|
* @returns DatabaseError instance
|
|
1486
1648
|
*
|
|
1487
1649
|
* @protected
|
|
1488
1650
|
*/
|
|
1489
|
-
|
|
1651
|
+
classifyError(error, sql3) {
|
|
1490
1652
|
if (isDatabaseError(error)) {
|
|
1491
1653
|
return error;
|
|
1492
1654
|
}
|
|
1493
1655
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1494
1656
|
return this.createDatabaseError(
|
|
1495
1657
|
"query",
|
|
1496
|
-
|
|
1658
|
+
sql3 ? `Query failed: ${errorMessage}` : errorMessage,
|
|
1497
1659
|
error instanceof Error ? error : void 0
|
|
1498
1660
|
);
|
|
1499
1661
|
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Handle query errors and convert to DatabaseError.
|
|
1664
|
+
*
|
|
1665
|
+
* @remarks
|
|
1666
|
+
* Protected helper for consistent error handling across CRUD operations.
|
|
1667
|
+
* Delegates to `classifyError` and attaches operation and table context if not present.
|
|
1668
|
+
* Subclasses override `classifyError` to provide dialect-specific error code classification.
|
|
1669
|
+
*
|
|
1670
|
+
* @param error - Original error
|
|
1671
|
+
* @param operation - Operation that failed
|
|
1672
|
+
* @param table - Table name
|
|
1673
|
+
* @returns DatabaseError instance
|
|
1674
|
+
*
|
|
1675
|
+
* @protected
|
|
1676
|
+
*/
|
|
1677
|
+
handleQueryError(error, operation, table) {
|
|
1678
|
+
const dbError = this.classifyError(error);
|
|
1679
|
+
if (!dbError.message.includes(operation)) {
|
|
1680
|
+
dbError.message = `${operation} operation failed on table '${table}': ${dbError.message}`;
|
|
1681
|
+
}
|
|
1682
|
+
if (!dbError.table) {
|
|
1683
|
+
dbError.table = table;
|
|
1684
|
+
}
|
|
1685
|
+
return dbError;
|
|
1686
|
+
}
|
|
1500
1687
|
};
|
|
1501
1688
|
var ALLOWED_DEFAULT_SQL_EXPRESSIONS = /* @__PURE__ */ new Set([
|
|
1502
1689
|
"current_timestamp",
|