@objectstack/driver-sql 3.2.9 → 3.3.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sql-driver.ts","../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * SQL Driver for ObjectStack\n *\n * Implements the standard DriverInterface from @objectstack/spec via Knex.js.\n * Supports PostgreSQL, MySQL, SQLite, and other SQL databases.\n */\n\nimport type { QueryInput, DriverOptions } from '@objectstack/spec/data';\nimport type { DriverInterface } from '@objectstack/core';\nimport knex, { Knex } from 'knex';\nimport { nanoid } from 'nanoid';\n\n/**\n * Default ID length for auto-generated IDs.\n */\nconst DEFAULT_ID_LENGTH = 16;\n\n// ── Introspection Types ──────────────────────────────────────────────────────\n\nexport interface IntrospectedColumn {\n name: string;\n type: string;\n nullable: boolean;\n defaultValue?: unknown;\n isPrimary?: boolean;\n isUnique?: boolean;\n maxLength?: number;\n}\n\nexport interface IntrospectedForeignKey {\n columnName: string;\n referencedTable: string;\n referencedColumn: string;\n constraintName?: string;\n}\n\nexport interface IntrospectedTable {\n name: string;\n columns: IntrospectedColumn[];\n foreignKeys: IntrospectedForeignKey[];\n primaryKeys: string[];\n}\n\nexport interface IntrospectedSchema {\n tables: Record<string, IntrospectedTable>;\n}\n\n// ── Configuration Types ──────────────────────────────────────────────────────\n\n/**\n * SqlDriver configuration — passed directly to Knex.\n * See https://knexjs.org/guide/#configuration-options\n */\nexport type SqlDriverConfig = Knex.Config;\n\n// ── SQL Driver ───────────────────────────────────────────────────────────────\n\n/**\n * SQL Driver for ObjectStack.\n *\n * Implements the DriverInterface contract via Knex.js for optimal SQL\n * generation against PostgreSQL, MySQL, SQLite and other SQL databases.\n */\nexport class SqlDriver implements DriverInterface {\n // DriverInterface metadata\n public readonly name = 'com.objectstack.driver.sql';\n public readonly version = '1.0.0';\n public readonly supports = {\n transactions: true,\n joins: true,\n fullTextSearch: false,\n jsonFields: true,\n arrayFields: true,\n queryFilters: true,\n queryAggregations: true,\n querySorting: true,\n queryPagination: true,\n queryWindowFunctions: true,\n querySubqueries: true,\n };\n\n private knex: Knex;\n private config: Knex.Config;\n private jsonFields: Record<string, string[]> = {};\n private booleanFields: Record<string, string[]> = {};\n private tablesWithTimestamps: Set<string> = new Set();\n\n /** Whether the underlying database is a SQLite variant (sqlite3 or better-sqlite3). */\n private get isSqlite(): boolean {\n const c = (this.config as any).client;\n return c === 'sqlite3' || c === 'better-sqlite3';\n }\n\n /** Whether the underlying database is PostgreSQL. */\n private get isPostgres(): boolean {\n const c = (this.config as any).client;\n return c === 'pg' || c === 'postgresql';\n }\n\n /** Whether the underlying database is MySQL. */\n private get isMysql(): boolean {\n const c = (this.config as any).client;\n return c === 'mysql' || c === 'mysql2';\n }\n\n constructor(config: SqlDriverConfig) {\n this.config = config;\n this.knex = knex(config);\n }\n\n // ===================================\n // Lifecycle\n // ===================================\n\n async connect(): Promise<void> {\n return Promise.resolve();\n }\n\n async checkHealth(): Promise<boolean> {\n try {\n await this.knex.raw('SELECT 1');\n return true;\n } catch {\n return false;\n }\n }\n\n async disconnect(): Promise<void> {\n await this.knex.destroy();\n }\n\n // ===================================\n // CRUD — DriverInterface core\n // ===================================\n\n async find(object: string, query: QueryInput, options?: DriverOptions): Promise<any[]> {\n const builder = this.getBuilder(object, options);\n\n // SELECT\n if (query.fields) {\n builder.select((query.fields as string[]).map((f: string) => this.mapSortField(f)));\n } else {\n builder.select('*');\n }\n\n // WHERE — support both `where` (standard) and `filters` (legacy)\n const filterCondition = query.where || (query as any).filters;\n if (filterCondition) {\n this.applyFilters(builder, filterCondition);\n }\n\n // ORDER BY — support both `orderBy` (standard) and `sort` (legacy)\n const sortArray = query.orderBy || (query as any).sort;\n if (sortArray && Array.isArray(sortArray)) {\n for (const item of sortArray) {\n const field = item.field || item[0];\n const dir = item.order || item[1] || 'asc';\n if (field) {\n builder.orderBy(this.mapSortField(field), dir);\n }\n }\n }\n\n // PAGINATION — support both offset/limit (standard) and skip/top (legacy)\n const offsetValue = query.offset ?? (query as any).skip;\n const limitValue = query.limit ?? (query as any).top;\n\n if (offsetValue !== undefined) builder.offset(offsetValue);\n if (limitValue !== undefined) builder.limit(limitValue);\n\n let results: any[];\n try {\n results = await builder;\n } catch (error: any) {\n if (\n error.message &&\n (error.message.includes('no such column') ||\n (error.message.includes('column') && error.message.includes('does not exist')))\n ) {\n return [];\n }\n throw error;\n }\n\n if (!Array.isArray(results)) {\n return [];\n }\n\n if (this.isSqlite) {\n for (const row of results) {\n this.formatOutput(object, row);\n }\n }\n return results;\n }\n\n async findOne(object: string, query: QueryInput, options?: DriverOptions): Promise<any> {\n // When called with a string/number id fall back gracefully\n if (typeof query === 'string' || typeof query === 'number') {\n const res = await this.getBuilder(object, options).where('id', query).first();\n return this.formatOutput(object, res) || null;\n }\n\n if (query && typeof query === 'object') {\n const results = await this.find(object, { ...query, limit: 1 }, options);\n return results[0] || null;\n }\n\n return null;\n }\n\n async create(object: string, data: Record<string, any>, options?: DriverOptions): Promise<any> {\n const { _id, ...rest } = data;\n const toInsert = { ...rest };\n\n if (_id !== undefined && toInsert.id === undefined) {\n toInsert.id = _id;\n } else if (toInsert.id === undefined) {\n toInsert.id = nanoid(DEFAULT_ID_LENGTH);\n }\n\n const builder = this.getBuilder(object, options);\n const formatted = this.formatInput(object, toInsert);\n\n const result = await builder.insert(formatted).returning('*');\n return this.formatOutput(object, result[0]);\n }\n\n async update(object: string, id: string | number, data: Record<string, any>, options?: DriverOptions): Promise<any> {\n const builder = this.getBuilder(object, options);\n const formatted = this.formatInput(object, data);\n\n if (this.tablesWithTimestamps.has(object)) {\n if (this.isSqlite) {\n const now = new Date();\n formatted.updated_at = now.toISOString().replace('T', ' ').replace('Z', '');\n } else {\n formatted.updated_at = this.knex.fn.now();\n }\n }\n\n await builder.where('id', id).update(formatted);\n\n const updated = await this.getBuilder(object, options).where('id', id).first();\n return this.formatOutput(object, updated) || null;\n }\n\n async delete(object: string, id: string | number, options?: DriverOptions): Promise<any> {\n const builder = this.getBuilder(object, options);\n return await builder.where('id', id).delete();\n }\n\n // ===================================\n // Optional — bulk & batch\n // ===================================\n\n async bulkCreate(object: string, data: any[], options?: DriverOptions): Promise<any> {\n const builder = this.getBuilder(object, options);\n return await builder.insert(data).returning('*');\n }\n\n async updateMany(object: string, query: QueryInput, data: any, options?: DriverOptions): Promise<any> {\n const builder = this.getBuilder(object, options);\n const filters = query.where || (query as any).filters || query;\n if (filters) this.applyFilters(builder, filters);\n const count = await builder.update(data);\n return { modifiedCount: count || 0 };\n }\n\n async deleteMany(object: string, query: QueryInput, options?: DriverOptions): Promise<any> {\n const builder = this.getBuilder(object, options);\n const filters = query.where || (query as any).filters || query;\n if (filters) this.applyFilters(builder, filters);\n const count = await builder.delete();\n return { deletedCount: count || 0 };\n }\n\n async count(object: string, query: QueryInput, options?: DriverOptions): Promise<number> {\n const builder = this.getBuilder(object, options);\n\n let actualFilters = query as any;\n if (query && (query.where || (query as any).filters)) {\n actualFilters = query.where || (query as any).filters;\n }\n\n if (actualFilters) {\n this.applyFilters(builder, actualFilters);\n }\n\n const result = await builder.count<{ count: number }[]>('* as count');\n if (result && result.length > 0) {\n const row: any = result[0];\n return Number(row.count || row['count(*)']);\n }\n return 0;\n }\n\n // ===================================\n // Raw Execution\n // ===================================\n\n async execute(command: any, params?: any[], options?: DriverOptions): Promise<any> {\n if (typeof command !== 'string') {\n return command;\n }\n\n const builder =\n options?.transaction\n ? this.knex.raw(command, params || []).transacting(options.transaction as Knex.Transaction)\n : this.knex.raw(command, params || []);\n\n return await builder;\n }\n\n // ===================================\n // Transactions\n // ===================================\n\n async beginTransaction(): Promise<Knex.Transaction> {\n return await this.knex.transaction();\n }\n\n async commitTransaction(trx: Knex.Transaction): Promise<void> {\n await trx.commit();\n }\n\n async rollbackTransaction(trx: Knex.Transaction): Promise<void> {\n await trx.rollback();\n }\n\n // ===================================\n // Aggregation\n // ===================================\n\n async aggregate(object: string, query: any, options?: DriverOptions): Promise<any> {\n const builder = this.getBuilder(object, options);\n\n if (query.where) {\n this.applyFilters(builder, query.where);\n }\n\n if (query.groupBy) {\n builder.groupBy(query.groupBy);\n for (const field of query.groupBy) {\n builder.select(field);\n }\n }\n\n const aggregates = query.aggregations || query.aggregate;\n if (aggregates) {\n for (const agg of aggregates) {\n const funcName = agg.function || agg.func;\n const rawFunc = this.mapAggregateFunc(funcName);\n if (agg.alias) {\n builder.select(this.knex.raw(`${rawFunc}(??) as ??`, [agg.field, agg.alias]));\n } else {\n builder.select(this.knex.raw(`${rawFunc}(??)`, [agg.field]));\n }\n }\n }\n\n return await builder;\n }\n\n // ===================================\n // Distinct\n // ===================================\n\n async distinct(object: string, field: string, filters?: any, options?: DriverOptions): Promise<any[]> {\n const builder = this.getBuilder(object, options);\n\n if (filters) {\n this.applyFilters(builder, filters);\n }\n\n builder.distinct(field);\n const results = await builder;\n return results.map((row: any) => row[field]);\n }\n\n // ===================================\n // Window Functions\n // ===================================\n\n async findWithWindowFunctions(object: string, query: any, options?: DriverOptions): Promise<any[]> {\n const builder = this.getBuilder(object, options);\n\n builder.select('*');\n\n if (query.where) {\n this.applyFilters(builder, query.where);\n }\n\n if (query.windowFunctions && Array.isArray(query.windowFunctions)) {\n for (const wf of query.windowFunctions) {\n const windowFunc = this.buildWindowFunction(wf);\n builder.select(this.knex.raw(`${windowFunc} as ??`, [wf.alias]));\n }\n }\n\n if (query.orderBy && Array.isArray(query.orderBy)) {\n for (const sort of query.orderBy) {\n builder.orderBy(this.mapSortField(sort.field), sort.order || 'asc');\n }\n }\n\n if (query.limit) builder.limit(query.limit);\n if (query.offset) builder.offset(query.offset);\n\n return await builder;\n }\n\n // ===================================\n // Query Plan Analysis\n // ===================================\n\n async analyzeQuery(object: string, query: any, options?: DriverOptions): Promise<any> {\n const builder = this.getBuilder(object, options);\n\n if (query.fields) {\n builder.select(query.fields);\n } else {\n builder.select('*');\n }\n\n if (query.where) {\n this.applyFilters(builder, query.where);\n }\n\n if (query.orderBy && Array.isArray(query.orderBy)) {\n for (const sort of query.orderBy) {\n builder.orderBy(this.mapSortField(sort.field), sort.order || 'asc');\n }\n }\n\n if (query.limit) builder.limit(query.limit);\n if (query.offset) builder.offset(query.offset);\n\n const sql = builder.toSQL();\n const client = (this.config as any).client;\n let explainResults: any;\n\n try {\n if (this.isPostgres) {\n explainResults = await this.knex.raw(`EXPLAIN (FORMAT JSON, ANALYZE) ${sql.sql}`, sql.bindings);\n } else if (this.isMysql) {\n explainResults = await this.knex.raw(`EXPLAIN FORMAT=JSON ${sql.sql}`, sql.bindings);\n } else if (this.isSqlite) {\n explainResults = await this.knex.raw(`EXPLAIN QUERY PLAN ${sql.sql}`, sql.bindings);\n } else {\n return {\n sql: sql.sql,\n bindings: sql.bindings,\n client,\n note: 'EXPLAIN not supported for this database client',\n };\n }\n\n return { sql: sql.sql, bindings: sql.bindings, client, plan: explainResults };\n } catch (error: any) {\n return {\n sql: sql.sql,\n bindings: sql.bindings,\n client,\n error: error.message,\n note: 'Failed to execute EXPLAIN.',\n };\n }\n }\n\n // ===================================\n // Schema Sync (syncSchema / init)\n // ===================================\n\n async syncSchema(object: string, schema: unknown, _options?: DriverOptions): Promise<void> {\n const objectDef = schema as { name: string; fields?: Record<string, any> };\n await this.initObjects([objectDef]);\n }\n\n /**\n * Batch-initialise tables from an array of object definitions.\n */\n async initObjects(objects: Array<{ name: string; fields?: Record<string, any> }>): Promise<void> {\n await this.ensureDatabaseExists();\n\n for (const obj of objects) {\n const tableName = obj.name;\n\n const jsonCols: string[] = [];\n const booleanCols: string[] = [];\n if (obj.fields) {\n for (const [name, field] of Object.entries<any>(obj.fields)) {\n const type = field.type || 'string';\n if (this.isJsonField(type, field)) {\n jsonCols.push(name);\n }\n if (type === 'boolean') {\n booleanCols.push(name);\n }\n }\n }\n this.jsonFields[tableName] = jsonCols;\n this.booleanFields[tableName] = booleanCols;\n\n let exists = await this.knex.schema.hasTable(tableName);\n\n if (exists) {\n const columnInfo = await this.knex(tableName).columnInfo();\n const existingColumns = Object.keys(columnInfo);\n\n if (existingColumns.includes('_id') && !existingColumns.includes('id')) {\n await this.knex.schema.dropTable(tableName);\n exists = false;\n }\n }\n\n if (!exists) {\n await this.knex.schema.createTable(tableName, (table) => {\n table.string('id').primary();\n table.timestamp('created_at').defaultTo(this.knex.fn.now());\n table.timestamp('updated_at').defaultTo(this.knex.fn.now());\n if (obj.fields) {\n for (const [name, field] of Object.entries(obj.fields)) {\n this.createColumn(table, name, field);\n }\n }\n });\n this.tablesWithTimestamps.add(tableName);\n } else {\n const columnInfo = await this.knex(tableName).columnInfo();\n const existingColumns = Object.keys(columnInfo);\n\n if (existingColumns.includes('updated_at')) {\n this.tablesWithTimestamps.add(tableName);\n }\n\n await this.knex.schema.alterTable(tableName, (table) => {\n if (obj.fields) {\n for (const [name, field] of Object.entries(obj.fields)) {\n if (!existingColumns.includes(name)) {\n this.createColumn(table, name, field);\n }\n }\n }\n });\n }\n }\n }\n\n // ===================================\n // Schema Introspection\n // ===================================\n\n async introspectSchema(): Promise<IntrospectedSchema> {\n const tables: Record<string, IntrospectedTable> = {};\n let tableNames: string[] = [];\n\n if (this.isPostgres) {\n const result = await this.knex.raw(`\n SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = 'public'\n AND table_type = 'BASE TABLE'\n `);\n tableNames = result.rows.map((row: any) => row.table_name);\n } else if (this.isMysql) {\n const result = await this.knex.raw(`\n SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = DATABASE()\n AND table_type = 'BASE TABLE'\n `);\n tableNames = result[0].map((row: any) => row.TABLE_NAME);\n } else if (this.isSqlite) {\n const result = await this.knex.raw(`\n SELECT name as table_name\n FROM sqlite_master\n WHERE type='table'\n AND name NOT LIKE 'sqlite_%'\n `);\n tableNames = result.map((row: any) => row.table_name);\n }\n\n for (const tableName of tableNames) {\n const columns = await this.introspectColumns(tableName);\n const foreignKeys = await this.introspectForeignKeys(tableName);\n const primaryKeys = await this.introspectPrimaryKeys(tableName);\n const uniqueConstraints = await this.introspectUniqueConstraints(tableName);\n\n for (const col of columns) {\n if (primaryKeys.includes(col.name)) col.isPrimary = true;\n if (uniqueConstraints.includes(col.name)) col.isUnique = true;\n }\n\n tables[tableName] = { name: tableName, columns, foreignKeys, primaryKeys };\n }\n\n return { tables };\n }\n\n // ===================================\n // Internal helpers\n // ===================================\n\n /** Expose the underlying Knex instance for advanced usage. */\n getKnex(): Knex {\n return this.knex;\n }\n\n private getBuilder(object: string, options?: DriverOptions) {\n let builder = this.knex(object);\n if (options?.transaction) {\n builder = builder.transacting(options.transaction as Knex.Transaction);\n }\n return builder;\n }\n\n // ── Filter helpers ──────────────────────────────────────────────────────────\n\n private applyFilters(builder: Knex.QueryBuilder, filters: any) {\n if (!filters) return;\n\n if (!Array.isArray(filters) && typeof filters === 'object') {\n const hasMongoOperators = Object.keys(filters).some(\n (k) =>\n k.startsWith('$') ||\n (typeof filters[k] === 'object' &&\n filters[k] !== null &&\n Object.keys(filters[k]).some((op) => op.startsWith('$'))),\n );\n\n if (hasMongoOperators) {\n this.applyFilterCondition(builder, filters);\n return;\n }\n\n for (const [key, value] of Object.entries(filters)) {\n if (['filters', 'sort', 'limit', 'skip', 'offset', 'fields', 'orderBy'].includes(key)) continue;\n builder.where(key, value as any);\n }\n return;\n }\n\n if (!Array.isArray(filters) || filters.length === 0) return;\n\n let nextJoin: 'and' | 'or' = 'and';\n\n for (const item of filters) {\n if (typeof item === 'string') {\n if (item.toLowerCase() === 'or') nextJoin = 'or';\n else if (item.toLowerCase() === 'and') nextJoin = 'and';\n continue;\n }\n\n if (Array.isArray(item)) {\n const [fieldRaw, op, value] = item;\n const isCriterion = typeof fieldRaw === 'string' && typeof op === 'string';\n\n if (isCriterion) {\n const field = this.mapSortField(fieldRaw);\n const apply = (b: any) => {\n const method = nextJoin === 'or' ? 'orWhere' : 'where';\n const methodIn = nextJoin === 'or' ? 'orWhereIn' : 'whereIn';\n const methodNotIn = nextJoin === 'or' ? 'orWhereNotIn' : 'whereNotIn';\n\n if (op === 'contains') {\n b[method](field, 'like', `%${value}%`);\n return;\n }\n\n switch (op) {\n case '=':\n b[method](field, value);\n break;\n case '!=':\n b[method](field, '<>', value);\n break;\n case 'in':\n b[methodIn](field, value);\n break;\n case 'nin':\n b[methodNotIn](field, value);\n break;\n default:\n b[method](field, op, value);\n }\n };\n apply(builder);\n } else {\n const method = nextJoin === 'or' ? 'orWhere' : 'where';\n (builder as any)[method]((qb: any) => {\n this.applyFilters(qb, item);\n });\n }\n\n nextJoin = 'and';\n }\n }\n }\n\n private applyFilterCondition(builder: Knex.QueryBuilder, condition: any, logicalOp: 'and' | 'or' = 'and') {\n if (!condition || typeof condition !== 'object') return;\n\n for (const [key, value] of Object.entries(condition)) {\n if (key === '$and' && Array.isArray(value)) {\n builder.where((qb) => {\n for (const sub of value) {\n qb.where((subQb) => {\n this.applyFilterCondition(subQb, sub, 'and');\n });\n }\n });\n } else if (key === '$or' && Array.isArray(value)) {\n const method = logicalOp === 'or' ? 'orWhere' : 'where';\n (builder as any)[method]((qb: any) => {\n for (const sub of value) {\n qb.orWhere((subQb: any) => {\n this.applyFilterCondition(subQb, sub, 'or');\n });\n }\n });\n } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const field = this.mapSortField(key);\n for (const [op, opValue] of Object.entries(value as Record<string, any>)) {\n const method = logicalOp === 'or' ? 'orWhere' : 'where';\n switch (op) {\n case '$eq':\n (builder as any)[method](field, opValue);\n break;\n case '$ne':\n (builder as any)[method](field, '<>', opValue);\n break;\n case '$gt':\n (builder as any)[method](field, '>', opValue);\n break;\n case '$gte':\n (builder as any)[method](field, '>=', opValue);\n break;\n case '$lt':\n (builder as any)[method](field, '<', opValue);\n break;\n case '$lte':\n (builder as any)[method](field, '<=', opValue);\n break;\n case '$in': {\n const mIn = logicalOp === 'or' ? 'orWhereIn' : 'whereIn';\n (builder as any)[mIn](field, opValue as any[]);\n break;\n }\n case '$nin': {\n const mNotIn = logicalOp === 'or' ? 'orWhereNotIn' : 'whereNotIn';\n (builder as any)[mNotIn](field, opValue as any[]);\n break;\n }\n case '$contains':\n (builder as any)[method](field, 'like', `%${opValue}%`);\n break;\n default:\n (builder as any)[method](field, opValue);\n }\n }\n } else {\n const field = this.mapSortField(key);\n const method = logicalOp === 'or' ? 'orWhere' : 'where';\n (builder as any)[method](field, value as any);\n }\n }\n }\n\n // ── Field mapping ───────────────────────────────────────────────────────────\n\n private mapSortField(field: string): string {\n if (field === 'createdAt') return 'created_at';\n if (field === 'updatedAt') return 'updated_at';\n return field;\n }\n\n private mapAggregateFunc(func: string): string {\n switch (func) {\n case 'count':\n return 'count';\n case 'sum':\n return 'sum';\n case 'avg':\n return 'avg';\n case 'min':\n return 'min';\n case 'max':\n return 'max';\n default:\n throw new Error(`Unsupported aggregate function: ${func}`);\n }\n }\n\n // ── Window function builder ─────────────────────────────────────────────────\n\n private buildWindowFunction(spec: any): string {\n const func = spec.function.toUpperCase();\n let sql = `${func}()`;\n\n const overParts: string[] = [];\n\n if (spec.partitionBy && Array.isArray(spec.partitionBy) && spec.partitionBy.length > 0) {\n const partitionFields = spec.partitionBy.map((f: string) => this.mapSortField(f)).join(', ');\n overParts.push(`PARTITION BY ${partitionFields}`);\n }\n\n if (spec.orderBy && Array.isArray(spec.orderBy) && spec.orderBy.length > 0) {\n const orderFields = spec.orderBy\n .map((s: any) => {\n const field = this.mapSortField(s.field);\n const order = (s.order || 'asc').toUpperCase();\n return `${field} ${order}`;\n })\n .join(', ');\n overParts.push(`ORDER BY ${orderFields}`);\n }\n\n sql += overParts.length > 0 ? ` OVER (${overParts.join(' ')})` : ` OVER ()`;\n return sql;\n }\n\n // ── Column creation helper ──────────────────────────────────────────────────\n\n private createColumn(table: Knex.CreateTableBuilder, name: string, field: any) {\n if (field.multiple) {\n table.json(name);\n return;\n }\n\n const type = field.type || 'string';\n let col: any;\n switch (type) {\n case 'string':\n case 'email':\n case 'url':\n case 'phone':\n case 'password':\n col = table.string(name);\n break;\n case 'text':\n case 'textarea':\n case 'html':\n case 'markdown':\n col = table.text(name);\n break;\n case 'integer':\n case 'int':\n col = table.integer(name);\n break;\n case 'float':\n case 'number':\n case 'currency':\n case 'percent':\n col = table.float(name);\n break;\n case 'boolean':\n col = table.boolean(name);\n break;\n case 'date':\n col = table.date(name);\n break;\n case 'datetime':\n col = table.timestamp(name);\n break;\n case 'time':\n col = table.time(name);\n break;\n case 'json':\n case 'object':\n case 'array':\n case 'image':\n case 'file':\n case 'avatar':\n case 'location':\n col = table.json(name);\n break;\n case 'lookup':\n col = table.string(name);\n if (field.reference_to) {\n table.foreign(name).references('id').inTable(field.reference_to);\n }\n break;\n case 'summary':\n col = table.float(name);\n break;\n case 'auto_number':\n col = table.string(name);\n break;\n case 'formula':\n return; // Virtual — no column\n default:\n col = table.string(name);\n }\n\n if (col) {\n if (field.unique) col.unique();\n if (field.required) col.notNullable();\n }\n }\n\n // ── Database helpers ────────────────────────────────────────────────────────\n\n private async ensureDatabaseExists() {\n if (!this.isPostgres) return;\n\n try {\n await this.knex.raw('SELECT 1');\n } catch (e: any) {\n if (e.code === '3D000') {\n await this.createDatabase();\n } else {\n throw e;\n }\n }\n }\n\n private async createDatabase() {\n const config = this.config as any;\n const connection = config.connection;\n let dbName = '';\n const adminConfig = { ...config };\n\n if (typeof connection === 'string') {\n const url = new URL(connection);\n dbName = url.pathname.slice(1);\n url.pathname = '/postgres';\n adminConfig.connection = url.toString();\n } else {\n dbName = connection.database;\n adminConfig.connection = { ...connection, database: 'postgres' };\n }\n\n const adminKnex = knex(adminConfig);\n try {\n await adminKnex.raw(`CREATE DATABASE \"${dbName}\"`);\n } finally {\n await adminKnex.destroy();\n }\n }\n\n private isJsonField(type: string, field: any): boolean {\n return ['json', 'object', 'array', 'image', 'file', 'avatar', 'location'].includes(type) || field.multiple;\n }\n\n // ── SQLite serialisation ────────────────────────────────────────────────────\n\n private formatInput(object: string, data: any): any {\n if (!this.isSqlite) return data;\n\n const fields = this.jsonFields[object];\n if (!fields || fields.length === 0) return data;\n\n const copy = { ...data };\n for (const field of fields) {\n if (copy[field] !== undefined && typeof copy[field] === 'object' && copy[field] !== null) {\n copy[field] = JSON.stringify(copy[field]);\n }\n }\n return copy;\n }\n\n private formatOutput(object: string, data: any): any {\n if (!data) return data;\n\n if (this.isSqlite) {\n const jsonFields = this.jsonFields[object];\n if (jsonFields && jsonFields.length > 0) {\n for (const field of jsonFields) {\n if (data[field] !== undefined && typeof data[field] === 'string') {\n try {\n data[field] = JSON.parse(data[field]);\n } catch {\n // keep as string\n }\n }\n }\n }\n\n const booleanFields = this.booleanFields[object];\n if (booleanFields && booleanFields.length > 0) {\n for (const field of booleanFields) {\n if (data[field] !== undefined && data[field] !== null) {\n data[field] = Boolean(data[field]);\n }\n }\n }\n }\n\n return data;\n }\n\n // ── Introspection internals ─────────────────────────────────────────────────\n\n private async introspectColumns(tableName: string): Promise<IntrospectedColumn[]> {\n const columnInfo = await this.knex(tableName).columnInfo();\n const columns: IntrospectedColumn[] = [];\n\n for (const [colName, info] of Object.entries<any>(columnInfo)) {\n let type = 'string';\n let maxLength: number | undefined;\n\n if (this.isSqlite) {\n type = info.type?.toLowerCase() || 'string';\n } else {\n type = info.type || 'string';\n }\n\n if (info.maxLength) {\n maxLength = info.maxLength;\n }\n\n columns.push({\n name: colName,\n type,\n nullable: info.nullable !== false,\n defaultValue: info.defaultValue,\n isPrimary: false,\n isUnique: false,\n maxLength,\n });\n }\n\n return columns;\n }\n\n private async introspectForeignKeys(tableName: string): Promise<IntrospectedForeignKey[]> {\n const foreignKeys: IntrospectedForeignKey[] = [];\n\n try {\n if (this.isPostgres) {\n const result = await this.knex.raw(\n `\n SELECT\n kcu.column_name,\n ccu.table_name AS referenced_table,\n ccu.column_name AS referenced_column,\n tc.constraint_name\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n WHERE tc.constraint_type = 'FOREIGN KEY'\n AND tc.table_name = ?\n `,\n [tableName],\n );\n\n for (const row of result.rows) {\n foreignKeys.push({\n columnName: row.column_name,\n referencedTable: row.referenced_table,\n referencedColumn: row.referenced_column,\n constraintName: row.constraint_name,\n });\n }\n } else if (this.isMysql) {\n const result = await this.knex.raw(\n `\n SELECT\n COLUMN_NAME as column_name,\n REFERENCED_TABLE_NAME as referenced_table,\n REFERENCED_COLUMN_NAME as referenced_column,\n CONSTRAINT_NAME as constraint_name\n FROM information_schema.KEY_COLUMN_USAGE\n WHERE TABLE_SCHEMA = DATABASE()\n AND TABLE_NAME = ?\n AND REFERENCED_TABLE_NAME IS NOT NULL\n `,\n [tableName],\n );\n\n for (const row of result[0]) {\n foreignKeys.push({\n columnName: row.column_name,\n referencedTable: row.referenced_table,\n referencedColumn: row.referenced_column,\n constraintName: row.constraint_name,\n });\n }\n } else if (this.isSqlite) {\n const tableExistsResult = await this.knex.raw(\n \"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?\",\n [tableName],\n );\n\n if (!Array.isArray(tableExistsResult) || tableExistsResult.length === 0) {\n return foreignKeys;\n }\n\n const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, '');\n const result = await this.knex.raw(`PRAGMA foreign_key_list(${safeTableName})`);\n\n for (const row of result) {\n foreignKeys.push({\n columnName: row.from,\n referencedTable: row.table,\n referencedColumn: row.to,\n constraintName: `fk_${tableName}_${row.from}`,\n });\n }\n }\n } catch {\n // silently ignore introspection errors\n }\n\n return foreignKeys;\n }\n\n private async introspectPrimaryKeys(tableName: string): Promise<string[]> {\n const primaryKeys: string[] = [];\n\n try {\n if (this.isPostgres) {\n const result = await this.knex.raw(\n `\n SELECT a.attname as column_name\n FROM pg_index i\n JOIN pg_attribute a ON a.attrelid = i.indrelid\n AND a.attnum = ANY(i.indkey)\n WHERE i.indrelid = ?::regclass\n AND i.indisprimary\n `,\n [tableName],\n );\n\n for (const row of result.rows) {\n primaryKeys.push(row.column_name);\n }\n } else if (this.isMysql) {\n const result = await this.knex.raw(\n `\n SELECT COLUMN_NAME as column_name\n FROM information_schema.KEY_COLUMN_USAGE\n WHERE TABLE_SCHEMA = DATABASE()\n AND TABLE_NAME = ?\n AND CONSTRAINT_NAME = 'PRIMARY'\n `,\n [tableName],\n );\n\n for (const row of result[0]) {\n primaryKeys.push(row.column_name);\n }\n } else if (this.isSqlite) {\n const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, '');\n\n const tablesResult = await this.knex.raw(\"SELECT name FROM sqlite_master WHERE type = 'table'\");\n const tableNames = Array.isArray(tablesResult) ? tablesResult.map((row: any) => row.name) : [];\n\n if (!tableNames.includes(safeTableName)) {\n return primaryKeys;\n }\n\n const result = await this.knex.raw(`PRAGMA table_info(${safeTableName})`);\n\n for (const row of result) {\n if (row.pk === 1) {\n primaryKeys.push(row.name);\n }\n }\n }\n } catch {\n // silently ignore\n }\n\n return primaryKeys;\n }\n\n private async introspectUniqueConstraints(tableName: string): Promise<string[]> {\n const uniqueColumns: string[] = [];\n\n try {\n if (this.isPostgres) {\n const result = await this.knex.raw(\n `\n SELECT c.column_name\n FROM information_schema.table_constraints tc\n JOIN information_schema.constraint_column_usage AS ccu\n ON tc.constraint_schema = ccu.constraint_schema\n AND tc.constraint_name = ccu.constraint_name\n WHERE tc.constraint_type = 'UNIQUE'\n AND tc.table_name = ?\n `,\n [tableName],\n );\n\n for (const row of result.rows) {\n uniqueColumns.push(row.column_name);\n }\n } else if (this.isMysql) {\n const result = await this.knex.raw(\n `\n SELECT COLUMN_NAME\n FROM information_schema.TABLE_CONSTRAINTS tc\n JOIN information_schema.KEY_COLUMN_USAGE kcu\n USING (CONSTRAINT_NAME, TABLE_SCHEMA, TABLE_NAME)\n WHERE CONSTRAINT_TYPE = 'UNIQUE'\n AND TABLE_SCHEMA = DATABASE()\n AND TABLE_NAME = ?\n `,\n [tableName],\n );\n\n for (const row of result[0]) {\n uniqueColumns.push(row.COLUMN_NAME);\n }\n } else if (this.isSqlite) {\n const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, '');\n\n const tablesResult = await this.knex.raw(\"SELECT name FROM sqlite_master WHERE type = 'table'\");\n const tableNames = Array.isArray(tablesResult) ? tablesResult.map((row: any) => row.name) : [];\n\n if (!tableNames.includes(safeTableName)) {\n return uniqueColumns;\n }\n\n const indexes = await this.knex.raw(`PRAGMA index_list(${safeTableName})`);\n\n for (const idx of indexes) {\n if (idx.unique === 1) {\n const info = await this.knex.raw(`PRAGMA index_info(${idx.name})`);\n if (info.length === 1) {\n uniqueColumns.push(info[0].name);\n }\n }\n }\n }\n } catch {\n // silently ignore\n }\n\n return uniqueColumns;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { SqlDriver } from './sql-driver.js';\n\nexport { SqlDriver };\nexport type {\n SqlDriverConfig,\n IntrospectedSchema,\n IntrospectedTable,\n IntrospectedColumn,\n IntrospectedForeignKey,\n} from './sql-driver.js';\n\nexport default {\n id: 'com.objectstack.driver.sql',\n version: '1.0.0',\n\n onEnable: async (context: any) => {\n const { logger, config, drivers } = context;\n logger.info('[SQL Driver] Initializing...');\n\n if (drivers) {\n const driver = new SqlDriver(config);\n drivers.register(driver);\n logger.info(`[SQL Driver] Registered driver: ${driver.name}`);\n } else {\n logger.warn('[SQL Driver] No driver registry found in context.');\n }\n },\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/sql-driver.ts","../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * SQL Driver for ObjectStack\n *\n * Implements the standard IDataDriver from @objectstack/spec via Knex.js.\n * Supports PostgreSQL, MySQL, SQLite, and other SQL databases.\n */\n\nimport type { QueryAST, DriverOptions } from '@objectstack/spec/data';\nimport type { IDataDriver } from '@objectstack/spec/contracts';\nimport knex, { Knex } from 'knex';\nimport { nanoid } from 'nanoid';\n\n/**\n * Default ID length for auto-generated IDs.\n */\nconst DEFAULT_ID_LENGTH = 16;\n\n// ── Introspection Types ──────────────────────────────────────────────────────\n\nexport interface IntrospectedColumn {\n name: string;\n type: string;\n nullable: boolean;\n defaultValue?: unknown;\n isPrimary?: boolean;\n isUnique?: boolean;\n maxLength?: number;\n}\n\nexport interface IntrospectedForeignKey {\n columnName: string;\n referencedTable: string;\n referencedColumn: string;\n constraintName?: string;\n}\n\nexport interface IntrospectedTable {\n name: string;\n columns: IntrospectedColumn[];\n foreignKeys: IntrospectedForeignKey[];\n primaryKeys: string[];\n}\n\nexport interface IntrospectedSchema {\n tables: Record<string, IntrospectedTable>;\n}\n\n// ── Configuration Types ──────────────────────────────────────────────────────\n\n/**\n * SqlDriver configuration — passed directly to Knex.\n * See https://knexjs.org/guide/#configuration-options\n */\nexport type SqlDriverConfig = Knex.Config;\n\n// ── SQL Driver ───────────────────────────────────────────────────────────────\n\n/**\n * SQL Driver for ObjectStack.\n *\n * Implements the IDataDriver contract via Knex.js for optimal SQL\n * generation against PostgreSQL, MySQL, SQLite and other SQL databases.\n */\nexport class SqlDriver implements IDataDriver {\n // IDataDriver metadata\n public readonly name: string = 'com.objectstack.driver.sql';\n public readonly version: string = '1.0.0';\n public readonly supports = {\n // Basic CRUD Operations\n create: true,\n read: true,\n update: true,\n delete: true,\n\n // Bulk Operations\n bulkCreate: true,\n bulkUpdate: true,\n bulkDelete: true,\n\n // Transaction & Connection Management\n transactions: true,\n savepoints: false,\n\n // Query Operations\n queryFilters: true,\n queryAggregations: true,\n querySorting: true,\n queryPagination: true,\n queryWindowFunctions: true,\n querySubqueries: true,\n queryCTE: false,\n joins: true,\n\n // Advanced Features\n fullTextSearch: false,\n jsonQuery: false,\n geospatialQuery: false,\n streaming: false,\n jsonFields: true,\n arrayFields: true,\n vectorSearch: false,\n\n // Schema Management\n schemaSync: true,\n batchSchemaSync: false,\n migrations: false,\n indexes: false,\n\n // Performance & Optimization\n connectionPooling: true,\n preparedStatements: true,\n queryCache: false,\n };\n\n protected knex: Knex;\n protected config: Knex.Config;\n protected jsonFields: Record<string, string[]> = {};\n protected booleanFields: Record<string, string[]> = {};\n protected tablesWithTimestamps: Set<string> = new Set();\n\n /** Whether the underlying database is a SQLite variant (sqlite3 or better-sqlite3). */\n protected get isSqlite(): boolean {\n const c = (this.config as any).client;\n return c === 'sqlite3' || c === 'better-sqlite3';\n }\n\n /** Whether the underlying database is PostgreSQL. */\n protected get isPostgres(): boolean {\n const c = (this.config as any).client;\n return c === 'pg' || c === 'postgresql';\n }\n\n /** Whether the underlying database is MySQL. */\n protected get isMysql(): boolean {\n const c = (this.config as any).client;\n return c === 'mysql' || c === 'mysql2';\n }\n\n constructor(config: SqlDriverConfig) {\n this.config = config;\n this.knex = knex(config);\n }\n\n // ===================================\n // Lifecycle\n // ===================================\n\n async connect(): Promise<void> {\n return Promise.resolve();\n }\n\n async checkHealth(): Promise<boolean> {\n try {\n await this.knex.raw('SELECT 1');\n return true;\n } catch {\n return false;\n }\n }\n\n async disconnect(): Promise<void> {\n await this.knex.destroy();\n }\n\n // ===================================\n // CRUD — DriverInterface core\n // ===================================\n\n async find(object: string, query: QueryAST, options?: DriverOptions): Promise<any[]> {\n const builder = this.getBuilder(object, options);\n\n // SELECT\n if (query.fields) {\n builder.select((query.fields as string[]).map((f: string) => this.mapSortField(f)));\n } else {\n builder.select('*');\n }\n\n // WHERE\n if (query.where) {\n this.applyFilters(builder, query.where);\n }\n\n // ORDER BY\n if (query.orderBy && Array.isArray(query.orderBy)) {\n for (const item of query.orderBy) {\n if (item.field) {\n builder.orderBy(this.mapSortField(item.field), item.order || 'asc');\n }\n }\n }\n\n // PAGINATION\n if (query.offset !== undefined) builder.offset(query.offset);\n if (query.limit !== undefined) builder.limit(query.limit);\n\n let results: any[];\n try {\n results = await builder;\n } catch (error: any) {\n if (\n error.message &&\n (error.message.includes('no such column') ||\n (error.message.includes('column') && error.message.includes('does not exist')))\n ) {\n return [];\n }\n throw error;\n }\n\n if (!Array.isArray(results)) {\n return [];\n }\n\n if (this.isSqlite) {\n for (const row of results) {\n this.formatOutput(object, row);\n }\n }\n return results;\n }\n\n async findOne(object: string, query: QueryAST, options?: DriverOptions): Promise<any> {\n // When called with a string/number id fall back gracefully\n if (typeof query === 'string' || typeof query === 'number') {\n const res = await this.getBuilder(object, options).where('id', query).first();\n return this.formatOutput(object, res) || null;\n }\n\n if (query && typeof query === 'object') {\n const results = await this.find(object, { ...query, limit: 1 }, options);\n return results[0] || null;\n }\n\n return null;\n }\n\n /**\n * Stream records matching a structured query.\n * NOTE: Current implementation fetches all results then yields them.\n * TODO: Use Knex .stream() for true cursor-based streaming on large datasets.\n */\n async *findStream(object: string, query: QueryAST, options?: DriverOptions): AsyncGenerator<Record<string, any>> {\n const results = await this.find(object, query, options);\n for (const row of results) {\n yield row;\n }\n }\n\n async create(object: string, data: Record<string, any>, options?: DriverOptions): Promise<any> {\n const { _id, ...rest } = data;\n const toInsert = { ...rest };\n\n if (_id !== undefined && toInsert.id === undefined) {\n toInsert.id = _id;\n } else if (toInsert.id === undefined) {\n toInsert.id = nanoid(DEFAULT_ID_LENGTH);\n }\n\n const builder = this.getBuilder(object, options);\n const formatted = this.formatInput(object, toInsert);\n\n const result = await builder.insert(formatted).returning('*');\n return this.formatOutput(object, result[0]);\n }\n\n async update(object: string, id: string | number, data: Record<string, any>, options?: DriverOptions): Promise<any> {\n const builder = this.getBuilder(object, options);\n const formatted = this.formatInput(object, data);\n\n if (this.tablesWithTimestamps.has(object)) {\n if (this.isSqlite) {\n const now = new Date();\n formatted.updated_at = now.toISOString().replace('T', ' ').replace('Z', '');\n } else {\n formatted.updated_at = this.knex.fn.now();\n }\n }\n\n await builder.where('id', id).update(formatted);\n\n const updated = await this.getBuilder(object, options).where('id', id).first();\n return this.formatOutput(object, updated) || null;\n }\n\n async upsert(object: string, data: Record<string, any>, conflictKeys?: string[], options?: DriverOptions): Promise<Record<string, any>> {\n const { _id, ...rest } = data;\n const toUpsert = { ...rest };\n\n if (_id !== undefined && toUpsert.id === undefined) {\n toUpsert.id = _id;\n } else if (toUpsert.id === undefined) {\n toUpsert.id = nanoid(DEFAULT_ID_LENGTH);\n }\n\n const formatted = this.formatInput(object, toUpsert);\n const mergeKeys = conflictKeys && conflictKeys.length > 0 ? conflictKeys : ['id'];\n\n const builder = this.getBuilder(object, options);\n await builder.insert(formatted).onConflict(mergeKeys).merge();\n\n const result = await this.getBuilder(object, options).where('id', toUpsert.id).first();\n return this.formatOutput(object, result) || toUpsert;\n }\n\n async delete(object: string, id: string | number, options?: DriverOptions): Promise<boolean> {\n const builder = this.getBuilder(object, options);\n const count = await builder.where('id', id).delete();\n return count > 0;\n }\n\n // ===================================\n // Bulk & Batch Operations\n // ===================================\n\n async bulkCreate(object: string, data: any[], options?: DriverOptions): Promise<any> {\n const builder = this.getBuilder(object, options);\n return await builder.insert(data).returning('*');\n }\n\n /**\n * Batch-update multiple records by ID.\n * NOTE: Current implementation performs sequential updates for correctness.\n * TODO: Optimize with SQL CASE statements or batched transactions for performance.\n */\n async bulkUpdate(object: string, updates: Array<{ id: string | number; data: Record<string, any> }>, options?: DriverOptions): Promise<Record<string, any>[]> {\n const results: Record<string, any>[] = [];\n for (const { id, data } of updates) {\n const updated = await this.update(object, id, data, options);\n if (updated) results.push(updated);\n }\n return results;\n }\n\n async bulkDelete(object: string, ids: Array<string | number>, options?: DriverOptions): Promise<void> {\n const builder = this.getBuilder(object, options);\n await builder.whereIn('id', ids).delete();\n }\n\n async updateMany(object: string, query: QueryAST, data: any, options?: DriverOptions): Promise<number> {\n const builder = this.getBuilder(object, options);\n if (query.where) this.applyFilters(builder, query.where);\n const count = await builder.update(data);\n return count || 0;\n }\n\n async deleteMany(object: string, query: QueryAST, options?: DriverOptions): Promise<number> {\n const builder = this.getBuilder(object, options);\n if (query.where) this.applyFilters(builder, query.where);\n const count = await builder.delete();\n return count || 0;\n }\n\n async count(object: string, query?: QueryAST, options?: DriverOptions): Promise<number> {\n const builder = this.getBuilder(object, options);\n\n if (query?.where) {\n this.applyFilters(builder, query.where);\n }\n\n const result = await builder.count<{ count: number }[]>('* as count');\n if (result && result.length > 0) {\n const row: any = result[0];\n return Number(row.count ?? row['count(*)'] ?? 0);\n }\n return 0;\n }\n\n // ===================================\n // Raw Execution\n // ===================================\n\n async execute(command: any, params?: any[], options?: DriverOptions): Promise<any> {\n if (typeof command !== 'string') {\n return command;\n }\n\n const builder =\n options?.transaction\n ? this.knex.raw(command, params || []).transacting(options.transaction as Knex.Transaction)\n : this.knex.raw(command, params || []);\n\n return await builder;\n }\n\n // ===================================\n // Transactions\n // ===================================\n\n async beginTransaction(): Promise<Knex.Transaction> {\n return await this.knex.transaction();\n }\n\n /** IDataDriver standard */\n async commit(transaction: unknown): Promise<void> {\n await (transaction as Knex.Transaction).commit();\n }\n\n /** IDataDriver standard */\n async rollback(transaction: unknown): Promise<void> {\n await (transaction as Knex.Transaction).rollback();\n }\n\n /** @deprecated Use commit() instead */\n async commitTransaction(trx: Knex.Transaction): Promise<void> {\n await this.commit(trx);\n }\n\n /** @deprecated Use rollback() instead */\n async rollbackTransaction(trx: Knex.Transaction): Promise<void> {\n await this.rollback(trx);\n }\n\n // ===================================\n // Aggregation\n // ===================================\n\n async aggregate(object: string, query: any, options?: DriverOptions): Promise<any> {\n const builder = this.getBuilder(object, options);\n\n if (query.where) {\n this.applyFilters(builder, query.where);\n }\n\n if (query.groupBy) {\n builder.groupBy(query.groupBy);\n for (const field of query.groupBy) {\n builder.select(field);\n }\n }\n\n const aggregates = query.aggregations || query.aggregate;\n if (aggregates) {\n for (const agg of aggregates) {\n const funcName = agg.function || agg.func;\n const rawFunc = this.mapAggregateFunc(funcName);\n if (agg.alias) {\n builder.select(this.knex.raw(`${rawFunc}(??) as ??`, [agg.field, agg.alias]));\n } else {\n builder.select(this.knex.raw(`${rawFunc}(??)`, [agg.field]));\n }\n }\n }\n\n return await builder;\n }\n\n // ===================================\n // Distinct\n // ===================================\n\n async distinct(object: string, field: string, filters?: any, options?: DriverOptions): Promise<any[]> {\n const builder = this.getBuilder(object, options);\n\n if (filters) {\n this.applyFilters(builder, filters);\n }\n\n builder.distinct(field);\n const results = await builder;\n return results.map((row: any) => row[field]);\n }\n\n // ===================================\n // Window Functions\n // ===================================\n\n async findWithWindowFunctions(object: string, query: any, options?: DriverOptions): Promise<any[]> {\n const builder = this.getBuilder(object, options);\n\n builder.select('*');\n\n if (query.where) {\n this.applyFilters(builder, query.where);\n }\n\n if (query.windowFunctions && Array.isArray(query.windowFunctions)) {\n for (const wf of query.windowFunctions) {\n const windowFunc = this.buildWindowFunction(wf);\n builder.select(this.knex.raw(`${windowFunc} as ??`, [wf.alias]));\n }\n }\n\n if (query.orderBy && Array.isArray(query.orderBy)) {\n for (const sort of query.orderBy) {\n builder.orderBy(this.mapSortField(sort.field), sort.order || 'asc');\n }\n }\n\n if (query.limit) builder.limit(query.limit);\n if (query.offset) builder.offset(query.offset);\n\n return await builder;\n }\n\n // ===================================\n // Query Plan Analysis\n // ===================================\n\n /** IDataDriver standard: analyze query performance */\n async explain(object: string, query: any, options?: DriverOptions): Promise<any> {\n return this.analyzeQuery(object, query, options);\n }\n\n async analyzeQuery(object: string, query: any, options?: DriverOptions): Promise<any> {\n const builder = this.getBuilder(object, options);\n\n if (query.fields) {\n builder.select(query.fields);\n } else {\n builder.select('*');\n }\n\n if (query.where) {\n this.applyFilters(builder, query.where);\n }\n\n if (query.orderBy && Array.isArray(query.orderBy)) {\n for (const sort of query.orderBy) {\n builder.orderBy(this.mapSortField(sort.field), sort.order || 'asc');\n }\n }\n\n if (query.limit) builder.limit(query.limit);\n if (query.offset) builder.offset(query.offset);\n\n const sql = builder.toSQL();\n const client = (this.config as any).client;\n let explainResults: any;\n\n try {\n if (this.isPostgres) {\n explainResults = await this.knex.raw(`EXPLAIN (FORMAT JSON, ANALYZE) ${sql.sql}`, sql.bindings);\n } else if (this.isMysql) {\n explainResults = await this.knex.raw(`EXPLAIN FORMAT=JSON ${sql.sql}`, sql.bindings);\n } else if (this.isSqlite) {\n explainResults = await this.knex.raw(`EXPLAIN QUERY PLAN ${sql.sql}`, sql.bindings);\n } else {\n return {\n sql: sql.sql,\n bindings: sql.bindings,\n client,\n note: 'EXPLAIN not supported for this database client',\n };\n }\n\n return { sql: sql.sql, bindings: sql.bindings, client, plan: explainResults };\n } catch (error: any) {\n return {\n sql: sql.sql,\n bindings: sql.bindings,\n client,\n error: error.message,\n note: 'Failed to execute EXPLAIN.',\n };\n }\n }\n\n // ===================================\n // Schema Sync (syncSchema / init)\n // ===================================\n\n async syncSchema(object: string, schema: unknown, _options?: DriverOptions): Promise<void> {\n const objectDef = schema as { name: string; fields?: Record<string, any> };\n // Use the physical table name (`object`) for DDL operations. The caller\n // (e.g. syncRegisteredSchemas) passes the resolved tableName (e.g. 'sys_user')\n // while objectDef.name may contain the FQN (e.g. 'sys__user').\n await this.initObjects([{ ...objectDef, name: object }]);\n }\n\n async dropTable(object: string, _options?: DriverOptions): Promise<void> {\n await this.knex.schema.dropTableIfExists(object);\n }\n\n /**\n * Batch-initialise tables from an array of object definitions.\n */\n async initObjects(objects: Array<{ name: string; tableName?: string; fields?: Record<string, any> }>): Promise<void> {\n await this.ensureDatabaseExists();\n\n for (const obj of objects) {\n // Prefer explicit tableName (physical name) over obj.name (which may be a FQN).\n const tableName = obj.tableName || obj.name;\n\n const jsonCols: string[] = [];\n const booleanCols: string[] = [];\n if (obj.fields) {\n for (const [name, field] of Object.entries<any>(obj.fields)) {\n const type = field.type || 'string';\n if (this.isJsonField(type, field)) {\n jsonCols.push(name);\n }\n if (type === 'boolean') {\n booleanCols.push(name);\n }\n }\n }\n this.jsonFields[tableName] = jsonCols;\n this.booleanFields[tableName] = booleanCols;\n\n let exists = await this.knex.schema.hasTable(tableName);\n\n if (exists) {\n const columnInfo = await this.knex(tableName).columnInfo();\n const existingColumns = Object.keys(columnInfo);\n\n if (existingColumns.includes('_id') && !existingColumns.includes('id')) {\n await this.knex.schema.dropTable(tableName);\n exists = false;\n }\n }\n\n // Columns created unconditionally by initObjects — skip them when\n // iterating obj.fields to avoid duplicate-column errors (e.g. SQLite\n // rejects CREATE TABLE with two columns of the same name).\n const builtinColumns = new Set(['id', 'created_at', 'updated_at']);\n\n if (!exists) {\n await this.knex.schema.createTable(tableName, (table) => {\n table.string('id').primary();\n table.timestamp('created_at').defaultTo(this.knex.fn.now());\n table.timestamp('updated_at').defaultTo(this.knex.fn.now());\n if (obj.fields) {\n for (const [name, field] of Object.entries(obj.fields)) {\n if (builtinColumns.has(name)) continue;\n this.createColumn(table, name, field);\n }\n }\n });\n this.tablesWithTimestamps.add(tableName);\n } else {\n const columnInfo = await this.knex(tableName).columnInfo();\n const existingColumns = Object.keys(columnInfo);\n\n if (existingColumns.includes('updated_at')) {\n this.tablesWithTimestamps.add(tableName);\n }\n\n await this.knex.schema.alterTable(tableName, (table) => {\n if (obj.fields) {\n for (const [name, field] of Object.entries(obj.fields)) {\n if (!existingColumns.includes(name)) {\n this.createColumn(table, name, field);\n }\n }\n }\n });\n }\n }\n }\n\n // ===================================\n // Schema Introspection\n // ===================================\n\n async introspectSchema(): Promise<IntrospectedSchema> {\n const tables: Record<string, IntrospectedTable> = {};\n let tableNames: string[] = [];\n\n if (this.isPostgres) {\n const result = await this.knex.raw(`\n SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = 'public'\n AND table_type = 'BASE TABLE'\n `);\n tableNames = result.rows.map((row: any) => row.table_name);\n } else if (this.isMysql) {\n const result = await this.knex.raw(`\n SELECT table_name\n FROM information_schema.tables\n WHERE table_schema = DATABASE()\n AND table_type = 'BASE TABLE'\n `);\n tableNames = result[0].map((row: any) => row.TABLE_NAME);\n } else if (this.isSqlite) {\n const result = await this.knex.raw(`\n SELECT name as table_name\n FROM sqlite_master\n WHERE type='table'\n AND name NOT LIKE 'sqlite_%'\n `);\n tableNames = result.map((row: any) => row.table_name);\n }\n\n for (const tableName of tableNames) {\n const columns = await this.introspectColumns(tableName);\n const foreignKeys = await this.introspectForeignKeys(tableName);\n const primaryKeys = await this.introspectPrimaryKeys(tableName);\n const uniqueConstraints = await this.introspectUniqueConstraints(tableName);\n\n for (const col of columns) {\n if (primaryKeys.includes(col.name)) col.isPrimary = true;\n if (uniqueConstraints.includes(col.name)) col.isUnique = true;\n }\n\n tables[tableName] = { name: tableName, columns, foreignKeys, primaryKeys };\n }\n\n return { tables };\n }\n\n // ===================================\n // Internal helpers\n // ===================================\n\n /** Expose the underlying Knex instance for advanced usage. */\n getKnex(): Knex {\n return this.knex;\n }\n\n protected getBuilder(object: string, options?: DriverOptions) {\n let builder = this.knex(object);\n if (options?.transaction) {\n builder = builder.transacting(options.transaction as Knex.Transaction);\n }\n return builder;\n }\n\n // ── Filter helpers ──────────────────────────────────────────────────────────\n\n protected applyFilters(builder: Knex.QueryBuilder, filters: any) {\n if (!filters) return;\n\n if (!Array.isArray(filters) && typeof filters === 'object') {\n const hasMongoOperators = Object.keys(filters).some(\n (k) =>\n k.startsWith('$') ||\n (typeof filters[k] === 'object' &&\n filters[k] !== null &&\n Object.keys(filters[k]).some((op) => op.startsWith('$'))),\n );\n\n if (hasMongoOperators) {\n this.applyFilterCondition(builder, filters);\n return;\n }\n\n for (const [key, value] of Object.entries(filters)) {\n if (['limit', 'offset', 'fields', 'orderBy'].includes(key)) continue;\n builder.where(key, value as any);\n }\n return;\n }\n\n if (!Array.isArray(filters) || filters.length === 0) return;\n\n let nextJoin: 'and' | 'or' = 'and';\n\n for (const item of filters) {\n if (typeof item === 'string') {\n if (item.toLowerCase() === 'or') nextJoin = 'or';\n else if (item.toLowerCase() === 'and') nextJoin = 'and';\n continue;\n }\n\n if (Array.isArray(item)) {\n const [fieldRaw, op, value] = item;\n const isCriterion = typeof fieldRaw === 'string' && typeof op === 'string';\n\n if (isCriterion) {\n const field = this.mapSortField(fieldRaw);\n const apply = (b: any) => {\n const method = nextJoin === 'or' ? 'orWhere' : 'where';\n const methodIn = nextJoin === 'or' ? 'orWhereIn' : 'whereIn';\n const methodNotIn = nextJoin === 'or' ? 'orWhereNotIn' : 'whereNotIn';\n\n if (op === 'contains') {\n b[method](field, 'like', `%${value}%`);\n return;\n }\n\n switch (op) {\n case '=':\n b[method](field, value);\n break;\n case '!=':\n b[method](field, '<>', value);\n break;\n case 'in':\n b[methodIn](field, value);\n break;\n case 'nin':\n b[methodNotIn](field, value);\n break;\n default:\n b[method](field, op, value);\n }\n };\n apply(builder);\n } else {\n const method = nextJoin === 'or' ? 'orWhere' : 'where';\n (builder as any)[method]((qb: any) => {\n this.applyFilters(qb, item);\n });\n }\n\n nextJoin = 'and';\n }\n }\n }\n\n protected applyFilterCondition(builder: Knex.QueryBuilder, condition: any, logicalOp: 'and' | 'or' = 'and') {\n if (!condition || typeof condition !== 'object') return;\n\n for (const [key, value] of Object.entries(condition)) {\n if (key === '$and' && Array.isArray(value)) {\n builder.where((qb) => {\n for (const sub of value) {\n qb.where((subQb) => {\n this.applyFilterCondition(subQb, sub, 'and');\n });\n }\n });\n } else if (key === '$or' && Array.isArray(value)) {\n const method = logicalOp === 'or' ? 'orWhere' : 'where';\n (builder as any)[method]((qb: any) => {\n for (const sub of value) {\n qb.orWhere((subQb: any) => {\n this.applyFilterCondition(subQb, sub, 'or');\n });\n }\n });\n } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const field = this.mapSortField(key);\n for (const [op, opValue] of Object.entries(value as Record<string, any>)) {\n const method = logicalOp === 'or' ? 'orWhere' : 'where';\n switch (op) {\n case '$eq':\n (builder as any)[method](field, opValue);\n break;\n case '$ne':\n (builder as any)[method](field, '<>', opValue);\n break;\n case '$gt':\n (builder as any)[method](field, '>', opValue);\n break;\n case '$gte':\n (builder as any)[method](field, '>=', opValue);\n break;\n case '$lt':\n (builder as any)[method](field, '<', opValue);\n break;\n case '$lte':\n (builder as any)[method](field, '<=', opValue);\n break;\n case '$in': {\n const mIn = logicalOp === 'or' ? 'orWhereIn' : 'whereIn';\n (builder as any)[mIn](field, opValue as any[]);\n break;\n }\n case '$nin': {\n const mNotIn = logicalOp === 'or' ? 'orWhereNotIn' : 'whereNotIn';\n (builder as any)[mNotIn](field, opValue as any[]);\n break;\n }\n case '$contains':\n (builder as any)[method](field, 'like', `%${opValue}%`);\n break;\n default:\n (builder as any)[method](field, opValue);\n }\n }\n } else {\n const field = this.mapSortField(key);\n const method = logicalOp === 'or' ? 'orWhere' : 'where';\n (builder as any)[method](field, value as any);\n }\n }\n }\n\n // ── Field mapping ───────────────────────────────────────────────────────────\n\n protected mapSortField(field: string): string {\n if (field === 'createdAt') return 'created_at';\n if (field === 'updatedAt') return 'updated_at';\n return field;\n }\n\n protected mapAggregateFunc(func: string): string {\n switch (func) {\n case 'count':\n return 'count';\n case 'sum':\n return 'sum';\n case 'avg':\n return 'avg';\n case 'min':\n return 'min';\n case 'max':\n return 'max';\n default:\n throw new Error(`Unsupported aggregate function: ${func}`);\n }\n }\n\n // ── Window function builder ─────────────────────────────────────────────────\n\n protected buildWindowFunction(spec: any): string {\n const func = spec.function.toUpperCase();\n let sql = `${func}()`;\n\n const overParts: string[] = [];\n\n if (spec.partitionBy && Array.isArray(spec.partitionBy) && spec.partitionBy.length > 0) {\n const partitionFields = spec.partitionBy.map((f: string) => this.mapSortField(f)).join(', ');\n overParts.push(`PARTITION BY ${partitionFields}`);\n }\n\n if (spec.orderBy && Array.isArray(spec.orderBy) && spec.orderBy.length > 0) {\n const orderFields = spec.orderBy\n .map((s: any) => {\n const field = this.mapSortField(s.field);\n const order = (s.order || 'asc').toUpperCase();\n return `${field} ${order}`;\n })\n .join(', ');\n overParts.push(`ORDER BY ${orderFields}`);\n }\n\n sql += overParts.length > 0 ? ` OVER (${overParts.join(' ')})` : ` OVER ()`;\n return sql;\n }\n\n // ── Column creation helper ──────────────────────────────────────────────────\n\n protected createColumn(table: Knex.CreateTableBuilder, name: string, field: any) {\n if (field.multiple) {\n table.json(name);\n return;\n }\n\n const type = field.type || 'string';\n let col: any;\n switch (type) {\n case 'string':\n case 'email':\n case 'url':\n case 'phone':\n case 'password':\n col = table.string(name);\n break;\n case 'text':\n case 'textarea':\n case 'html':\n case 'markdown':\n col = table.text(name);\n break;\n case 'integer':\n case 'int':\n col = table.integer(name);\n break;\n case 'float':\n case 'number':\n case 'currency':\n case 'percent':\n col = table.float(name);\n break;\n case 'boolean':\n col = table.boolean(name);\n break;\n case 'date':\n col = table.date(name);\n break;\n case 'datetime':\n col = table.timestamp(name);\n break;\n case 'time':\n col = table.time(name);\n break;\n case 'json':\n case 'object':\n case 'array':\n case 'image':\n case 'file':\n case 'avatar':\n case 'location':\n col = table.json(name);\n break;\n case 'lookup':\n col = table.string(name);\n if (field.reference_to) {\n table.foreign(name).references('id').inTable(field.reference_to);\n }\n break;\n case 'summary':\n col = table.float(name);\n break;\n case 'auto_number':\n col = table.string(name);\n break;\n case 'formula':\n return; // Virtual — no column\n default:\n col = table.string(name);\n }\n\n if (col) {\n if (field.unique) col.unique();\n if (field.required) col.notNullable();\n }\n }\n\n // ── Database helpers ────────────────────────────────────────────────────────\n\n protected async ensureDatabaseExists() {\n // SQLite auto-creates database files — no need to check\n if (this.isSqlite) return;\n\n // Only PostgreSQL and MySQL support programmatic database creation\n if (!this.isPostgres && !this.isMysql) return;\n\n try {\n await this.knex.raw('SELECT 1');\n } catch (e: any) {\n // PostgreSQL: '3D000' = database does not exist\n // MySQL: 'ER_BAD_DB_ERROR' (errno 1049) = unknown database\n if (\n e.code === '3D000' ||\n e.code === 'ER_BAD_DB_ERROR' ||\n e.errno === 1049\n ) {\n await this.createDatabase();\n } else {\n throw e;\n }\n }\n }\n\n protected async createDatabase() {\n const config = this.config as any;\n const connection = config.connection;\n let dbName = '';\n const adminConfig = { ...config };\n\n if (this.isPostgres) {\n // PostgreSQL: connect to the 'postgres' maintenance database\n if (typeof connection === 'string') {\n const url = new URL(connection);\n dbName = url.pathname.slice(1);\n url.pathname = '/postgres';\n adminConfig.connection = url.toString();\n } else {\n dbName = connection.database;\n adminConfig.connection = { ...connection, database: 'postgres' };\n }\n } else if (this.isMysql) {\n // MySQL: connect without specifying a database\n if (typeof connection === 'string') {\n const url = new URL(connection);\n dbName = url.pathname.slice(1);\n url.pathname = '/';\n adminConfig.connection = url.toString();\n } else {\n dbName = connection.database;\n const { database: _db, ...rest } = connection;\n adminConfig.connection = rest;\n }\n } else {\n return; // Unsupported dialect for auto-creation\n }\n\n const adminKnex = knex(adminConfig);\n try {\n if (this.isPostgres) {\n await adminKnex.raw(`CREATE DATABASE \"${dbName}\"`);\n } else if (this.isMysql) {\n await adminKnex.raw(`CREATE DATABASE IF NOT EXISTS \\`${dbName}\\``);\n }\n } finally {\n await adminKnex.destroy();\n }\n }\n\n protected isJsonField(type: string, field: any): boolean {\n return ['json', 'object', 'array', 'image', 'file', 'avatar', 'location'].includes(type) || field.multiple;\n }\n\n // ── SQLite serialisation ────────────────────────────────────────────────────\n\n protected formatInput(object: string, data: any): any {\n if (!this.isSqlite) return data;\n\n const fields = this.jsonFields[object];\n if (!fields || fields.length === 0) return data;\n\n const copy = { ...data };\n for (const field of fields) {\n if (copy[field] !== undefined && typeof copy[field] === 'object' && copy[field] !== null) {\n copy[field] = JSON.stringify(copy[field]);\n }\n }\n return copy;\n }\n\n protected formatOutput(object: string, data: any): any {\n if (!data) return data;\n\n if (this.isSqlite) {\n const jsonFields = this.jsonFields[object];\n if (jsonFields && jsonFields.length > 0) {\n for (const field of jsonFields) {\n if (data[field] !== undefined && typeof data[field] === 'string') {\n try {\n data[field] = JSON.parse(data[field]);\n } catch {\n // keep as string\n }\n }\n }\n }\n\n const booleanFields = this.booleanFields[object];\n if (booleanFields && booleanFields.length > 0) {\n for (const field of booleanFields) {\n if (data[field] !== undefined && data[field] !== null) {\n data[field] = Boolean(data[field]);\n }\n }\n }\n }\n\n return data;\n }\n\n // ── Introspection internals ─────────────────────────────────────────────────\n\n protected async introspectColumns(tableName: string): Promise<IntrospectedColumn[]> {\n const columnInfo = await this.knex(tableName).columnInfo();\n const columns: IntrospectedColumn[] = [];\n\n for (const [colName, info] of Object.entries<any>(columnInfo)) {\n let type = 'string';\n let maxLength: number | undefined;\n\n if (this.isSqlite) {\n type = info.type?.toLowerCase() || 'string';\n } else {\n type = info.type || 'string';\n }\n\n if (info.maxLength) {\n maxLength = info.maxLength;\n }\n\n columns.push({\n name: colName,\n type,\n nullable: info.nullable !== false,\n defaultValue: info.defaultValue,\n isPrimary: false,\n isUnique: false,\n maxLength,\n });\n }\n\n return columns;\n }\n\n protected async introspectForeignKeys(tableName: string): Promise<IntrospectedForeignKey[]> {\n const foreignKeys: IntrospectedForeignKey[] = [];\n\n try {\n if (this.isPostgres) {\n const result = await this.knex.raw(\n `\n SELECT\n kcu.column_name,\n ccu.table_name AS referenced_table,\n ccu.column_name AS referenced_column,\n tc.constraint_name\n FROM information_schema.table_constraints AS tc\n JOIN information_schema.key_column_usage AS kcu\n ON tc.constraint_name = kcu.constraint_name\n AND tc.table_schema = kcu.table_schema\n JOIN information_schema.constraint_column_usage AS ccu\n ON ccu.constraint_name = tc.constraint_name\n AND ccu.table_schema = tc.table_schema\n WHERE tc.constraint_type = 'FOREIGN KEY'\n AND tc.table_name = ?\n `,\n [tableName],\n );\n\n for (const row of result.rows) {\n foreignKeys.push({\n columnName: row.column_name,\n referencedTable: row.referenced_table,\n referencedColumn: row.referenced_column,\n constraintName: row.constraint_name,\n });\n }\n } else if (this.isMysql) {\n const result = await this.knex.raw(\n `\n SELECT\n COLUMN_NAME as column_name,\n REFERENCED_TABLE_NAME as referenced_table,\n REFERENCED_COLUMN_NAME as referenced_column,\n CONSTRAINT_NAME as constraint_name\n FROM information_schema.KEY_COLUMN_USAGE\n WHERE TABLE_SCHEMA = DATABASE()\n AND TABLE_NAME = ?\n AND REFERENCED_TABLE_NAME IS NOT NULL\n `,\n [tableName],\n );\n\n for (const row of result[0]) {\n foreignKeys.push({\n columnName: row.column_name,\n referencedTable: row.referenced_table,\n referencedColumn: row.referenced_column,\n constraintName: row.constraint_name,\n });\n }\n } else if (this.isSqlite) {\n const tableExistsResult = await this.knex.raw(\n \"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?\",\n [tableName],\n );\n\n if (!Array.isArray(tableExistsResult) || tableExistsResult.length === 0) {\n return foreignKeys;\n }\n\n const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, '');\n const result = await this.knex.raw(`PRAGMA foreign_key_list(${safeTableName})`);\n\n for (const row of result) {\n foreignKeys.push({\n columnName: row.from,\n referencedTable: row.table,\n referencedColumn: row.to,\n constraintName: `fk_${tableName}_${row.from}`,\n });\n }\n }\n } catch {\n // silently ignore introspection errors\n }\n\n return foreignKeys;\n }\n\n protected async introspectPrimaryKeys(tableName: string): Promise<string[]> {\n const primaryKeys: string[] = [];\n\n try {\n if (this.isPostgres) {\n const result = await this.knex.raw(\n `\n SELECT a.attname as column_name\n FROM pg_index i\n JOIN pg_attribute a ON a.attrelid = i.indrelid\n AND a.attnum = ANY(i.indkey)\n WHERE i.indrelid = ?::regclass\n AND i.indisprimary\n `,\n [tableName],\n );\n\n for (const row of result.rows) {\n primaryKeys.push(row.column_name);\n }\n } else if (this.isMysql) {\n const result = await this.knex.raw(\n `\n SELECT COLUMN_NAME as column_name\n FROM information_schema.KEY_COLUMN_USAGE\n WHERE TABLE_SCHEMA = DATABASE()\n AND TABLE_NAME = ?\n AND CONSTRAINT_NAME = 'PRIMARY'\n `,\n [tableName],\n );\n\n for (const row of result[0]) {\n primaryKeys.push(row.column_name);\n }\n } else if (this.isSqlite) {\n const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, '');\n\n const tablesResult = await this.knex.raw(\"SELECT name FROM sqlite_master WHERE type = 'table'\");\n const tableNames = Array.isArray(tablesResult) ? tablesResult.map((row: any) => row.name) : [];\n\n if (!tableNames.includes(safeTableName)) {\n return primaryKeys;\n }\n\n const result = await this.knex.raw(`PRAGMA table_info(${safeTableName})`);\n\n for (const row of result) {\n if (row.pk === 1) {\n primaryKeys.push(row.name);\n }\n }\n }\n } catch {\n // silently ignore\n }\n\n return primaryKeys;\n }\n\n protected async introspectUniqueConstraints(tableName: string): Promise<string[]> {\n const uniqueColumns: string[] = [];\n\n try {\n if (this.isPostgres) {\n const result = await this.knex.raw(\n `\n SELECT c.column_name\n FROM information_schema.table_constraints tc\n JOIN information_schema.constraint_column_usage AS ccu\n ON tc.constraint_schema = ccu.constraint_schema\n AND tc.constraint_name = ccu.constraint_name\n WHERE tc.constraint_type = 'UNIQUE'\n AND tc.table_name = ?\n `,\n [tableName],\n );\n\n for (const row of result.rows) {\n uniqueColumns.push(row.column_name);\n }\n } else if (this.isMysql) {\n const result = await this.knex.raw(\n `\n SELECT COLUMN_NAME\n FROM information_schema.TABLE_CONSTRAINTS tc\n JOIN information_schema.KEY_COLUMN_USAGE kcu\n USING (CONSTRAINT_NAME, TABLE_SCHEMA, TABLE_NAME)\n WHERE CONSTRAINT_TYPE = 'UNIQUE'\n AND TABLE_SCHEMA = DATABASE()\n AND TABLE_NAME = ?\n `,\n [tableName],\n );\n\n for (const row of result[0]) {\n uniqueColumns.push(row.COLUMN_NAME);\n }\n } else if (this.isSqlite) {\n const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, '');\n\n const tablesResult = await this.knex.raw(\"SELECT name FROM sqlite_master WHERE type = 'table'\");\n const tableNames = Array.isArray(tablesResult) ? tablesResult.map((row: any) => row.name) : [];\n\n if (!tableNames.includes(safeTableName)) {\n return uniqueColumns;\n }\n\n const indexes = await this.knex.raw(`PRAGMA index_list(${safeTableName})`);\n\n for (const idx of indexes) {\n if (idx.unique === 1) {\n const info = await this.knex.raw(`PRAGMA index_info(${idx.name})`);\n if (info.length === 1) {\n uniqueColumns.push(info[0].name);\n }\n }\n }\n }\n } catch {\n // silently ignore\n }\n\n return uniqueColumns;\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { SqlDriver } from './sql-driver.js';\n\nexport { SqlDriver };\nexport type {\n SqlDriverConfig,\n IntrospectedSchema,\n IntrospectedTable,\n IntrospectedColumn,\n IntrospectedForeignKey,\n} from './sql-driver.js';\n\nexport default {\n id: 'com.objectstack.driver.sql',\n version: '1.0.0',\n\n onEnable: async (context: any) => {\n const { logger, config, drivers } = context;\n logger.info('[SQL Driver] Initializing...');\n\n if (drivers) {\n const driver = new SqlDriver(config);\n drivers.register(driver);\n logger.info(`[SQL Driver] Registered driver: ${driver.name}`);\n } else {\n logger.warn('[SQL Driver] No driver registry found in context.');\n }\n },\n};\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/driver-sql",
3
- "version": "3.2.9",
3
+ "version": "3.3.1",
4
4
  "license": "Apache-2.0",
5
5
  "description": "SQL Driver for ObjectStack - Supports PostgreSQL, MySQL, SQLite via Knex",
6
6
  "main": "dist/index.js",
@@ -13,14 +13,14 @@
13
13
  }
14
14
  },
15
15
  "dependencies": {
16
- "knex": "^3.1.0",
17
- "nanoid": "^3.3.11",
18
- "@objectstack/core": "3.2.9",
19
- "@objectstack/spec": "3.2.9"
16
+ "knex": "^3.2.3",
17
+ "nanoid": "^5.1.7",
18
+ "@objectstack/core": "3.3.1",
19
+ "@objectstack/spec": "3.3.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^25.5.0",
23
- "better-sqlite3": "^11.9.1",
23
+ "better-sqlite3": "^12.8.0",
24
24
  "typescript": "^5.0.0",
25
25
  "vitest": "^4.1.0"
26
26
  },
@@ -144,29 +144,29 @@ describe('SqlDriver Advanced Operations (SQLite)', () => {
144
144
  });
145
145
 
146
146
  it('should update many records', async () => {
147
- const result = await driver.updateMany('orders', { status: 'pending' } as any, { status: 'processing' });
147
+ const result = await driver.updateMany('orders', { where: { status: 'pending' } } as any, { status: 'processing' });
148
148
 
149
- expect(result.modifiedCount).toBeGreaterThan(0);
149
+ expect(result).toBeGreaterThan(0);
150
150
 
151
151
  const results = await driver.find('orders', { where: { status: 'processing' } });
152
152
  expect(results.length).toBe(1);
153
153
  });
154
154
 
155
155
  it('should delete many records', async () => {
156
- const result = await driver.deleteMany('orders', { status: 'cancelled' } as any);
156
+ const result = await driver.deleteMany('orders', { where: { status: 'cancelled' } } as any);
157
157
 
158
- expect(result.deletedCount).toBe(1);
158
+ expect(result).toBe(1);
159
159
 
160
160
  const remaining = await driver.count('orders', {});
161
161
  expect(remaining).toBe(4);
162
162
  });
163
163
 
164
164
  it('should handle empty bulk update and delete', async () => {
165
- const result = await driver.updateMany('orders', { status: 'nonexistent' } as any, { status: 'updated' });
166
- expect(result.modifiedCount).toBe(0);
165
+ const result = await driver.updateMany('orders', { where: { status: 'nonexistent' } } as any, { status: 'updated' });
166
+ expect(result).toBe(0);
167
167
 
168
- const deleteResult = await driver.deleteMany('orders', { id: 'nonexistent' } as any);
169
- expect(deleteResult.deletedCount).toBe(0);
168
+ const deleteResult = await driver.deleteMany('orders', { where: { id: 'nonexistent' } } as any);
169
+ expect(deleteResult).toBe(0);
170
170
  });
171
171
  });
172
172
 
@@ -365,7 +365,7 @@ describe('SqlDriver Advanced Operations (SQLite)', () => {
365
365
 
366
366
  it('should handle count with complex filters', async () => {
367
367
  const count = await driver.count('orders', {
368
- $and: [{ status: 'completed' }, { amount: { $gt: 100 } }],
368
+ where: { $and: [{ status: 'completed' }, { amount: { $gt: 100 } }] },
369
369
  } as any);
370
370
 
371
371
  expect(count).toBe(2);
@@ -72,11 +72,11 @@ describe('SqlDriver (QueryAST Format)', () => {
72
72
  });
73
73
 
74
74
  describe('QueryAST Format Support', () => {
75
- it('should support QueryAST with "top" instead of "limit"', async () => {
75
+ it('should support QueryAST with limit and orderBy', async () => {
76
76
  const results = await driver.find('products', {
77
77
  fields: ['name', 'price'],
78
- top: 2,
79
- sort: [{ field: 'price', order: 'asc' as const }],
78
+ limit: 2,
79
+ orderBy: [{ field: 'price', order: 'asc' as const }],
80
80
  } as any);
81
81
 
82
82
  expect(results.length).toBe(2);
@@ -84,10 +84,10 @@ describe('SqlDriver (QueryAST Format)', () => {
84
84
  expect(results[1].name).toBe('Chair');
85
85
  });
86
86
 
87
- it('should support QueryAST sort format with object notation', async () => {
87
+ it('should support QueryAST orderBy with object notation', async () => {
88
88
  const results = await driver.find('products', {
89
89
  fields: ['name'],
90
- sort: [
90
+ orderBy: [
91
91
  { field: 'category', order: 'asc' as const },
92
92
  { field: 'price', order: 'desc' as const },
93
93
  ],
@@ -98,12 +98,12 @@ describe('SqlDriver (QueryAST Format)', () => {
98
98
  expect(results[3].name).toBe('Desk');
99
99
  });
100
100
 
101
- it('should support QueryAST with filters and pagination', async () => {
101
+ it('should support QueryAST with where, offset, limit, and orderBy', async () => {
102
102
  const results = await driver.find('products', {
103
- filters: [['category', '=', 'Electronics']],
104
- skip: 1,
105
- top: 1,
106
- sort: [{ field: 'price', order: 'asc' as const }],
103
+ where: [['category', '=', 'Electronics']],
104
+ offset: 1,
105
+ limit: 1,
106
+ orderBy: [{ field: 'price', order: 'asc' as const }],
107
107
  } as any);
108
108
 
109
109
  expect(results.length).toBe(1);
@@ -131,20 +131,20 @@ describe('SqlDriver (QueryAST Format)', () => {
131
131
  expect(furniture.total_price).toBe(550);
132
132
  });
133
133
 
134
- it('should support count with QueryAST format', async () => {
134
+ it('should support count with QueryAST where clause', async () => {
135
135
  const count = await driver.count('products', {
136
- filters: [['price', '>', 300]],
136
+ where: [['price', '>', 300]],
137
137
  } as any);
138
138
  expect(count).toBe(3);
139
139
  });
140
140
  });
141
141
 
142
- describe('Backward Compatibility', () => {
143
- it('should still support legacy UnifiedQuery format with "limit"', async () => {
142
+ describe('Standard QueryAST Pagination', () => {
143
+ it('should support limit with orderBy using standard keys', async () => {
144
144
  const results = await driver.find('products', {
145
145
  fields: ['name'],
146
146
  limit: 2,
147
- sort: [['price', 'asc']],
147
+ orderBy: [{ field: 'price', order: 'asc' }],
148
148
  } as any);
149
149
 
150
150
  expect(results.length).toBe(2);
@@ -161,14 +161,12 @@ describe('SqlDriver (QueryAST Format)', () => {
161
161
  const electronics = results.find((r: any) => r.category === 'Electronics');
162
162
  expect(electronics.avg_price).toBeCloseTo(541.67, 1);
163
163
  });
164
- });
165
164
 
166
- describe('Mixed Format Support', () => {
167
- it('should handle query with both top and skip', async () => {
165
+ it('should support offset and limit with orderBy', async () => {
168
166
  const results = await driver.find('products', {
169
- top: 3,
170
- skip: 2,
171
- sort: [{ field: 'name', order: 'asc' as const }],
167
+ limit: 3,
168
+ offset: 2,
169
+ orderBy: [{ field: 'name', order: 'asc' as const }],
172
170
  } as any);
173
171
 
174
172
  expect(results.length).toBe(3);
@@ -177,4 +175,69 @@ describe('SqlDriver (QueryAST Format)', () => {
177
175
  expect(results[2].name).toBe('Mouse');
178
176
  });
179
177
  });
178
+
179
+ describe('Legacy Keys Are Ignored', () => {
180
+ it('should ignore legacy "filters" key — only "where" is recognized', async () => {
181
+ const results = await driver.find('products', {
182
+ filters: [['category', '=', 'Furniture']],
183
+ } as any);
184
+
185
+ // "filters" is not recognized, so no WHERE clause is applied — returns all rows
186
+ expect(results.length).toBe(5);
187
+ });
188
+
189
+ it('should use "where" and ignore "filters" when both are present', async () => {
190
+ const results = await driver.find('products', {
191
+ where: [['category', '=', 'Electronics']],
192
+ filters: [['category', '=', 'Furniture']],
193
+ } as any);
194
+
195
+ // Only "where" is applied — returns Electronics, not Furniture
196
+ expect(results.every((r: any) => r.category === 'Electronics')).toBe(true);
197
+ expect(results.length).toBe(3);
198
+ });
199
+
200
+ it('should ignore legacy "sort" key — only "orderBy" is recognized', async () => {
201
+ const results = await driver.find('products', {
202
+ fields: ['name'],
203
+ limit: 5,
204
+ orderBy: [{ field: 'price', order: 'desc' as const }],
205
+ sort: [{ field: 'price', order: 'asc' as const }],
206
+ } as any);
207
+
208
+ // "sort" is ignored; "orderBy" (desc) is applied — most expensive first
209
+ expect(results.length).toBe(5);
210
+ expect(results[0].name).toBe('Laptop');
211
+ expect(results[4].name).toBe('Mouse');
212
+ });
213
+
214
+ it('should ignore legacy "skip" key — only "offset" is recognized', async () => {
215
+ const results = await driver.find('products', {
216
+ skip: 3,
217
+ orderBy: [{ field: 'name', order: 'asc' as const }],
218
+ } as any);
219
+
220
+ // "skip" is not recognized — no offset applied, returns all 5 rows
221
+ expect(results.length).toBe(5);
222
+ });
223
+
224
+ it('should ignore legacy "top" key — only "limit" is recognized', async () => {
225
+ const results = await driver.find('products', {
226
+ top: 2,
227
+ orderBy: [{ field: 'name', order: 'asc' as const }],
228
+ } as any);
229
+
230
+ // "top" is not recognized — no limit applied, returns all 5 rows
231
+ expect(results.length).toBe(5);
232
+ });
233
+
234
+ it('should ignore legacy "filters" in count — only "where" is recognized', async () => {
235
+ const count = await driver.count('products', {
236
+ filters: [['price', '>', 300]],
237
+ } as any);
238
+
239
+ // "filters" is not recognized — counts all rows
240
+ expect(count).toBe(5);
241
+ });
242
+ });
180
243
  });
@@ -243,4 +243,71 @@ describe('SqlDriver Schema Sync (SQLite)', () => {
243
243
  expect(row.email).toBe('test@example.com');
244
244
  expect(row.office_loc).toEqual({ lat: 10, lng: 20 });
245
245
  });
246
+
247
+ it('should skip ensureDatabaseExists for SQLite (no-op)', async () => {
248
+ // SQLite auto-creates database files, so ensureDatabaseExists should be a no-op
249
+ // This test verifies that initObjects works normally for SQLite without errors
250
+ const objects = [
251
+ {
252
+ name: 'db_check_test',
253
+ fields: {
254
+ value: { type: 'string' },
255
+ },
256
+ },
257
+ ];
258
+
259
+ // Should not throw — SQLite skips ensureDatabaseExists
260
+ await driver.initObjects(objects);
261
+
262
+ const exists = await knexInstance.schema.hasTable('db_check_test');
263
+ expect(exists).toBe(true);
264
+ });
265
+
266
+ it('should use object parameter (physical table name) in syncSchema, not schema.name (FQN)', async () => {
267
+ // Simulates the real-world scenario: syncRegisteredSchemas passes the
268
+ // physical table name 'sys_user' while the schema object has name 'sys__user' (FQN).
269
+ const physicalTableName = 'sys_user';
270
+ const fqnName = 'sys__user';
271
+
272
+ await driver.syncSchema(physicalTableName, {
273
+ name: fqnName,
274
+ fields: {
275
+ email: { type: 'string' },
276
+ display_name: { type: 'string' },
277
+ },
278
+ });
279
+
280
+ // The table should be created with the physical name, not the FQN
281
+ const existsPhysical = await knexInstance.schema.hasTable(physicalTableName);
282
+ expect(existsPhysical).toBe(true);
283
+
284
+ const existsFqn = await knexInstance.schema.hasTable(fqnName);
285
+ expect(existsFqn).toBe(false);
286
+
287
+ // Verify the table has the correct columns
288
+ const columns = await knexInstance(physicalTableName).columnInfo();
289
+ expect(columns).toHaveProperty('id');
290
+ expect(columns).toHaveProperty('email');
291
+ expect(columns).toHaveProperty('display_name');
292
+ });
293
+
294
+ it('should prefer tableName over name in initObjects for defense-in-depth', async () => {
295
+ const objects = [
296
+ {
297
+ name: 'crm__deal',
298
+ tableName: 'crm_deal',
299
+ fields: {
300
+ amount: { type: 'number' },
301
+ },
302
+ },
303
+ ];
304
+
305
+ await driver.initObjects(objects);
306
+
307
+ const existsPhysical = await knexInstance.schema.hasTable('crm_deal');
308
+ expect(existsPhysical).toBe(true);
309
+
310
+ const existsFqn = await knexInstance.schema.hasTable('crm__deal');
311
+ expect(existsFqn).toBe(false);
312
+ });
246
313
  });