@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mantiq/database",
3
- "version": "0.1.4",
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"
@@ -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, MongoDatabaseConnection>()
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): MongoDatabaseConnection {
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 implements DatabaseConnection {
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 mssql = await import('mssql')
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 mssql = await import('mssql')
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: DatabaseConnection = {
104
- select: async (s, b = []) => {
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
- // @ts-ignore — attach grammar
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
  }