Skip to content

Commit 271b9f8

Browse files
authored
Merge pull request #161 from objectstack-ai/copilot/refactor-remaining-drivers
2 parents 2780f2f + 1b19db7 commit 271b9f8

File tree

9 files changed

+660
-59
lines changed

9 files changed

+660
-59
lines changed

packages/drivers/excel/src/index.ts

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
7680
export 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
*/

packages/drivers/fs/src/index.ts

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
* A persistent file-based driver for ObjectQL that stores data in JSON files.
1313
* Each object type is stored in a separate JSON file for easy inspection and backup.
1414
*
15+
* Implements both the legacy Driver interface from @objectql/types and
16+
* the standard DriverInterface from @objectstack/spec for compatibility
17+
* with the new kernel-based plugin system.
18+
*
1519
* ✅ Production-ready features:
1620
* - Persistent storage with JSON files
1721
* - One file per table/object (e.g., users.json, projects.json)
@@ -56,6 +60,17 @@ export interface FileSystemDriverConfig {
5660
* - Content: Array of records `[{id: "1", ...}, {id: "2", ...}]`
5761
*/
5862
export class FileSystemDriver implements Driver {
63+
// Driver metadata (ObjectStack-compatible)
64+
public readonly name = 'FileSystemDriver';
65+
public readonly version = '3.0.1';
66+
public readonly supports = {
67+
transactions: false,
68+
joins: false,
69+
fullTextSearch: false,
70+
jsonFields: true,
71+
arrayFields: true
72+
};
73+
5974
private config: FileSystemDriverConfig;
6075
private idCounters: Map<string, number>;
6176
private cache: Map<string, any[]>;
@@ -81,6 +96,31 @@ export class FileSystemDriver implements Driver {
8196
}
8297
}
8398

99+
/**
100+
* Connect to the database (for DriverInterface compatibility)
101+
* This is a no-op for filesystem driver as there's no external connection.
102+
*/
103+
async connect(): Promise<void> {
104+
// No-op: FileSystem driver doesn't need connection
105+
}
106+
107+
/**
108+
* Check database connection health
109+
*/
110+
async checkHealth(): Promise<boolean> {
111+
try {
112+
// Check if data directory is accessible
113+
if (!fs.existsSync(this.config.dataDir)) {
114+
return false;
115+
}
116+
// Try to read directory
117+
fs.readdirSync(this.config.dataDir);
118+
return true;
119+
} catch (error) {
120+
return false;
121+
}
122+
}
123+
84124
/**
85125
* Load initial data into the store.
86126
*/
@@ -217,29 +257,31 @@ export class FileSystemDriver implements Driver {
217257
* Find multiple records matching the query criteria.
218258
*/
219259
async find(objectName: string, query: any = {}, options?: any): Promise<any[]> {
260+
// Normalize query to support both legacy and QueryAST formats
261+
const normalizedQuery = this.normalizeQuery(query);
220262
let results = this.loadRecords(objectName);
221263

222264
// Apply filters
223-
if (query.filters) {
224-
results = this.applyFilters(results, query.filters);
265+
if (normalizedQuery.filters) {
266+
results = this.applyFilters(results, normalizedQuery.filters);
225267
}
226268

227269
// Apply sorting
228-
if (query.sort && Array.isArray(query.sort)) {
229-
results = this.applySort(results, query.sort);
270+
if (normalizedQuery.sort && Array.isArray(normalizedQuery.sort)) {
271+
results = this.applySort(results, normalizedQuery.sort);
230272
}
231273

232274
// Apply pagination
233-
if (query.skip) {
234-
results = results.slice(query.skip);
275+
if (normalizedQuery.skip) {
276+
results = results.slice(normalizedQuery.skip);
235277
}
236-
if (query.limit) {
237-
results = results.slice(0, query.limit);
278+
if (normalizedQuery.limit) {
279+
results = results.slice(0, normalizedQuery.limit);
238280
}
239281

240282
// Apply field projection
241-
if (query.fields && Array.isArray(query.fields)) {
242-
results = results.map(doc => this.projectFields(doc, query.fields));
283+
if (normalizedQuery.fields && Array.isArray(normalizedQuery.fields)) {
284+
results = results.map(doc => this.projectFields(doc, normalizedQuery.fields));
243285
}
244286

245287
// Return deep copies to prevent external modifications
@@ -530,6 +572,39 @@ export class FileSystemDriver implements Driver {
530572

531573
// ========== Helper Methods ==========
532574

575+
/**
576+
* Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
577+
* This ensures backward compatibility while supporting the new @objectstack/spec interface.
578+
*
579+
* QueryAST format uses 'top' for limit, while UnifiedQuery uses 'limit'.
580+
* QueryAST sort is array of {field, order}, while UnifiedQuery is array of [field, order].
581+
*/
582+
private normalizeQuery(query: any): any {
583+
if (!query) return {};
584+
585+
const normalized: any = { ...query };
586+
587+
// Normalize limit/top
588+
if (normalized.top !== undefined && normalized.limit === undefined) {
589+
normalized.limit = normalized.top;
590+
}
591+
592+
// Normalize sort format
593+
if (normalized.sort && Array.isArray(normalized.sort)) {
594+
// Check if it's already in the array format [field, order]
595+
const firstSort = normalized.sort[0];
596+
if (firstSort && typeof firstSort === 'object' && !Array.isArray(firstSort)) {
597+
// Convert from QueryAST format {field, order} to internal format [field, order]
598+
normalized.sort = normalized.sort.map((item: any) => [
599+
item.field,
600+
item.order || item.direction || item.dir || 'asc'
601+
]);
602+
}
603+
}
604+
605+
return normalized;
606+
}
607+
533608
/**
534609
* Apply filters to an array of records.
535610
*

0 commit comments

Comments
 (0)