@@ -72,8 +72,23 @@ export interface ExcelDriverConfig {
7272 * in a separate worksheet, with the first row containing column headers.
7373 *
7474 * Uses ExcelJS library for secure Excel file operations.
75+ *
76+ * Implements both the legacy Driver interface from @objectql/types and
77+ * the standard DriverInterface from @objectstack/spec for compatibility
78+ * with the new kernel-based plugin system.
7579 */
7680export class ExcelDriver implements Driver {
81+ // Driver metadata (ObjectStack-compatible)
82+ public readonly name = 'ExcelDriver' ;
83+ public readonly version = '3.0.1' ;
84+ public readonly supports = {
85+ transactions : false ,
86+ joins : false ,
87+ fullTextSearch : false ,
88+ jsonFields : true ,
89+ arrayFields : true
90+ } ;
91+
7792 private config : ExcelDriverConfig ;
7893 private workbook ! : ExcelJS . Workbook ;
7994 private workbooks : Map < string , ExcelJS . Workbook > ; // For file-per-object mode
@@ -114,6 +129,46 @@ export class ExcelDriver implements Driver {
114129 await this . loadWorkbook ( ) ;
115130 }
116131
132+ /**
133+ * Connect to the database (for DriverInterface compatibility)
134+ * This calls init() to load the workbook.
135+ */
136+ async connect ( ) : Promise < void > {
137+ await this . init ( ) ;
138+ }
139+
140+ /**
141+ * Check database connection health
142+ */
143+ async checkHealth ( ) : Promise < boolean > {
144+ try {
145+ if ( this . fileStorageMode === 'single-file' ) {
146+ // Check if file exists or can be created
147+ if ( ! fs . existsSync ( this . filePath ) ) {
148+ if ( ! this . config . createIfMissing ) {
149+ return false ;
150+ }
151+ // Check if directory is writable
152+ const dir = path . dirname ( this . filePath ) ;
153+ if ( ! fs . existsSync ( dir ) ) {
154+ return false ;
155+ }
156+ }
157+ return true ;
158+ } else {
159+ // Check if directory exists or can be created
160+ if ( ! fs . existsSync ( this . filePath ) ) {
161+ if ( ! this . config . createIfMissing ) {
162+ return false ;
163+ }
164+ }
165+ return true ;
166+ }
167+ } catch ( error ) {
168+ return false ;
169+ }
170+ }
171+
117172 /**
118173 * Factory method to create and initialize the driver.
119174 */
@@ -515,6 +570,8 @@ export class ExcelDriver implements Driver {
515570 * Find multiple records matching the query criteria.
516571 */
517572 async find ( objectName : string , query : any = { } , options ?: any ) : Promise < any [ ] > {
573+ // Normalize query to support both legacy and QueryAST formats
574+ const normalizedQuery = this . normalizeQuery ( query ) ;
518575 let results = this . data . get ( objectName ) || [ ] ;
519576
520577 // Return empty array if no data
@@ -526,26 +583,26 @@ export class ExcelDriver implements Driver {
526583 results = results . map ( r => ( { ...r } ) ) ;
527584
528585 // Apply filters
529- if ( query . filters ) {
530- results = this . applyFilters ( results , query . filters ) ;
586+ if ( normalizedQuery . filters ) {
587+ results = this . applyFilters ( results , normalizedQuery . filters ) ;
531588 }
532589
533590 // Apply sorting
534- if ( query . sort && Array . isArray ( query . sort ) ) {
535- results = this . applySort ( results , query . sort ) ;
591+ if ( normalizedQuery . sort && Array . isArray ( normalizedQuery . sort ) ) {
592+ results = this . applySort ( results , normalizedQuery . sort ) ;
536593 }
537594
538595 // Apply pagination
539- if ( query . skip ) {
540- results = results . slice ( query . skip ) ;
596+ if ( normalizedQuery . skip ) {
597+ results = results . slice ( normalizedQuery . skip ) ;
541598 }
542- if ( query . limit ) {
543- results = results . slice ( 0 , query . limit ) ;
599+ if ( normalizedQuery . limit ) {
600+ results = results . slice ( 0 , normalizedQuery . limit ) ;
544601 }
545602
546603 // Apply field projection
547- if ( query . fields && Array . isArray ( query . fields ) ) {
548- results = results . map ( doc => this . projectFields ( doc , query . fields ) ) ;
604+ if ( normalizedQuery . fields && Array . isArray ( normalizedQuery . fields ) ) {
605+ results = results . map ( doc => this . projectFields ( doc , normalizedQuery . fields ) ) ;
549606 }
550607
551608 return results ;
@@ -794,6 +851,39 @@ export class ExcelDriver implements Driver {
794851
795852 // ========== Helper Methods ==========
796853
854+ /**
855+ * Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
856+ * This ensures backward compatibility while supporting the new @objectstack/spec interface.
857+ *
858+ * QueryAST format uses 'top' for limit, while UnifiedQuery uses 'limit'.
859+ * QueryAST sort is array of {field, order}, while UnifiedQuery is array of [field, order].
860+ */
861+ private normalizeQuery ( query : any ) : any {
862+ if ( ! query ) return { } ;
863+
864+ const normalized : any = { ...query } ;
865+
866+ // Normalize limit/top
867+ if ( normalized . top !== undefined && normalized . limit === undefined ) {
868+ normalized . limit = normalized . top ;
869+ }
870+
871+ // Normalize sort format
872+ if ( normalized . sort && Array . isArray ( normalized . sort ) ) {
873+ // Check if it's already in the array format [field, order]
874+ const firstSort = normalized . sort [ 0 ] ;
875+ if ( firstSort && typeof firstSort === 'object' && ! Array . isArray ( firstSort ) ) {
876+ // Convert from QueryAST format {field, order} to internal format [field, order]
877+ normalized . sort = normalized . sort . map ( ( item : any ) => [
878+ item . field ,
879+ item . order || item . direction || item . dir || 'asc'
880+ ] ) ;
881+ }
882+ }
883+
884+ return normalized ;
885+ }
886+
797887 /**
798888 * Apply filters to an array of records (in-memory filtering).
799889 */
0 commit comments