Skip to content

Commit 8f1311c

Browse files
Complete driver-excel migration to v4.0.0
Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com>
1 parent a03b2fa commit 8f1311c

4 files changed

Lines changed: 411 additions & 3 deletions

File tree

packages/drivers/excel/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@objectql/driver-excel",
3-
"version": "3.0.1",
3+
"version": "4.0.0",
44
"description": "Excel file driver for ObjectQL - Read/write data from Excel files (.xlsx) with flexible storage modes",
55
"keywords": [
66
"objectql",
@@ -21,6 +21,7 @@
2121
},
2222
"dependencies": {
2323
"@objectql/types": "workspace:*",
24+
"@objectstack/spec": "^0.2.0",
2425
"exceljs": "^4.4.0"
2526
},
2627
"devDependencies": {

packages/drivers/excel/src/index.ts

Lines changed: 230 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,35 @@
3030
*/
3131

3232
import { Driver, ObjectQLError } from '@objectql/types';
33+
import { DriverInterface, QueryAST, FilterNode, SortNode } from '@objectstack/spec';
3334
import * as ExcelJS from 'exceljs';
3435
import * as fs from 'fs';
3536
import * as path from 'path';
3637

38+
/**
39+
* Command interface for executeCommand method
40+
*/
41+
export interface Command {
42+
type: 'create' | 'update' | 'delete' | 'bulkCreate' | 'bulkUpdate' | 'bulkDelete';
43+
object: string;
44+
data?: any;
45+
id?: string | number;
46+
ids?: Array<string | number>;
47+
records?: any[];
48+
updates?: Array<{id: string | number, data: any}>;
49+
options?: any;
50+
}
51+
52+
/**
53+
* Command result interface
54+
*/
55+
export interface CommandResult {
56+
success: boolean;
57+
data?: any;
58+
affected: number;
59+
error?: string;
60+
}
61+
3762
/**
3863
* File storage mode for the Excel driver.
3964
*/
@@ -77,10 +102,10 @@ export interface ExcelDriverConfig {
77102
* the standard DriverInterface from @objectstack/spec for compatibility
78103
* with the new kernel-based plugin system.
79104
*/
80-
export class ExcelDriver implements Driver {
105+
export class ExcelDriver implements Driver, DriverInterface {
81106
// Driver metadata (ObjectStack-compatible)
82107
public readonly name = 'ExcelDriver';
83-
public readonly version = '3.0.1';
108+
public readonly version = '4.0.0';
84109
public readonly supports = {
85110
transactions: false,
86111
joins: false,
@@ -849,8 +874,211 @@ export class ExcelDriver implements Driver {
849874
}
850875
}
851876

877+
/**
878+
* Execute a query using QueryAST (DriverInterface v4.0 method)
879+
*
880+
* This method handles all query operations using the standard QueryAST format
881+
* from @objectstack/spec. It converts the AST to the legacy query format
882+
* and delegates to the existing find() method.
883+
*
884+
* @param ast - The query AST to execute
885+
* @param options - Optional execution options
886+
* @returns Query results with value array and count
887+
*/
888+
async executeQuery(ast: QueryAST, options?: any): Promise<{ value: any[]; count?: number }> {
889+
const objectName = ast.object || '';
890+
891+
// Convert QueryAST to legacy query format
892+
const legacyQuery: any = {
893+
fields: ast.fields,
894+
filters: this.convertFilterNodeToLegacy(ast.filters),
895+
sort: ast.sort?.map((s: SortNode) => [s.field, s.order]),
896+
limit: ast.top,
897+
skip: ast.skip,
898+
};
899+
900+
// Use existing find method
901+
const results = await this.find(objectName, legacyQuery, options);
902+
903+
return {
904+
value: results,
905+
count: results.length
906+
};
907+
}
908+
909+
/**
910+
* Execute a command (DriverInterface v4.0 method)
911+
*
912+
* This method handles all mutation operations (create, update, delete)
913+
* using a unified command interface.
914+
*
915+
* @param command - The command to execute
916+
* @param options - Optional execution options
917+
* @returns Command execution result
918+
*/
919+
async executeCommand(command: Command, options?: any): Promise<CommandResult> {
920+
try {
921+
const cmdOptions = { ...options, ...command.options };
922+
923+
switch (command.type) {
924+
case 'create':
925+
if (!command.data) {
926+
throw new Error('Create command requires data');
927+
}
928+
const created = await this.create(command.object, command.data, cmdOptions);
929+
return {
930+
success: true,
931+
data: created,
932+
affected: 1
933+
};
934+
935+
case 'update':
936+
if (!command.id || !command.data) {
937+
throw new Error('Update command requires id and data');
938+
}
939+
const updated = await this.update(command.object, command.id, command.data, cmdOptions);
940+
return {
941+
success: true,
942+
data: updated,
943+
affected: 1
944+
};
945+
946+
case 'delete':
947+
if (!command.id) {
948+
throw new Error('Delete command requires id');
949+
}
950+
await this.delete(command.object, command.id, cmdOptions);
951+
return {
952+
success: true,
953+
affected: 1
954+
};
955+
956+
case 'bulkCreate':
957+
if (!command.records || !Array.isArray(command.records)) {
958+
throw new Error('BulkCreate command requires records array');
959+
}
960+
const bulkCreated = [];
961+
for (const record of command.records) {
962+
const created = await this.create(command.object, record, cmdOptions);
963+
bulkCreated.push(created);
964+
}
965+
return {
966+
success: true,
967+
data: bulkCreated,
968+
affected: command.records.length
969+
};
970+
971+
case 'bulkUpdate':
972+
if (!command.updates || !Array.isArray(command.updates)) {
973+
throw new Error('BulkUpdate command requires updates array');
974+
}
975+
const updateResults = [];
976+
for (const update of command.updates) {
977+
const result = await this.update(command.object, update.id, update.data, cmdOptions);
978+
updateResults.push(result);
979+
}
980+
return {
981+
success: true,
982+
data: updateResults,
983+
affected: command.updates.length
984+
};
985+
986+
case 'bulkDelete':
987+
if (!command.ids || !Array.isArray(command.ids)) {
988+
throw new Error('BulkDelete command requires ids array');
989+
}
990+
let deleted = 0;
991+
for (const id of command.ids) {
992+
const result = await this.delete(command.object, id, cmdOptions);
993+
if (result) deleted++;
994+
}
995+
return {
996+
success: true,
997+
affected: deleted
998+
};
999+
1000+
default:
1001+
throw new Error(`Unsupported command type: ${(command as any).type}`);
1002+
}
1003+
} catch (error: any) {
1004+
return {
1005+
success: false,
1006+
affected: 0,
1007+
error: error.message || 'Unknown error occurred'
1008+
};
1009+
}
1010+
}
1011+
1012+
/**
1013+
* Execute raw command (for compatibility)
1014+
*
1015+
* @param command - Command string or object
1016+
* @param parameters - Command parameters
1017+
* @param options - Execution options
1018+
*/
1019+
async execute(command: any, parameters?: any[], options?: any): Promise<any> {
1020+
throw new Error('Excel driver does not support raw command execution. Use executeCommand() instead.');
1021+
}
1022+
8521023
// ========== Helper Methods ==========
8531024

1025+
/**
1026+
* Convert FilterNode from QueryAST to legacy filter format.
1027+
*
1028+
* @param node - The FilterNode to convert
1029+
* @returns Legacy filter array format
1030+
*/
1031+
private convertFilterNodeToLegacy(node?: FilterNode): any {
1032+
if (!node) return undefined;
1033+
1034+
switch (node.type) {
1035+
case 'comparison':
1036+
// Convert comparison node to [field, operator, value] format
1037+
const operator = node.operator || '=';
1038+
return [[node.field, operator, node.value]];
1039+
1040+
case 'and':
1041+
// Convert AND node to array with 'and' separator
1042+
if (!node.children || node.children.length === 0) return undefined;
1043+
const andResults: any[] = [];
1044+
for (const child of node.children) {
1045+
const converted = this.convertFilterNodeToLegacy(child);
1046+
if (converted) {
1047+
if (andResults.length > 0) {
1048+
andResults.push('and');
1049+
}
1050+
andResults.push(...(Array.isArray(converted) ? converted : [converted]));
1051+
}
1052+
}
1053+
return andResults.length > 0 ? andResults : undefined;
1054+
1055+
case 'or':
1056+
// Convert OR node to array with 'or' separator
1057+
if (!node.children || node.children.length === 0) return undefined;
1058+
const orResults: any[] = [];
1059+
for (const child of node.children) {
1060+
const converted = this.convertFilterNodeToLegacy(child);
1061+
if (converted) {
1062+
if (orResults.length > 0) {
1063+
orResults.push('or');
1064+
}
1065+
orResults.push(...(Array.isArray(converted) ? converted : [converted]));
1066+
}
1067+
}
1068+
return orResults.length > 0 ? orResults : undefined;
1069+
1070+
case 'not':
1071+
// NOT is complex - we'll just process the first child for now
1072+
if (node.children && node.children.length > 0) {
1073+
return this.convertFilterNodeToLegacy(node.children[0]);
1074+
}
1075+
return undefined;
1076+
1077+
default:
1078+
return undefined;
1079+
}
1080+
}
1081+
8541082
/**
8551083
* Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
8561084
* This ensures backward compatibility while supporting the new @objectstack/spec interface.

0 commit comments

Comments
 (0)