@mantiq/database 0.0.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/README.md +19 -0
- package/package.json +77 -0
- package/src/DatabaseManager.ts +115 -0
- package/src/DatabaseServiceProvider.ts +39 -0
- package/src/contracts/Connection.ts +13 -0
- package/src/contracts/Grammar.ts +16 -0
- package/src/contracts/MongoConnection.ts +122 -0
- package/src/contracts/Paginator.ts +10 -0
- package/src/drivers/BaseGrammar.ts +220 -0
- package/src/drivers/MSSQLConnection.ts +154 -0
- package/src/drivers/MSSQLGrammar.ts +106 -0
- package/src/drivers/MongoConnection.ts +298 -0
- package/src/drivers/MongoQueryBuilderImpl.ts +77 -0
- package/src/drivers/MySQLConnection.ts +120 -0
- package/src/drivers/MySQLGrammar.ts +19 -0
- package/src/drivers/PostgresConnection.ts +125 -0
- package/src/drivers/PostgresGrammar.ts +24 -0
- package/src/drivers/SQLiteConnection.ts +125 -0
- package/src/drivers/SQLiteGrammar.ts +19 -0
- package/src/errors/ConnectionError.ts +10 -0
- package/src/errors/ModelNotFoundError.ts +14 -0
- package/src/errors/QueryError.ts +11 -0
- package/src/events/DatabaseEvents.ts +101 -0
- package/src/factories/Factory.ts +170 -0
- package/src/factories/Faker.ts +382 -0
- package/src/helpers/db.ts +37 -0
- package/src/index.ts +100 -0
- package/src/migrations/Migration.ts +12 -0
- package/src/migrations/MigrationRepository.ts +50 -0
- package/src/migrations/Migrator.ts +201 -0
- package/src/orm/Collection.ts +236 -0
- package/src/orm/Document.ts +202 -0
- package/src/orm/Model.ts +775 -0
- package/src/orm/ModelQueryBuilder.ts +415 -0
- package/src/orm/Scope.ts +39 -0
- package/src/orm/eagerLoad.ts +300 -0
- package/src/query/Builder.ts +456 -0
- package/src/query/Expression.ts +18 -0
- package/src/schema/Blueprint.ts +196 -0
- package/src/schema/ColumnDefinition.ts +93 -0
- package/src/schema/SchemaBuilder.ts +376 -0
- package/src/seeders/Seeder.ts +28 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { DatabaseConnection } from '../contracts/Connection.ts'
|
|
2
|
+
import type { SchemaBuilder } from '../schema/SchemaBuilder.ts'
|
|
3
|
+
import { QueryBuilder } from '../query/Builder.ts'
|
|
4
|
+
import { MySQLGrammar } from './MySQLGrammar.ts'
|
|
5
|
+
import { SchemaBuilderImpl } from '../schema/SchemaBuilder.ts'
|
|
6
|
+
import { ConnectionError } from '../errors/ConnectionError.ts'
|
|
7
|
+
import { QueryError } from '../errors/QueryError.ts'
|
|
8
|
+
|
|
9
|
+
export interface MySQLConfig {
|
|
10
|
+
host?: string
|
|
11
|
+
port?: number
|
|
12
|
+
database: string
|
|
13
|
+
user?: string
|
|
14
|
+
password?: string
|
|
15
|
+
pool?: { min?: number; max?: number }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class MySQLConnection implements DatabaseConnection {
|
|
19
|
+
readonly _grammar = new MySQLGrammar()
|
|
20
|
+
private pool: any = null
|
|
21
|
+
private config: MySQLConfig
|
|
22
|
+
|
|
23
|
+
constructor(config: MySQLConfig) {
|
|
24
|
+
this.config = config
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private async getPool(): Promise<any> {
|
|
28
|
+
if (!this.pool) {
|
|
29
|
+
try {
|
|
30
|
+
const mysql = await import('mysql2/promise')
|
|
31
|
+
this.pool = await mysql.createPool({
|
|
32
|
+
host: this.config.host ?? 'localhost',
|
|
33
|
+
port: this.config.port ?? 3306,
|
|
34
|
+
database: this.config.database,
|
|
35
|
+
user: this.config.user,
|
|
36
|
+
password: this.config.password,
|
|
37
|
+
connectionLimit: this.config.pool?.max ?? 10,
|
|
38
|
+
waitForConnections: true,
|
|
39
|
+
})
|
|
40
|
+
} catch (e: any) {
|
|
41
|
+
throw new ConnectionError(`MySQL connection failed: ${e.message}`, 'mysql', e)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return this.pool
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async select(sql: string, bindings: any[] = []): Promise<Record<string, any>[]> {
|
|
48
|
+
const pool = await this.getPool()
|
|
49
|
+
try {
|
|
50
|
+
const [rows] = await pool.query(sql, bindings)
|
|
51
|
+
return rows as Record<string, any>[]
|
|
52
|
+
} catch (e: any) {
|
|
53
|
+
throw new QueryError(sql, bindings, e)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async statement(sql: string, bindings: any[] = []): Promise<number> {
|
|
58
|
+
const pool = await this.getPool()
|
|
59
|
+
try {
|
|
60
|
+
const [result] = await pool.query(sql, bindings)
|
|
61
|
+
return (result as any).affectedRows ?? 0
|
|
62
|
+
} catch (e: any) {
|
|
63
|
+
throw new QueryError(sql, bindings, e)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async insertGetId(sql: string, bindings: any[] = []): Promise<number | bigint> {
|
|
68
|
+
const pool = await this.getPool()
|
|
69
|
+
try {
|
|
70
|
+
const [result] = await pool.query(sql, bindings)
|
|
71
|
+
return (result as any).insertId ?? 0
|
|
72
|
+
} catch (e: any) {
|
|
73
|
+
throw new QueryError(sql, bindings, e)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async transaction<T>(callback: (connection: DatabaseConnection) => Promise<T>): Promise<T> {
|
|
78
|
+
const pool = await this.getPool()
|
|
79
|
+
const conn = await pool.getConnection()
|
|
80
|
+
try {
|
|
81
|
+
await conn.beginTransaction()
|
|
82
|
+
const txConn: DatabaseConnection = {
|
|
83
|
+
select: async (sql, b) => { const [rows] = await conn.query(sql, b); return rows as Record<string, any>[] },
|
|
84
|
+
statement: async (sql, b) => { const [r] = await conn.query(sql, b); return (r as any).affectedRows ?? 0 },
|
|
85
|
+
insertGetId: async (sql, b) => { const [r] = await conn.query(sql, b); return (r as any).insertId ?? 0 },
|
|
86
|
+
transaction: (cb) => cb(txConn),
|
|
87
|
+
table: (name) => new QueryBuilder(txConn, name),
|
|
88
|
+
schema: () => new SchemaBuilderImpl(txConn),
|
|
89
|
+
getDriverName: () => 'mysql',
|
|
90
|
+
getTablePrefix: () => '',
|
|
91
|
+
}
|
|
92
|
+
// @ts-ignore — attach grammar
|
|
93
|
+
txConn._grammar = this._grammar
|
|
94
|
+
const result = await callback(txConn)
|
|
95
|
+
await conn.commit()
|
|
96
|
+
return result
|
|
97
|
+
} catch (e) {
|
|
98
|
+
await conn.rollback()
|
|
99
|
+
throw e
|
|
100
|
+
} finally {
|
|
101
|
+
conn.release()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
table(name: string): QueryBuilder {
|
|
106
|
+
return new QueryBuilder(this, name)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
schema(): SchemaBuilder {
|
|
110
|
+
return new SchemaBuilderImpl(this)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getDriverName(): string {
|
|
114
|
+
return 'mysql'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getTablePrefix(): string {
|
|
118
|
+
return ''
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BaseGrammar } from './BaseGrammar.ts'
|
|
2
|
+
import type { QueryState } from '../query/Builder.ts'
|
|
3
|
+
|
|
4
|
+
export class MySQLGrammar extends BaseGrammar {
|
|
5
|
+
quoteIdentifier(name: string): string {
|
|
6
|
+
if (name.includes('.')) {
|
|
7
|
+
return name.split('.').map((p) => `\`${p}\``).join('.')
|
|
8
|
+
}
|
|
9
|
+
return `\`${name}\``
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
placeholder(_index: number): string {
|
|
13
|
+
return '?'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override compileTruncate(table: string): string {
|
|
17
|
+
return `TRUNCATE TABLE ${this.quoteIdentifier(table)}`
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { DatabaseConnection } from '../contracts/Connection.ts'
|
|
2
|
+
import type { SchemaBuilder } from '../schema/SchemaBuilder.ts'
|
|
3
|
+
import { QueryBuilder } from '../query/Builder.ts'
|
|
4
|
+
import { PostgresGrammar } from './PostgresGrammar.ts'
|
|
5
|
+
import { SchemaBuilderImpl } from '../schema/SchemaBuilder.ts'
|
|
6
|
+
import { ConnectionError } from '../errors/ConnectionError.ts'
|
|
7
|
+
import { QueryError } from '../errors/QueryError.ts'
|
|
8
|
+
|
|
9
|
+
export interface PostgresConfig {
|
|
10
|
+
host?: string
|
|
11
|
+
port?: number
|
|
12
|
+
database: string
|
|
13
|
+
user?: string
|
|
14
|
+
password?: string
|
|
15
|
+
ssl?: boolean
|
|
16
|
+
pool?: { min?: number; max?: number }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class PostgresConnection implements DatabaseConnection {
|
|
20
|
+
readonly _grammar = new PostgresGrammar()
|
|
21
|
+
private client: any = null
|
|
22
|
+
private config: PostgresConfig
|
|
23
|
+
|
|
24
|
+
constructor(config: PostgresConfig) {
|
|
25
|
+
this.config = config
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private async getClient(): Promise<any> {
|
|
29
|
+
if (!this.client) {
|
|
30
|
+
try {
|
|
31
|
+
// Uses pg (node-postgres) compatible driver
|
|
32
|
+
const { default: pg } = await import('pg')
|
|
33
|
+
const pool = new pg.Pool({
|
|
34
|
+
host: this.config.host ?? 'localhost',
|
|
35
|
+
port: this.config.port ?? 5432,
|
|
36
|
+
database: this.config.database,
|
|
37
|
+
user: this.config.user,
|
|
38
|
+
password: this.config.password,
|
|
39
|
+
ssl: this.config.ssl ? { rejectUnauthorized: false } : false,
|
|
40
|
+
min: this.config.pool?.min ?? 2,
|
|
41
|
+
max: this.config.pool?.max ?? 10,
|
|
42
|
+
})
|
|
43
|
+
this.client = pool
|
|
44
|
+
} catch (e: any) {
|
|
45
|
+
throw new ConnectionError(`Postgres connection failed: ${e.message}`, 'postgres', e)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return this.client
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async select(sql: string, bindings: any[] = []): Promise<Record<string, any>[]> {
|
|
52
|
+
const pool = await this.getClient()
|
|
53
|
+
try {
|
|
54
|
+
const result = await pool.query(sql, bindings)
|
|
55
|
+
return result.rows
|
|
56
|
+
} catch (e: any) {
|
|
57
|
+
throw new QueryError(sql, bindings, e)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async statement(sql: string, bindings: any[] = []): Promise<number> {
|
|
62
|
+
const pool = await this.getClient()
|
|
63
|
+
try {
|
|
64
|
+
const result = await pool.query(sql, bindings)
|
|
65
|
+
return result.rowCount ?? 0
|
|
66
|
+
} catch (e: any) {
|
|
67
|
+
throw new QueryError(sql, bindings, e)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async insertGetId(sql: string, bindings: any[] = []): Promise<number | bigint> {
|
|
72
|
+
const pool = await this.getClient()
|
|
73
|
+
try {
|
|
74
|
+
const result = await pool.query(sql, bindings)
|
|
75
|
+
return result.rows[0]?.id ?? 0
|
|
76
|
+
} catch (e: any) {
|
|
77
|
+
throw new QueryError(sql, bindings, e)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async transaction<T>(callback: (connection: DatabaseConnection) => Promise<T>): Promise<T> {
|
|
82
|
+
const pool = await this.getClient()
|
|
83
|
+
const client = await pool.connect()
|
|
84
|
+
try {
|
|
85
|
+
await client.query('BEGIN')
|
|
86
|
+
// Create a transactional connection wrapper
|
|
87
|
+
const txConn: DatabaseConnection = {
|
|
88
|
+
select: async (sql, b) => { const r = await client.query(sql, b); return r.rows },
|
|
89
|
+
statement: async (sql, b) => { const r = await client.query(sql, b); return r.rowCount ?? 0 },
|
|
90
|
+
insertGetId: async (sql, b) => { const r = await client.query(sql, b); return r.rows[0]?.id ?? 0 },
|
|
91
|
+
transaction: (cb) => cb(txConn),
|
|
92
|
+
table: (name) => new QueryBuilder(txConn, name),
|
|
93
|
+
schema: () => new SchemaBuilderImpl(txConn),
|
|
94
|
+
getDriverName: () => 'postgres',
|
|
95
|
+
getTablePrefix: () => '',
|
|
96
|
+
}
|
|
97
|
+
// @ts-ignore — attach grammar for the builder
|
|
98
|
+
txConn._grammar = this._grammar
|
|
99
|
+
const result = await callback(txConn)
|
|
100
|
+
await client.query('COMMIT')
|
|
101
|
+
return result
|
|
102
|
+
} catch (e) {
|
|
103
|
+
await client.query('ROLLBACK')
|
|
104
|
+
throw e
|
|
105
|
+
} finally {
|
|
106
|
+
client.release()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
table(name: string): QueryBuilder {
|
|
111
|
+
return new QueryBuilder(this, name)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
schema(): SchemaBuilder {
|
|
115
|
+
return new SchemaBuilderImpl(this)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
getDriverName(): string {
|
|
119
|
+
return 'postgres'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
getTablePrefix(): string {
|
|
123
|
+
return ''
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BaseGrammar } from './BaseGrammar.ts'
|
|
2
|
+
import type { QueryState } from '../query/Builder.ts'
|
|
3
|
+
|
|
4
|
+
export class PostgresGrammar extends BaseGrammar {
|
|
5
|
+
quoteIdentifier(name: string): string {
|
|
6
|
+
if (name.includes('.')) {
|
|
7
|
+
return name.split('.').map((p) => `"${p}"`).join('.')
|
|
8
|
+
}
|
|
9
|
+
return `"${name}"`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
placeholder(index: number): string {
|
|
13
|
+
return `$${index}`
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override compileInsertGetId(table: string, data: Record<string, any>): { sql: string; bindings: any[] } {
|
|
17
|
+
const { sql, bindings } = this.compileInsert(table, data)
|
|
18
|
+
return { sql: `${sql} RETURNING id`, bindings }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override compileTruncate(table: string): string {
|
|
22
|
+
return `TRUNCATE TABLE ${this.quoteIdentifier(table)} RESTART IDENTITY CASCADE`
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { DatabaseConnection } from '../contracts/Connection.ts'
|
|
2
|
+
import type { SchemaBuilder } from '../schema/SchemaBuilder.ts'
|
|
3
|
+
import type { EventDispatcher } from '@mantiq/core'
|
|
4
|
+
import { QueryBuilder } from '../query/Builder.ts'
|
|
5
|
+
import { SQLiteGrammar } from './SQLiteGrammar.ts'
|
|
6
|
+
import { SchemaBuilderImpl } from '../schema/SchemaBuilder.ts'
|
|
7
|
+
import { ConnectionError } from '../errors/ConnectionError.ts'
|
|
8
|
+
import { QueryError } from '../errors/QueryError.ts'
|
|
9
|
+
import { QueryExecuted, TransactionBeginning, TransactionCommitted, TransactionRolledBack } from '../events/DatabaseEvents.ts'
|
|
10
|
+
|
|
11
|
+
export interface SQLiteConfig {
|
|
12
|
+
database: string // ':memory:' or file path
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class SQLiteConnection implements DatabaseConnection {
|
|
16
|
+
readonly _grammar = new SQLiteGrammar()
|
|
17
|
+
private db: import('bun:sqlite').Database | null = null
|
|
18
|
+
private config: SQLiteConfig
|
|
19
|
+
|
|
20
|
+
/** Optional event dispatcher. Set by @mantiq/events when installed. */
|
|
21
|
+
static _dispatcher: EventDispatcher | null = null
|
|
22
|
+
|
|
23
|
+
constructor(config: SQLiteConfig) {
|
|
24
|
+
this.config = config
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private getDb(): import('bun:sqlite').Database {
|
|
28
|
+
if (!this.db) {
|
|
29
|
+
try {
|
|
30
|
+
const { Database } = require('bun:sqlite') as typeof import('bun:sqlite')
|
|
31
|
+
this.db = new Database(this.config.database, { create: true })
|
|
32
|
+
this.db.run('PRAGMA journal_mode = WAL')
|
|
33
|
+
this.db.run('PRAGMA foreign_keys = ON')
|
|
34
|
+
} catch (e: any) {
|
|
35
|
+
throw new ConnectionError(`SQLite connection failed: ${e.message}`, 'sqlite', e)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return this.db
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** bun:sqlite only accepts string | number | bigint | boolean | Uint8Array | null */
|
|
42
|
+
private sanitizeBindings(bindings: any[]): any[] {
|
|
43
|
+
return bindings.map((v) => {
|
|
44
|
+
if (v instanceof Date) return v.toISOString()
|
|
45
|
+
return v
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async select(sql: string, bindings: any[] = []): Promise<Record<string, any>[]> {
|
|
50
|
+
try {
|
|
51
|
+
const start = performance.now()
|
|
52
|
+
const stmt = this.getDb().prepare(sql)
|
|
53
|
+
const result = stmt.all(...this.sanitizeBindings(bindings)) as Record<string, any>[]
|
|
54
|
+
await this.fireQueryEvent(sql, bindings, performance.now() - start)
|
|
55
|
+
return result
|
|
56
|
+
} catch (e: any) {
|
|
57
|
+
throw new QueryError(sql, bindings, e)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async statement(sql: string, bindings: any[] = []): Promise<number> {
|
|
62
|
+
try {
|
|
63
|
+
const start = performance.now()
|
|
64
|
+
const stmt = this.getDb().prepare(sql)
|
|
65
|
+
const result = stmt.run(...this.sanitizeBindings(bindings))
|
|
66
|
+
await this.fireQueryEvent(sql, bindings, performance.now() - start)
|
|
67
|
+
return result.changes
|
|
68
|
+
} catch (e: any) {
|
|
69
|
+
throw new QueryError(sql, bindings, e)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async insertGetId(sql: string, bindings: any[] = []): Promise<number | bigint> {
|
|
74
|
+
try {
|
|
75
|
+
const start = performance.now()
|
|
76
|
+
const stmt = this.getDb().prepare(sql)
|
|
77
|
+
const result = stmt.run(...this.sanitizeBindings(bindings))
|
|
78
|
+
await this.fireQueryEvent(sql, bindings, performance.now() - start)
|
|
79
|
+
return result.lastInsertRowid
|
|
80
|
+
} catch (e: any) {
|
|
81
|
+
throw new QueryError(sql, bindings, e)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async transaction<T>(callback: (connection: DatabaseConnection) => Promise<T>): Promise<T> {
|
|
86
|
+
const db = this.getDb()
|
|
87
|
+
db.run('BEGIN')
|
|
88
|
+
await SQLiteConnection._dispatcher?.emit(new TransactionBeginning('sqlite'))
|
|
89
|
+
try {
|
|
90
|
+
const result = await callback(this)
|
|
91
|
+
db.run('COMMIT')
|
|
92
|
+
await SQLiteConnection._dispatcher?.emit(new TransactionCommitted('sqlite'))
|
|
93
|
+
return result
|
|
94
|
+
} catch (e) {
|
|
95
|
+
db.run('ROLLBACK')
|
|
96
|
+
await SQLiteConnection._dispatcher?.emit(new TransactionRolledBack('sqlite'))
|
|
97
|
+
throw e
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async fireQueryEvent(sql: string, bindings: any[], time: number): Promise<void> {
|
|
102
|
+
await SQLiteConnection._dispatcher?.emit(new QueryExecuted(sql, bindings, time, 'sqlite'))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
table(name: string): QueryBuilder {
|
|
106
|
+
return new QueryBuilder(this, name)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
schema(): SchemaBuilder {
|
|
110
|
+
return new SchemaBuilderImpl(this)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
getDriverName(): string {
|
|
114
|
+
return 'sqlite'
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getTablePrefix(): string {
|
|
118
|
+
return ''
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
close(): void {
|
|
122
|
+
this.db?.close()
|
|
123
|
+
this.db = null
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BaseGrammar } from './BaseGrammar.ts'
|
|
2
|
+
|
|
3
|
+
export class SQLiteGrammar extends BaseGrammar {
|
|
4
|
+
quoteIdentifier(name: string): string {
|
|
5
|
+
// Handle table.column notation
|
|
6
|
+
if (name.includes('.')) {
|
|
7
|
+
return name.split('.').map((p) => `"${p}"`).join('.')
|
|
8
|
+
}
|
|
9
|
+
return `"${name}"`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
placeholder(_index: number): string {
|
|
13
|
+
return '?'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override compileTruncate(table: string): string {
|
|
17
|
+
return `DELETE FROM ${this.quoteIdentifier(table)}`
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { MantiqError } from '@mantiq/core'
|
|
2
|
+
|
|
3
|
+
export class ConnectionError extends MantiqError {
|
|
4
|
+
constructor(
|
|
5
|
+
public readonly driver: string,
|
|
6
|
+
originalError: Error,
|
|
7
|
+
) {
|
|
8
|
+
super(`Failed to connect to ${driver} database: ${originalError.message}`)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MantiqError } from '@mantiq/core'
|
|
2
|
+
|
|
3
|
+
export class ModelNotFoundError extends MantiqError {
|
|
4
|
+
constructor(
|
|
5
|
+
public readonly modelName: string,
|
|
6
|
+
public readonly id?: any,
|
|
7
|
+
) {
|
|
8
|
+
super(
|
|
9
|
+
id !== undefined
|
|
10
|
+
? `No ${modelName} found with ID ${id}.`
|
|
11
|
+
: `No ${modelName} matching the given conditions.`,
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { MantiqError } from '@mantiq/core'
|
|
2
|
+
|
|
3
|
+
export class QueryError extends MantiqError {
|
|
4
|
+
constructor(
|
|
5
|
+
public readonly sql: string,
|
|
6
|
+
public readonly bindings: any[],
|
|
7
|
+
public readonly originalError: Error,
|
|
8
|
+
) {
|
|
9
|
+
super(`Database query failed: ${originalError.message}`, { sql, bindings })
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Event } from '@mantiq/core'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fired after a database query is executed.
|
|
5
|
+
* Useful for query logging, debugging, and performance monitoring.
|
|
6
|
+
*/
|
|
7
|
+
export class QueryExecuted extends Event {
|
|
8
|
+
constructor(
|
|
9
|
+
/** The SQL query string. */
|
|
10
|
+
public readonly sql: string,
|
|
11
|
+
/** The query bindings. */
|
|
12
|
+
public readonly bindings: any[],
|
|
13
|
+
/** Time in milliseconds the query took to execute. */
|
|
14
|
+
public readonly time: number,
|
|
15
|
+
/** The connection name (e.g. 'sqlite'). */
|
|
16
|
+
public readonly connectionName: string,
|
|
17
|
+
) {
|
|
18
|
+
super()
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fired when a database transaction begins.
|
|
24
|
+
*/
|
|
25
|
+
export class TransactionBeginning extends Event {
|
|
26
|
+
constructor(public readonly connectionName: string) {
|
|
27
|
+
super()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Fired when a database transaction is committed.
|
|
33
|
+
*/
|
|
34
|
+
export class TransactionCommitted extends Event {
|
|
35
|
+
constructor(public readonly connectionName: string) {
|
|
36
|
+
super()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Fired when a database transaction is rolled back.
|
|
42
|
+
*/
|
|
43
|
+
export class TransactionRolledBack extends Event {
|
|
44
|
+
constructor(public readonly connectionName: string) {
|
|
45
|
+
super()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Migration Events ─────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Fired before a single migration is executed (up or down).
|
|
53
|
+
*/
|
|
54
|
+
export class MigrationStarted extends Event {
|
|
55
|
+
constructor(
|
|
56
|
+
/** The migration name (e.g. '2024_01_01_create_users_table'). */
|
|
57
|
+
public readonly migration: string,
|
|
58
|
+
/** 'up' for running, 'down' for rolling back. */
|
|
59
|
+
public readonly method: 'up' | 'down',
|
|
60
|
+
) {
|
|
61
|
+
super()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Fired after a single migration has been executed (up or down).
|
|
67
|
+
*/
|
|
68
|
+
export class MigrationEnded extends Event {
|
|
69
|
+
constructor(
|
|
70
|
+
/** The migration name. */
|
|
71
|
+
public readonly migration: string,
|
|
72
|
+
/** 'up' for running, 'down' for rolling back. */
|
|
73
|
+
public readonly method: 'up' | 'down',
|
|
74
|
+
) {
|
|
75
|
+
super()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Fired before a batch of migrations starts (run/rollback/reset).
|
|
81
|
+
*/
|
|
82
|
+
export class MigrationsStarted extends Event {
|
|
83
|
+
constructor(
|
|
84
|
+
/** 'up' for running, 'down' for rolling back. */
|
|
85
|
+
public readonly method: 'up' | 'down',
|
|
86
|
+
) {
|
|
87
|
+
super()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Fired after a batch of migrations finishes (run/rollback/reset).
|
|
93
|
+
*/
|
|
94
|
+
export class MigrationsEnded extends Event {
|
|
95
|
+
constructor(
|
|
96
|
+
/** 'up' for running, 'down' for rolling back. */
|
|
97
|
+
public readonly method: 'up' | 'down',
|
|
98
|
+
) {
|
|
99
|
+
super()
|
|
100
|
+
}
|
|
101
|
+
}
|