3232 */
3333
3434import { Driver , ObjectQLError } from '@objectql/types' ;
35+ import { DriverInterface , QueryAST , FilterNode , SortNode } from '@objectstack/spec' ;
36+
37+ /**
38+ * Command interface for executeCommand method
39+ */
40+ export interface Command {
41+ type : 'create' | 'update' | 'delete' | 'bulkCreate' | 'bulkUpdate' | 'bulkDelete' ;
42+ object : string ;
43+ data ?: any ;
44+ id ?: string | number ;
45+ ids ?: Array < string | number > ;
46+ records ?: any [ ] ;
47+ updates ?: Array < { id : string | number , data : any } > ;
48+ options ?: any ;
49+ }
50+
51+ /**
52+ * Command result interface
53+ */
54+ export interface CommandResult {
55+ success : boolean ;
56+ data ?: any ;
57+ affected : number ;
58+ error ?: string ;
59+ }
3560
3661/**
3762 * Configuration options for the LocalStorage driver.
@@ -55,10 +80,10 @@ export interface LocalStorageDriverConfig {
5580 *
5681 * Example: `objectql:users:user-123` → `{"id":"user-123","name":"Alice",...}`
5782 */
58- export class LocalStorageDriver implements Driver {
83+ export class LocalStorageDriver implements Driver , DriverInterface {
5984 // Driver metadata (ObjectStack-compatible)
6085 public readonly name = 'LocalStorageDriver' ;
61- public readonly version = '3 .0.1 ' ;
86+ public readonly version = '4 .0.0 ' ;
6287 public readonly supports = {
6388 transactions : false ,
6489 joins : false ,
@@ -536,8 +561,211 @@ export class LocalStorageDriver implements Driver {
536561 // No-op: LocalStorage driver doesn't need cleanup
537562 }
538563
564+ /**
565+ * Execute a query using QueryAST (DriverInterface v4.0 method)
566+ *
567+ * This method handles all query operations using the standard QueryAST format
568+ * from @objectstack/spec. It converts the AST to the legacy query format
569+ * and delegates to the existing find() method.
570+ *
571+ * @param ast - The query AST to execute
572+ * @param options - Optional execution options
573+ * @returns Query results with value array and count
574+ */
575+ async executeQuery ( ast : QueryAST , options ?: any ) : Promise < { value : any [ ] ; count ?: number } > {
576+ const objectName = ast . object || '' ;
577+
578+ // Convert QueryAST to legacy query format
579+ const legacyQuery : any = {
580+ fields : ast . fields ,
581+ filters : this . convertFilterNodeToLegacy ( ast . filters ) ,
582+ sort : ast . sort ?. map ( ( s : SortNode ) => [ s . field , s . order ] ) ,
583+ limit : ast . top ,
584+ skip : ast . skip ,
585+ } ;
586+
587+ // Use existing find method
588+ const results = await this . find ( objectName , legacyQuery , options ) ;
589+
590+ return {
591+ value : results ,
592+ count : results . length
593+ } ;
594+ }
595+
596+ /**
597+ * Execute a command (DriverInterface v4.0 method)
598+ *
599+ * This method handles all mutation operations (create, update, delete)
600+ * using a unified command interface.
601+ *
602+ * @param command - The command to execute
603+ * @param options - Optional execution options
604+ * @returns Command execution result
605+ */
606+ async executeCommand ( command : Command , options ?: any ) : Promise < CommandResult > {
607+ try {
608+ const cmdOptions = { ...options , ...command . options } ;
609+
610+ switch ( command . type ) {
611+ case 'create' :
612+ if ( ! command . data ) {
613+ throw new Error ( 'Create command requires data' ) ;
614+ }
615+ const created = await this . create ( command . object , command . data , cmdOptions ) ;
616+ return {
617+ success : true ,
618+ data : created ,
619+ affected : 1
620+ } ;
621+
622+ case 'update' :
623+ if ( ! command . id || ! command . data ) {
624+ throw new Error ( 'Update command requires id and data' ) ;
625+ }
626+ const updated = await this . update ( command . object , command . id , command . data , cmdOptions ) ;
627+ return {
628+ success : true ,
629+ data : updated ,
630+ affected : 1
631+ } ;
632+
633+ case 'delete' :
634+ if ( ! command . id ) {
635+ throw new Error ( 'Delete command requires id' ) ;
636+ }
637+ await this . delete ( command . object , command . id , cmdOptions ) ;
638+ return {
639+ success : true ,
640+ affected : 1
641+ } ;
642+
643+ case 'bulkCreate' :
644+ if ( ! command . records || ! Array . isArray ( command . records ) ) {
645+ throw new Error ( 'BulkCreate command requires records array' ) ;
646+ }
647+ const bulkCreated = [ ] ;
648+ for ( const record of command . records ) {
649+ const created = await this . create ( command . object , record , cmdOptions ) ;
650+ bulkCreated . push ( created ) ;
651+ }
652+ return {
653+ success : true ,
654+ data : bulkCreated ,
655+ affected : command . records . length
656+ } ;
657+
658+ case 'bulkUpdate' :
659+ if ( ! command . updates || ! Array . isArray ( command . updates ) ) {
660+ throw new Error ( 'BulkUpdate command requires updates array' ) ;
661+ }
662+ const updateResults = [ ] ;
663+ for ( const update of command . updates ) {
664+ const result = await this . update ( command . object , update . id , update . data , cmdOptions ) ;
665+ updateResults . push ( result ) ;
666+ }
667+ return {
668+ success : true ,
669+ data : updateResults ,
670+ affected : command . updates . length
671+ } ;
672+
673+ case 'bulkDelete' :
674+ if ( ! command . ids || ! Array . isArray ( command . ids ) ) {
675+ throw new Error ( 'BulkDelete command requires ids array' ) ;
676+ }
677+ let deleted = 0 ;
678+ for ( const id of command . ids ) {
679+ const result = await this . delete ( command . object , id , cmdOptions ) ;
680+ if ( result ) deleted ++ ;
681+ }
682+ return {
683+ success : true ,
684+ affected : deleted
685+ } ;
686+
687+ default :
688+ throw new Error ( `Unsupported command type: ${ ( command as any ) . type } ` ) ;
689+ }
690+ } catch ( error : any ) {
691+ return {
692+ success : false ,
693+ affected : 0 ,
694+ error : error . message || 'Unknown error occurred'
695+ } ;
696+ }
697+ }
698+
699+ /**
700+ * Execute raw command (for compatibility)
701+ *
702+ * @param command - Command string or object
703+ * @param parameters - Command parameters
704+ * @param options - Execution options
705+ */
706+ async execute ( command : any , parameters ?: any [ ] , options ?: any ) : Promise < any > {
707+ throw new Error ( 'LocalStorage driver does not support raw command execution. Use executeCommand() instead.' ) ;
708+ }
709+
539710 // ========== Helper Methods (Same as MemoryDriver) ==========
540711
712+ /**
713+ * Convert FilterNode from QueryAST to legacy filter format.
714+ *
715+ * @param node - The FilterNode to convert
716+ * @returns Legacy filter array format
717+ */
718+ private convertFilterNodeToLegacy ( node ?: FilterNode ) : any {
719+ if ( ! node ) return undefined ;
720+
721+ switch ( node . type ) {
722+ case 'comparison' :
723+ // Convert comparison node to [field, operator, value] format
724+ const operator = node . operator || '=' ;
725+ return [ [ node . field , operator , node . value ] ] ;
726+
727+ case 'and' :
728+ // Convert AND node to array with 'and' separator
729+ if ( ! node . children || node . children . length === 0 ) return undefined ;
730+ const andResults : any [ ] = [ ] ;
731+ for ( const child of node . children ) {
732+ const converted = this . convertFilterNodeToLegacy ( child ) ;
733+ if ( converted ) {
734+ if ( andResults . length > 0 ) {
735+ andResults . push ( 'and' ) ;
736+ }
737+ andResults . push ( ...( Array . isArray ( converted ) ? converted : [ converted ] ) ) ;
738+ }
739+ }
740+ return andResults . length > 0 ? andResults : undefined ;
741+
742+ case 'or' :
743+ // Convert OR node to array with 'or' separator
744+ if ( ! node . children || node . children . length === 0 ) return undefined ;
745+ const orResults : any [ ] = [ ] ;
746+ for ( const child of node . children ) {
747+ const converted = this . convertFilterNodeToLegacy ( child ) ;
748+ if ( converted ) {
749+ if ( orResults . length > 0 ) {
750+ orResults . push ( 'or' ) ;
751+ }
752+ orResults . push ( ...( Array . isArray ( converted ) ? converted : [ converted ] ) ) ;
753+ }
754+ }
755+ return orResults . length > 0 ? orResults : undefined ;
756+
757+ case 'not' :
758+ // NOT is complex - we'll just process the first child for now
759+ if ( node . children && node . children . length > 0 ) {
760+ return this . convertFilterNodeToLegacy ( node . children [ 0 ] ) ;
761+ }
762+ return undefined ;
763+
764+ default :
765+ return undefined ;
766+ }
767+ }
768+
541769 /**
542770 * Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
543771 * This ensures backward compatibility while supporting the new @objectstack/spec interface.
0 commit comments