3535import * as fs from 'fs' ;
3636import * as path from 'path' ;
3737import { Driver , ObjectQLError } from '@objectql/types' ;
38+ import { DriverInterface , QueryAST , FilterNode , SortNode } from '@objectstack/spec' ;
39+
40+ /**
41+ * Command interface for executeCommand method
42+ */
43+ export interface Command {
44+ type : 'create' | 'update' | 'delete' | 'bulkCreate' | 'bulkUpdate' | 'bulkDelete' ;
45+ object : string ;
46+ data ?: any ;
47+ id ?: string | number ;
48+ ids ?: Array < string | number > ;
49+ records ?: any [ ] ;
50+ updates ?: Array < { id : string | number , data : any } > ;
51+ options ?: any ;
52+ }
53+
54+ /**
55+ * Command result interface
56+ */
57+ export interface CommandResult {
58+ success : boolean ;
59+ data ?: any ;
60+ affected : number ;
61+ error ?: string ;
62+ }
3863
3964/**
4065 * Configuration options for the FileSystem driver.
@@ -59,10 +84,10 @@ export interface FileSystemDriverConfig {
5984 * - File: `{dataDir}/{objectName}.json`
6085 * - Content: Array of records `[{id: "1", ...}, {id: "2", ...}]`
6186 */
62- export class FileSystemDriver implements Driver {
87+ export class FileSystemDriver implements Driver , DriverInterface {
6388 // Driver metadata (ObjectStack-compatible)
6489 public readonly name = 'FileSystemDriver' ;
65- public readonly version = '3 .0.1 ' ;
90+ public readonly version = '4 .0.0 ' ;
6691 public readonly supports = {
6792 transactions : false ,
6893 joins : false ,
@@ -570,8 +595,211 @@ export class FileSystemDriver implements Driver {
570595 return this . cache . size ;
571596 }
572597
598+ /**
599+ * Execute a query using QueryAST (DriverInterface v4.0 method)
600+ *
601+ * This method handles all query operations using the standard QueryAST format
602+ * from @objectstack/spec. It converts the AST to the legacy query format
603+ * and delegates to the existing find() method.
604+ *
605+ * @param ast - The query AST to execute
606+ * @param options - Optional execution options
607+ * @returns Query results with value array and count
608+ */
609+ async executeQuery ( ast : QueryAST , options ?: any ) : Promise < { value : any [ ] ; count ?: number } > {
610+ const objectName = ast . object || '' ;
611+
612+ // Convert QueryAST to legacy query format
613+ const legacyQuery : any = {
614+ fields : ast . fields ,
615+ filters : this . convertFilterNodeToLegacy ( ast . filters ) ,
616+ sort : ast . sort ?. map ( ( s : SortNode ) => [ s . field , s . order ] ) ,
617+ limit : ast . top ,
618+ skip : ast . skip ,
619+ } ;
620+
621+ // Use existing find method
622+ const results = await this . find ( objectName , legacyQuery , options ) ;
623+
624+ return {
625+ value : results ,
626+ count : results . length
627+ } ;
628+ }
629+
630+ /**
631+ * Execute a command (DriverInterface v4.0 method)
632+ *
633+ * This method handles all mutation operations (create, update, delete)
634+ * using a unified command interface.
635+ *
636+ * @param command - The command to execute
637+ * @param options - Optional execution options
638+ * @returns Command execution result
639+ */
640+ async executeCommand ( command : Command , options ?: any ) : Promise < CommandResult > {
641+ try {
642+ const cmdOptions = { ...options , ...command . options } ;
643+
644+ switch ( command . type ) {
645+ case 'create' :
646+ if ( ! command . data ) {
647+ throw new Error ( 'Create command requires data' ) ;
648+ }
649+ const created = await this . create ( command . object , command . data , cmdOptions ) ;
650+ return {
651+ success : true ,
652+ data : created ,
653+ affected : 1
654+ } ;
655+
656+ case 'update' :
657+ if ( ! command . id || ! command . data ) {
658+ throw new Error ( 'Update command requires id and data' ) ;
659+ }
660+ const updated = await this . update ( command . object , command . id , command . data , cmdOptions ) ;
661+ return {
662+ success : true ,
663+ data : updated ,
664+ affected : 1
665+ } ;
666+
667+ case 'delete' :
668+ if ( ! command . id ) {
669+ throw new Error ( 'Delete command requires id' ) ;
670+ }
671+ await this . delete ( command . object , command . id , cmdOptions ) ;
672+ return {
673+ success : true ,
674+ affected : 1
675+ } ;
676+
677+ case 'bulkCreate' :
678+ if ( ! command . records || ! Array . isArray ( command . records ) ) {
679+ throw new Error ( 'BulkCreate command requires records array' ) ;
680+ }
681+ const bulkCreated = [ ] ;
682+ for ( const record of command . records ) {
683+ const created = await this . create ( command . object , record , cmdOptions ) ;
684+ bulkCreated . push ( created ) ;
685+ }
686+ return {
687+ success : true ,
688+ data : bulkCreated ,
689+ affected : command . records . length
690+ } ;
691+
692+ case 'bulkUpdate' :
693+ if ( ! command . updates || ! Array . isArray ( command . updates ) ) {
694+ throw new Error ( 'BulkUpdate command requires updates array' ) ;
695+ }
696+ const updateResults = [ ] ;
697+ for ( const update of command . updates ) {
698+ const result = await this . update ( command . object , update . id , update . data , cmdOptions ) ;
699+ updateResults . push ( result ) ;
700+ }
701+ return {
702+ success : true ,
703+ data : updateResults ,
704+ affected : command . updates . length
705+ } ;
706+
707+ case 'bulkDelete' :
708+ if ( ! command . ids || ! Array . isArray ( command . ids ) ) {
709+ throw new Error ( 'BulkDelete command requires ids array' ) ;
710+ }
711+ let deleted = 0 ;
712+ for ( const id of command . ids ) {
713+ const result = await this . delete ( command . object , id , cmdOptions ) ;
714+ if ( result ) deleted ++ ;
715+ }
716+ return {
717+ success : true ,
718+ affected : deleted
719+ } ;
720+
721+ default :
722+ throw new Error ( `Unsupported command type: ${ ( command as any ) . type } ` ) ;
723+ }
724+ } catch ( error : any ) {
725+ return {
726+ success : false ,
727+ affected : 0 ,
728+ error : error . message || 'Unknown error occurred'
729+ } ;
730+ }
731+ }
732+
733+ /**
734+ * Execute raw command (for compatibility)
735+ *
736+ * @param command - Command string or object
737+ * @param parameters - Command parameters
738+ * @param options - Execution options
739+ */
740+ async execute ( command : any , parameters ?: any [ ] , options ?: any ) : Promise < any > {
741+ throw new Error ( 'FileSystem driver does not support raw command execution. Use executeCommand() instead.' ) ;
742+ }
743+
573744 // ========== Helper Methods ==========
574745
746+ /**
747+ * Convert FilterNode from QueryAST to legacy filter format.
748+ *
749+ * @param node - The FilterNode to convert
750+ * @returns Legacy filter array format
751+ */
752+ private convertFilterNodeToLegacy ( node ?: FilterNode ) : any {
753+ if ( ! node ) return undefined ;
754+
755+ switch ( node . type ) {
756+ case 'comparison' :
757+ // Convert comparison node to [field, operator, value] format
758+ const operator = node . operator || '=' ;
759+ return [ [ node . field , operator , node . value ] ] ;
760+
761+ case 'and' :
762+ // Convert AND node to array with 'and' separator
763+ if ( ! node . children || node . children . length === 0 ) return undefined ;
764+ const andResults : any [ ] = [ ] ;
765+ for ( const child of node . children ) {
766+ const converted = this . convertFilterNodeToLegacy ( child ) ;
767+ if ( converted ) {
768+ if ( andResults . length > 0 ) {
769+ andResults . push ( 'and' ) ;
770+ }
771+ andResults . push ( ...( Array . isArray ( converted ) ? converted : [ converted ] ) ) ;
772+ }
773+ }
774+ return andResults . length > 0 ? andResults : undefined ;
775+
776+ case 'or' :
777+ // Convert OR node to array with 'or' separator
778+ if ( ! node . children || node . children . length === 0 ) return undefined ;
779+ const orResults : any [ ] = [ ] ;
780+ for ( const child of node . children ) {
781+ const converted = this . convertFilterNodeToLegacy ( child ) ;
782+ if ( converted ) {
783+ if ( orResults . length > 0 ) {
784+ orResults . push ( 'or' ) ;
785+ }
786+ orResults . push ( ...( Array . isArray ( converted ) ? converted : [ converted ] ) ) ;
787+ }
788+ }
789+ return orResults . length > 0 ? orResults : undefined ;
790+
791+ case 'not' :
792+ // NOT is complex - we'll just process the first child for now
793+ if ( node . children && node . children . length > 0 ) {
794+ return this . convertFilterNodeToLegacy ( node . children [ 0 ] ) ;
795+ }
796+ return undefined ;
797+
798+ default :
799+ return undefined ;
800+ }
801+ }
802+
575803 /**
576804 * Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
577805 * This ensures backward compatibility while supporting the new @objectstack/spec interface.
0 commit comments