@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.cjs
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
var drizzleOrm = require('drizzle-orm');
|
|
4
4
|
|
|
5
5
|
// src/adapter.ts
|
|
6
|
+
function buildDrizzleOrderBy(columns, orderBy) {
|
|
7
|
+
if (!orderBy?.length) return [];
|
|
8
|
+
return orderBy.flatMap((spec) => {
|
|
9
|
+
const col = columns[spec.column];
|
|
10
|
+
if (!col) return [];
|
|
11
|
+
const direction = spec.direction === "desc" ? drizzleOrm.desc(col) : drizzleOrm.asc(col);
|
|
12
|
+
if (spec.nulls === void 0) return [direction];
|
|
13
|
+
const isNull2 = drizzleOrm.sql`${col} is null`;
|
|
14
|
+
return [spec.nulls === "last" ? drizzleOrm.asc(isNull2) : drizzleOrm.desc(isNull2), direction];
|
|
15
|
+
});
|
|
16
|
+
}
|
|
6
17
|
function buildDrizzleWhere(table, where) {
|
|
7
18
|
const columns = drizzleOrm.getColumns(table);
|
|
8
19
|
return processWhereClause(columns, where);
|
|
@@ -38,13 +49,26 @@ function processWhereClause(columns, where) {
|
|
|
38
49
|
parts.push(drizzleOrm.not(notCondition));
|
|
39
50
|
}
|
|
40
51
|
}
|
|
41
|
-
if (parts.length === 0)
|
|
52
|
+
if (parts.length === 0) {
|
|
53
|
+
if (namesABranch(where)) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Where clause produced no condition: ${JSON.stringify(where)}. Every branch resolved to nothing, which would match every row. Pass {} to mean "no filter".`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
42
60
|
if (parts.length === 1) return parts[0];
|
|
43
61
|
return drizzleOrm.and(...parts);
|
|
44
62
|
}
|
|
45
63
|
function isWhereCondition(item) {
|
|
46
64
|
return "column" in item && "op" in item;
|
|
47
65
|
}
|
|
66
|
+
function namesABranch(where) {
|
|
67
|
+
return Boolean(where.and?.length || where.or?.length || where.not);
|
|
68
|
+
}
|
|
69
|
+
function containsPattern(value) {
|
|
70
|
+
return `%${value.replace(/[!%_]/g, (character) => `!${character}`)}%`;
|
|
71
|
+
}
|
|
48
72
|
function buildCondition(columns, cond) {
|
|
49
73
|
const column = columns[cond.column];
|
|
50
74
|
if (!column) {
|
|
@@ -83,12 +107,37 @@ function buildCondition(columns, cond) {
|
|
|
83
107
|
case "NOT BETWEEN":
|
|
84
108
|
return drizzleOrm.notBetween(col, cond.value, cond.valueTo);
|
|
85
109
|
case "CONTAINS":
|
|
86
|
-
return drizzleOrm.
|
|
110
|
+
return drizzleOrm.sql`${col} like ${containsPattern(String(cond.value))} escape '!'`;
|
|
87
111
|
default:
|
|
88
112
|
throw new Error(`Unsupported operator: ${cond.op}`);
|
|
89
113
|
}
|
|
90
114
|
}
|
|
91
115
|
|
|
116
|
+
// src/transaction-forwarders.ts
|
|
117
|
+
function createTransactionForwarders(delegator, txDb) {
|
|
118
|
+
return {
|
|
119
|
+
select: async (table, options) => {
|
|
120
|
+
return delegator.select(table, options, txDb());
|
|
121
|
+
},
|
|
122
|
+
selectOne: async (table, options) => {
|
|
123
|
+
return delegator.selectOne(table, options, txDb());
|
|
124
|
+
},
|
|
125
|
+
update: async (table, data, where, options) => {
|
|
126
|
+
return delegator.update(table, data, where, options, txDb());
|
|
127
|
+
},
|
|
128
|
+
updateCount: async (table, data, where) => {
|
|
129
|
+
return delegator.updateCount(table, data, where, txDb());
|
|
130
|
+
},
|
|
131
|
+
delete: async (table, where, options) => {
|
|
132
|
+
return delegator.delete(table, where, options, txDb());
|
|
133
|
+
},
|
|
134
|
+
upsert: async (table, data, options) => {
|
|
135
|
+
return delegator.upsert(table, data, options, txDb());
|
|
136
|
+
},
|
|
137
|
+
getDrizzle: () => txDb()
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
92
141
|
// src/types/error.ts
|
|
93
142
|
function isDatabaseError(error) {
|
|
94
143
|
return typeof error === "object" && error !== null && "kind" in error && typeof error.kind === "string";
|
|
@@ -520,6 +569,23 @@ var DrizzleAdapter = class {
|
|
|
520
569
|
if (names == null || names === "*" || !tableObj || typeof tableObj !== "object") {
|
|
521
570
|
return void 0;
|
|
522
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) {
|
|
523
589
|
const cols = drizzleOrm.getColumns(tableObj);
|
|
524
590
|
const byAnyName = {};
|
|
525
591
|
for (const [jsName, col] of Object.entries(cols)) {
|
|
@@ -528,11 +594,13 @@ var DrizzleAdapter = class {
|
|
|
528
594
|
if (typeof sqlName === "string") byAnyName[sqlName] = { jsName, col };
|
|
529
595
|
}
|
|
530
596
|
const projection = {};
|
|
597
|
+
const unresolved = [];
|
|
531
598
|
for (const name of names) {
|
|
532
599
|
const hit = byAnyName[name];
|
|
533
600
|
if (hit) projection[hit.jsName] = hit.col;
|
|
601
|
+
else unresolved.push(name);
|
|
534
602
|
}
|
|
535
|
-
return
|
|
603
|
+
return { projection, unresolved };
|
|
536
604
|
}
|
|
537
605
|
// ============================================================
|
|
538
606
|
// Connection Status (Default implementations, can override)
|
|
@@ -704,12 +772,10 @@ var DrizzleAdapter = class {
|
|
|
704
772
|
}
|
|
705
773
|
}
|
|
706
774
|
if (options?.orderBy?.length) {
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
return o.direction === "desc" ? drizzleOrm.desc(col) : drizzleOrm.asc(col);
|
|
712
|
-
}).filter(Boolean);
|
|
775
|
+
const orderClauses = buildDrizzleOrderBy(
|
|
776
|
+
drizzleOrm.getColumns(tableObj),
|
|
777
|
+
options.orderBy
|
|
778
|
+
);
|
|
713
779
|
if (orderClauses.length) {
|
|
714
780
|
query = query.orderBy(...orderClauses);
|
|
715
781
|
}
|
|
@@ -761,6 +827,93 @@ var DrizzleAdapter = class {
|
|
|
761
827
|
* });
|
|
762
828
|
* ```
|
|
763
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
|
+
}
|
|
764
917
|
async selectOne(table, options, executor) {
|
|
765
918
|
const results = await this.select(
|
|
766
919
|
table,
|
|
@@ -1305,11 +1458,11 @@ var DrizzleAdapter = class {
|
|
|
1305
1458
|
*/
|
|
1306
1459
|
async tableExists(tableName, schema) {
|
|
1307
1460
|
try {
|
|
1308
|
-
let
|
|
1461
|
+
let sql3;
|
|
1309
1462
|
const params = [];
|
|
1310
1463
|
switch (this.dialect) {
|
|
1311
1464
|
case "postgresql":
|
|
1312
|
-
|
|
1465
|
+
sql3 = `
|
|
1313
1466
|
SELECT EXISTS (
|
|
1314
1467
|
SELECT FROM information_schema.tables
|
|
1315
1468
|
WHERE table_schema = COALESCE($1, (
|
|
@@ -1323,7 +1476,7 @@ var DrizzleAdapter = class {
|
|
|
1323
1476
|
params.push(schema ?? null, tableName);
|
|
1324
1477
|
break;
|
|
1325
1478
|
case "mysql":
|
|
1326
|
-
|
|
1479
|
+
sql3 = `
|
|
1327
1480
|
SELECT COUNT(*) as count
|
|
1328
1481
|
FROM information_schema.tables
|
|
1329
1482
|
WHERE table_schema = DATABASE()
|
|
@@ -1332,7 +1485,7 @@ var DrizzleAdapter = class {
|
|
|
1332
1485
|
params.push(tableName);
|
|
1333
1486
|
break;
|
|
1334
1487
|
case "sqlite":
|
|
1335
|
-
|
|
1488
|
+
sql3 = `
|
|
1336
1489
|
SELECT COUNT(*) as count
|
|
1337
1490
|
FROM sqlite_master
|
|
1338
1491
|
WHERE type = 'table'
|
|
@@ -1348,7 +1501,7 @@ var DrizzleAdapter = class {
|
|
|
1348
1501
|
);
|
|
1349
1502
|
}
|
|
1350
1503
|
const results = await this.executeQuery(
|
|
1351
|
-
|
|
1504
|
+
sql3,
|
|
1352
1505
|
params
|
|
1353
1506
|
);
|
|
1354
1507
|
if (results.length === 0) {
|
|
@@ -1380,11 +1533,11 @@ var DrizzleAdapter = class {
|
|
|
1380
1533
|
*/
|
|
1381
1534
|
async listTables(schema) {
|
|
1382
1535
|
try {
|
|
1383
|
-
let
|
|
1536
|
+
let sql3;
|
|
1384
1537
|
const params = [];
|
|
1385
1538
|
switch (this.dialect) {
|
|
1386
1539
|
case "postgresql":
|
|
1387
|
-
|
|
1540
|
+
sql3 = `
|
|
1388
1541
|
SELECT table_name
|
|
1389
1542
|
FROM information_schema.tables
|
|
1390
1543
|
WHERE table_schema = $1
|
|
@@ -1394,7 +1547,7 @@ var DrizzleAdapter = class {
|
|
|
1394
1547
|
params.push(schema ?? "public");
|
|
1395
1548
|
break;
|
|
1396
1549
|
case "mysql":
|
|
1397
|
-
|
|
1550
|
+
sql3 = `
|
|
1398
1551
|
SELECT table_name AS table_name
|
|
1399
1552
|
FROM information_schema.tables
|
|
1400
1553
|
WHERE table_schema = DATABASE()
|
|
@@ -1403,7 +1556,7 @@ var DrizzleAdapter = class {
|
|
|
1403
1556
|
`;
|
|
1404
1557
|
break;
|
|
1405
1558
|
case "sqlite":
|
|
1406
|
-
|
|
1559
|
+
sql3 = `
|
|
1407
1560
|
SELECT name as table_name
|
|
1408
1561
|
FROM sqlite_master
|
|
1409
1562
|
WHERE type = 'table'
|
|
@@ -1419,7 +1572,7 @@ var DrizzleAdapter = class {
|
|
|
1419
1572
|
);
|
|
1420
1573
|
}
|
|
1421
1574
|
const results = await this.executeQuery(
|
|
1422
|
-
|
|
1575
|
+
sql3,
|
|
1423
1576
|
params
|
|
1424
1577
|
);
|
|
1425
1578
|
return results.map((row) => row.table_name);
|
|
@@ -1475,30 +1628,64 @@ var DrizzleAdapter = class {
|
|
|
1475
1628
|
});
|
|
1476
1629
|
}
|
|
1477
1630
|
/**
|
|
1478
|
-
*
|
|
1631
|
+
* Helper to create transaction context CRUD forwarding methods.
|
|
1632
|
+
*
|
|
1633
|
+
* @param txDb - Thunk returning the transaction-bound Drizzle executor
|
|
1634
|
+
* @returns Object providing forwarded CRUD and getDrizzle methods
|
|
1635
|
+
*
|
|
1636
|
+
* @protected
|
|
1637
|
+
*/
|
|
1638
|
+
createTransactionForwarders(txDb) {
|
|
1639
|
+
return createTransactionForwarders(this, txDb);
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Classify an error into a DatabaseError.
|
|
1479
1643
|
*
|
|
1480
1644
|
* @remarks
|
|
1481
|
-
*
|
|
1482
|
-
* Subclasses can override to add dialect-specific error classification.
|
|
1645
|
+
* Subclasses can override this method to add dialect-specific error code classification.
|
|
1483
1646
|
*
|
|
1484
1647
|
* @param error - Original error
|
|
1485
|
-
* @param
|
|
1486
|
-
* @param table - Table name
|
|
1648
|
+
* @param sql - SQL statement that caused the error (optional)
|
|
1487
1649
|
* @returns DatabaseError instance
|
|
1488
1650
|
*
|
|
1489
1651
|
* @protected
|
|
1490
1652
|
*/
|
|
1491
|
-
|
|
1653
|
+
classifyError(error, sql3) {
|
|
1492
1654
|
if (isDatabaseError(error)) {
|
|
1493
1655
|
return error;
|
|
1494
1656
|
}
|
|
1495
1657
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1496
1658
|
return this.createDatabaseError(
|
|
1497
1659
|
"query",
|
|
1498
|
-
|
|
1660
|
+
sql3 ? `Query failed: ${errorMessage}` : errorMessage,
|
|
1499
1661
|
error instanceof Error ? error : void 0
|
|
1500
1662
|
);
|
|
1501
1663
|
}
|
|
1664
|
+
/**
|
|
1665
|
+
* Handle query errors and convert to DatabaseError.
|
|
1666
|
+
*
|
|
1667
|
+
* @remarks
|
|
1668
|
+
* Protected helper for consistent error handling across CRUD operations.
|
|
1669
|
+
* Delegates to `classifyError` and attaches operation and table context if not present.
|
|
1670
|
+
* Subclasses override `classifyError` to provide dialect-specific error code classification.
|
|
1671
|
+
*
|
|
1672
|
+
* @param error - Original error
|
|
1673
|
+
* @param operation - Operation that failed
|
|
1674
|
+
* @param table - Table name
|
|
1675
|
+
* @returns DatabaseError instance
|
|
1676
|
+
*
|
|
1677
|
+
* @protected
|
|
1678
|
+
*/
|
|
1679
|
+
handleQueryError(error, operation, table) {
|
|
1680
|
+
const dbError = this.classifyError(error);
|
|
1681
|
+
if (!dbError.message.includes(operation)) {
|
|
1682
|
+
dbError.message = `${operation} operation failed on table '${table}': ${dbError.message}`;
|
|
1683
|
+
}
|
|
1684
|
+
if (!dbError.table) {
|
|
1685
|
+
dbError.table = table;
|
|
1686
|
+
}
|
|
1687
|
+
return dbError;
|
|
1688
|
+
}
|
|
1502
1689
|
};
|
|
1503
1690
|
var ALLOWED_DEFAULT_SQL_EXPRESSIONS = /* @__PURE__ */ new Set([
|
|
1504
1691
|
"current_timestamp",
|