@mantiq/database 0.1.4 → 0.3.0-rc.1
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/package.json +2 -2
- package/src/DatabaseManager.ts +10 -11
- package/src/contracts/Connection.ts +15 -2
- package/src/drivers/BaseSQLConnection.ts +111 -0
- package/src/drivers/MSSQLConnection.ts +15 -19
- package/src/drivers/MongoConnection.ts +289 -114
- package/src/drivers/MongoQueryBuilderImpl.ts +4 -4
- package/src/drivers/MySQLConnection.ts +17 -23
- package/src/drivers/PostgresConnection.ts +17 -24
- package/src/drivers/SQLiteConnection.ts +4 -10
- package/src/errors/ConnectionError.ts +5 -5
- package/src/errors/DriverNotSupportedError.ts +6 -0
- package/src/index.ts +6 -1
- package/src/orm/Document.ts +14 -13
- package/src/orm/Model.ts +11 -2
- package/src/orm/ModelQueryBuilder.ts +8 -7
- package/src/query/Builder.ts +44 -51
- package/src/schema/Blueprint.ts +3 -3
- package/src/schema/ColumnDefinition.ts +5 -5
- package/src/schema/SchemaBuilder.ts +5 -2
- package/src/types.d.ts +21 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mantiq/database",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.0-rc.1",
|
|
4
4
|
"description": "Query builder, ORM, migrations, seeders, factories — with SQLite, Postgres, MySQL and MongoDB support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"LICENSE"
|
|
41
41
|
],
|
|
42
42
|
"scripts": {
|
|
43
|
-
"build": "bun build ./src/index.ts --outdir ./dist --target bun",
|
|
43
|
+
"build": "bun build ./src/index.ts --outdir ./dist --target bun --packages=external",
|
|
44
44
|
"test": "bun test",
|
|
45
45
|
"typecheck": "tsc --noEmit",
|
|
46
46
|
"clean": "rm -rf dist"
|
package/src/DatabaseManager.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { DatabaseConnection } from './contracts/Connection.ts'
|
|
2
|
-
import type { MongoDatabaseConnection } from './contracts/MongoConnection.ts'
|
|
3
2
|
import { SQLiteConnection } from './drivers/SQLiteConnection.ts'
|
|
4
3
|
import { PostgresConnection } from './drivers/PostgresConnection.ts'
|
|
5
4
|
import { MySQLConnection } from './drivers/MySQLConnection.ts'
|
|
@@ -9,31 +8,31 @@ import { ConnectionError } from './errors/ConnectionError.ts'
|
|
|
9
8
|
export interface SQLConfig {
|
|
10
9
|
driver: 'sqlite' | 'postgres' | 'mysql'
|
|
11
10
|
database: string
|
|
12
|
-
host?: string
|
|
13
|
-
port?: number
|
|
14
|
-
user?: string
|
|
15
|
-
password?: string
|
|
16
|
-
ssl?: boolean
|
|
17
|
-
pool?: { min?: number; max?: number }
|
|
11
|
+
host?: string | undefined
|
|
12
|
+
port?: number | undefined
|
|
13
|
+
user?: string | undefined
|
|
14
|
+
password?: string | undefined
|
|
15
|
+
ssl?: boolean | undefined
|
|
16
|
+
pool?: { min?: number | undefined; max?: number | undefined } | undefined
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
export interface MongoConfig {
|
|
21
20
|
driver: 'mongodb'
|
|
22
21
|
uri: string
|
|
23
22
|
database: string
|
|
24
|
-
options?: Record<string, any>
|
|
23
|
+
options?: Record<string, any> | undefined
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
export type ConnectionConfig = SQLConfig | MongoConfig
|
|
28
27
|
|
|
29
28
|
export interface DatabaseConfig {
|
|
30
|
-
default?: string
|
|
29
|
+
default?: string | undefined
|
|
31
30
|
connections: Record<string, ConnectionConfig>
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
export class DatabaseManager {
|
|
35
34
|
private sqlConnections = new Map<string, DatabaseConnection>()
|
|
36
|
-
private mongoConnections = new Map<string,
|
|
35
|
+
private mongoConnections = new Map<string, MongoConnection>()
|
|
37
36
|
|
|
38
37
|
constructor(private readonly config: DatabaseConfig) {}
|
|
39
38
|
|
|
@@ -51,7 +50,7 @@ export class DatabaseManager {
|
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
/** Get a MongoDB connection by name */
|
|
54
|
-
mongo(name?: string):
|
|
53
|
+
mongo(name?: string): MongoConnection {
|
|
55
54
|
const connName = name ?? this.config.default ?? 'default'
|
|
56
55
|
if (this.mongoConnections.has(connName)) return this.mongoConnections.get(connName)!
|
|
57
56
|
|
|
@@ -1,10 +1,23 @@
|
|
|
1
|
-
import type { QueryBuilder } from '../query/Builder.ts'
|
|
1
|
+
import type { QueryBuilder, QueryState } from '../query/Builder.ts'
|
|
2
2
|
import type { SchemaBuilder } from '../schema/SchemaBuilder.ts'
|
|
3
3
|
|
|
4
4
|
export interface DatabaseConnection {
|
|
5
|
+
// ── Universal execution (works on ALL drivers) ──────────────────────────
|
|
6
|
+
executeSelect(state: QueryState): Promise<Record<string, any>[]>
|
|
7
|
+
executeInsert(table: string, data: Record<string, any>): Promise<number>
|
|
8
|
+
executeInsertGetId(table: string, data: Record<string, any>): Promise<number | string>
|
|
9
|
+
executeUpdate(table: string, state: QueryState, data: Record<string, any>): Promise<number>
|
|
10
|
+
executeDelete(table: string, state: QueryState): Promise<number>
|
|
11
|
+
executeTruncate(table: string): Promise<void>
|
|
12
|
+
executeAggregate(state: QueryState, fn: 'count' | 'sum' | 'avg' | 'min' | 'max', column: string): Promise<number>
|
|
13
|
+
executeExists(state: QueryState): Promise<boolean>
|
|
14
|
+
|
|
15
|
+
// ── Raw SQL escape hatch (throws DriverNotSupportedError on non-SQL) ───
|
|
5
16
|
select(sql: string, bindings?: any[]): Promise<Record<string, any>[]>
|
|
6
17
|
statement(sql: string, bindings?: any[]): Promise<number>
|
|
7
|
-
insertGetId(sql: string, bindings?: any[]): Promise<number | bigint>
|
|
18
|
+
insertGetId(sql: string, bindings?: any[]): Promise<number | bigint | string>
|
|
19
|
+
|
|
20
|
+
// ── Shared ──────────────────────────────────────────────────────────────
|
|
8
21
|
transaction<T>(callback: (connection: DatabaseConnection) => Promise<T>): Promise<T>
|
|
9
22
|
table(name: string): QueryBuilder
|
|
10
23
|
schema(): SchemaBuilder
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { DatabaseConnection } from '../contracts/Connection.ts'
|
|
2
|
+
import type { Grammar } from '../contracts/Grammar.ts'
|
|
3
|
+
import type { QueryState } from '../query/Builder.ts'
|
|
4
|
+
import type { SchemaBuilder } from '../schema/SchemaBuilder.ts'
|
|
5
|
+
import { QueryBuilder } from '../query/Builder.ts'
|
|
6
|
+
import { Expression } from '../query/Expression.ts'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Abstract base for all SQL connections. Provides `executeXxx()` methods
|
|
10
|
+
* by compiling QueryState via Grammar and delegating to the raw SQL methods
|
|
11
|
+
* that each driver must implement.
|
|
12
|
+
*/
|
|
13
|
+
export abstract class BaseSQLConnection implements DatabaseConnection {
|
|
14
|
+
abstract readonly _grammar: Grammar
|
|
15
|
+
|
|
16
|
+
// ── Subclasses implement these (raw SQL execution) ──────────────────────
|
|
17
|
+
abstract select(sql: string, bindings?: any[]): Promise<Record<string, any>[]>
|
|
18
|
+
abstract statement(sql: string, bindings?: any[]): Promise<number>
|
|
19
|
+
abstract insertGetId(sql: string, bindings?: any[]): Promise<number | bigint | string>
|
|
20
|
+
abstract transaction<T>(callback: (connection: DatabaseConnection) => Promise<T>): Promise<T>
|
|
21
|
+
abstract schema(): SchemaBuilder
|
|
22
|
+
abstract getDriverName(): string
|
|
23
|
+
|
|
24
|
+
getTablePrefix(): string {
|
|
25
|
+
return ''
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
table(name: string): QueryBuilder {
|
|
29
|
+
return new QueryBuilder(this, name)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ── Universal executeXxx (compile via Grammar → run via raw methods) ────
|
|
33
|
+
|
|
34
|
+
async executeSelect(state: QueryState): Promise<Record<string, any>[]> {
|
|
35
|
+
const { sql, bindings } = this._grammar.compileSelect(state)
|
|
36
|
+
return this.select(sql, bindings)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async executeInsert(table: string, data: Record<string, any>): Promise<number> {
|
|
40
|
+
const { sql, bindings } = this._grammar.compileInsert(table, data)
|
|
41
|
+
return this.statement(sql, bindings)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async executeInsertGetId(table: string, data: Record<string, any>): Promise<number | string> {
|
|
45
|
+
const { sql, bindings } = this._grammar.compileInsertGetId(table, data)
|
|
46
|
+
const id = await this.insertGetId(sql, bindings)
|
|
47
|
+
// SQL drivers always return numeric IDs (bigint from SQLite, string from pg for BIGSERIAL)
|
|
48
|
+
return Number(id)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async executeUpdate(table: string, state: QueryState, data: Record<string, any>): Promise<number> {
|
|
52
|
+
const { sql, bindings } = this._grammar.compileUpdate(table, state, data)
|
|
53
|
+
return this.statement(sql, bindings)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async executeDelete(table: string, state: QueryState): Promise<number> {
|
|
57
|
+
const { sql, bindings } = this._grammar.compileDelete(table, state)
|
|
58
|
+
return this.statement(sql, bindings)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async executeTruncate(table: string): Promise<void> {
|
|
62
|
+
const sql = this._grammar.compileTruncate(table)
|
|
63
|
+
await this.statement(sql, [])
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async executeAggregate(state: QueryState, fn: 'count' | 'sum' | 'avg' | 'min' | 'max', column: string): Promise<number> {
|
|
67
|
+
const aggState: QueryState = {
|
|
68
|
+
...state,
|
|
69
|
+
columns: [new Expression(`${fn.toUpperCase()}(${column}) as aggregate`)],
|
|
70
|
+
orders: [], // aggregates don't need ORDER BY
|
|
71
|
+
}
|
|
72
|
+
const { sql, bindings } = this._grammar.compileSelect(aggState)
|
|
73
|
+
const rows = await this.select(sql, bindings)
|
|
74
|
+
return Number(rows[0]?.['aggregate'] ?? 0)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async executeExists(state: QueryState): Promise<boolean> {
|
|
78
|
+
const existsState: QueryState = {
|
|
79
|
+
...state,
|
|
80
|
+
columns: [new Expression('1 as exists_check')],
|
|
81
|
+
limitValue: 1,
|
|
82
|
+
orders: [],
|
|
83
|
+
}
|
|
84
|
+
const { sql, bindings } = this._grammar.compileSelect(existsState)
|
|
85
|
+
const rows = await this.select(sql, bindings)
|
|
86
|
+
return rows.length > 0
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates executeXxx methods for a transactional connection wrapper.
|
|
91
|
+
* Call this in transaction() to give the txConn the universal methods.
|
|
92
|
+
*/
|
|
93
|
+
protected applyExecuteMethods(txConn: any): void {
|
|
94
|
+
txConn.executeSelect = (state: QueryState) =>
|
|
95
|
+
BaseSQLConnection.prototype.executeSelect.call({ ...txConn, _grammar: this._grammar }, state)
|
|
96
|
+
txConn.executeInsert = (table: string, data: Record<string, any>) =>
|
|
97
|
+
BaseSQLConnection.prototype.executeInsert.call({ ...txConn, _grammar: this._grammar }, table, data)
|
|
98
|
+
txConn.executeInsertGetId = (table: string, data: Record<string, any>) =>
|
|
99
|
+
BaseSQLConnection.prototype.executeInsertGetId.call({ ...txConn, _grammar: this._grammar }, table, data)
|
|
100
|
+
txConn.executeUpdate = (table: string, state: QueryState, data: Record<string, any>) =>
|
|
101
|
+
BaseSQLConnection.prototype.executeUpdate.call({ ...txConn, _grammar: this._grammar }, table, state, data)
|
|
102
|
+
txConn.executeDelete = (table: string, state: QueryState) =>
|
|
103
|
+
BaseSQLConnection.prototype.executeDelete.call({ ...txConn, _grammar: this._grammar }, table, state)
|
|
104
|
+
txConn.executeTruncate = (table: string) =>
|
|
105
|
+
BaseSQLConnection.prototype.executeTruncate.call({ ...txConn, _grammar: this._grammar }, table)
|
|
106
|
+
txConn.executeAggregate = (state: QueryState, fn: 'count' | 'sum' | 'avg' | 'min' | 'max', column: string) =>
|
|
107
|
+
BaseSQLConnection.prototype.executeAggregate.call({ ...txConn, _grammar: this._grammar }, state, fn, column)
|
|
108
|
+
txConn.executeExists = (state: QueryState) =>
|
|
109
|
+
BaseSQLConnection.prototype.executeExists.call({ ...txConn, _grammar: this._grammar }, state)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DatabaseConnection } from '../contracts/Connection.ts'
|
|
2
2
|
import type { SchemaBuilder } from '../schema/SchemaBuilder.ts'
|
|
3
|
+
import { BaseSQLConnection } from './BaseSQLConnection.ts'
|
|
3
4
|
import { QueryBuilder } from '../query/Builder.ts'
|
|
4
5
|
import { MSSQLGrammar } from './MSSQLGrammar.ts'
|
|
5
6
|
import { SchemaBuilderImpl } from '../schema/SchemaBuilder.ts'
|
|
@@ -17,19 +18,21 @@ export interface MSSQLConfig {
|
|
|
17
18
|
pool?: { min?: number; max?: number }
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export class MSSQLConnection
|
|
21
|
+
export class MSSQLConnection extends BaseSQLConnection {
|
|
21
22
|
readonly _grammar = new MSSQLGrammar()
|
|
22
23
|
private pool: any = null
|
|
23
24
|
private config: MSSQLConfig
|
|
24
25
|
|
|
25
26
|
constructor(config: MSSQLConfig) {
|
|
27
|
+
super()
|
|
26
28
|
this.config = config
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
private async getPool(): Promise<any> {
|
|
30
32
|
if (!this.pool) {
|
|
31
33
|
try {
|
|
32
|
-
const
|
|
34
|
+
const mssqlModule = 'mssql'
|
|
35
|
+
const mssql: any = await import(mssqlModule)
|
|
33
36
|
const sql = mssql.default ?? mssql
|
|
34
37
|
this.pool = await sql.connect({
|
|
35
38
|
server: this.config.host ?? 'localhost',
|
|
@@ -95,38 +98,39 @@ export class MSSQLConnection implements DatabaseConnection {
|
|
|
95
98
|
|
|
96
99
|
async transaction<T>(callback: (connection: DatabaseConnection) => Promise<T>): Promise<T> {
|
|
97
100
|
const pool = await this.getPool()
|
|
98
|
-
const
|
|
101
|
+
const mssqlModule = 'mssql'
|
|
102
|
+
const mssql: any = await import(mssqlModule)
|
|
99
103
|
const sql = mssql.default ?? mssql
|
|
100
104
|
const transaction = new sql.Transaction(pool)
|
|
101
105
|
await transaction.begin()
|
|
102
106
|
try {
|
|
103
|
-
const txConn:
|
|
104
|
-
|
|
107
|
+
const txConn: any = {
|
|
108
|
+
_grammar: this._grammar,
|
|
109
|
+
select: async (s: string, b: any[] = []) => {
|
|
105
110
|
const req = transaction.request()
|
|
106
111
|
b.forEach((val: any, i: number) => req.input(`p${i + 1}`, val))
|
|
107
112
|
const r = await req.query(s)
|
|
108
113
|
return r.recordset ?? []
|
|
109
114
|
},
|
|
110
|
-
statement: async (s, b = []) => {
|
|
115
|
+
statement: async (s: string, b: any[] = []) => {
|
|
111
116
|
const req = transaction.request()
|
|
112
117
|
b.forEach((val: any, i: number) => req.input(`p${i + 1}`, val))
|
|
113
118
|
const r = await req.query(s)
|
|
114
119
|
return r.rowsAffected?.[0] ?? 0
|
|
115
120
|
},
|
|
116
|
-
insertGetId: async (s, b = []) => {
|
|
121
|
+
insertGetId: async (s: string, b: any[] = []) => {
|
|
117
122
|
const req = transaction.request()
|
|
118
123
|
b.forEach((val: any, i: number) => req.input(`p${i + 1}`, val))
|
|
119
124
|
const r = await req.query(s)
|
|
120
125
|
return r.recordset?.[0]?.id ?? 0
|
|
121
126
|
},
|
|
122
|
-
transaction: (cb) => cb(txConn),
|
|
123
|
-
table: (name) => new QueryBuilder(txConn, name),
|
|
127
|
+
transaction: (cb: any) => cb(txConn),
|
|
128
|
+
table: (name: string) => new QueryBuilder(txConn, name),
|
|
124
129
|
schema: () => new SchemaBuilderImpl(txConn),
|
|
125
130
|
getDriverName: () => 'mssql',
|
|
126
131
|
getTablePrefix: () => '',
|
|
127
132
|
}
|
|
128
|
-
|
|
129
|
-
txConn._grammar = this._grammar
|
|
133
|
+
this.applyExecuteMethods(txConn)
|
|
130
134
|
const result = await callback(txConn)
|
|
131
135
|
await transaction.commit()
|
|
132
136
|
return result
|
|
@@ -136,10 +140,6 @@ export class MSSQLConnection implements DatabaseConnection {
|
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
|
|
139
|
-
table(name: string): QueryBuilder {
|
|
140
|
-
return new QueryBuilder(this, name)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
143
|
schema(): SchemaBuilder {
|
|
144
144
|
return new SchemaBuilderImpl(this)
|
|
145
145
|
}
|
|
@@ -147,8 +147,4 @@ export class MSSQLConnection implements DatabaseConnection {
|
|
|
147
147
|
getDriverName(): string {
|
|
148
148
|
return 'mssql'
|
|
149
149
|
}
|
|
150
|
-
|
|
151
|
-
getTablePrefix(): string {
|
|
152
|
-
return ''
|
|
153
|
-
}
|
|
154
150
|
}
|