@objectql/driver-excel 3.0.1 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -3
- package/dist/index.d.ts +112 -0
- package/dist/index.js +320 -32
- package/dist/index.js.map +1 -1
- package/jest.config.js +8 -0
- package/package.json +3 -2
- package/src/index.ts +346 -10
- package/test/index.test.ts +184 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @objectql/driver-excel
|
|
2
2
|
|
|
3
|
+
## 4.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- **Release Version 4.0.1**
|
|
8
|
+
|
|
9
|
+
This patch release includes the latest repository improvements and infrastructure updates:
|
|
10
|
+
- Added comprehensive GitHub workflows for CI/CD, testing, and quality assurance
|
|
11
|
+
- Enhanced documentation and developer experience
|
|
12
|
+
- Improved build and release processes with Changesets
|
|
13
|
+
- Added Excel driver for reading/writing Excel files as data sources
|
|
14
|
+
- Repository structure and tooling improvements
|
|
15
|
+
- Bug fixes and stability enhancements
|
|
16
|
+
|
|
17
|
+
- Updated dependencies
|
|
18
|
+
- @objectql/types@4.0.1
|
|
19
|
+
|
|
3
20
|
## 3.0.1
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
|
@@ -7,7 +24,6 @@
|
|
|
7
24
|
- 79d04e1: Patch release for January 2026 updates
|
|
8
25
|
|
|
9
26
|
This patch includes minor improvements and maintenance updates:
|
|
10
|
-
|
|
11
27
|
- Enhanced type safety across core packages
|
|
12
28
|
- Improved error handling in drivers
|
|
13
29
|
- Documentation updates
|
|
@@ -30,7 +46,6 @@
|
|
|
30
46
|
This is a coordinated major release that unifies all ObjectQL packages to version 2.0.0, establishing a synchronized versioning strategy across the entire ecosystem.
|
|
31
47
|
|
|
32
48
|
### 🎯 Key Changes
|
|
33
|
-
|
|
34
49
|
- **Unified Versioning**: All core packages now share the same version number (2.0.0)
|
|
35
50
|
- **Fixed Group Management**: Updated changeset configuration to include all @objectql packages in the fixed versioning group
|
|
36
51
|
- **Simplified Maintenance**: Future releases will automatically maintain version consistency across the entire monorepo
|
|
@@ -38,7 +53,6 @@
|
|
|
38
53
|
### 📦 Packages Included
|
|
39
54
|
|
|
40
55
|
All ObjectQL packages are now synchronized at version 2.0.0:
|
|
41
|
-
|
|
42
56
|
- Foundation: `@objectql/types`, `@objectql/core`, `@objectql/platform-node`
|
|
43
57
|
- Drivers: `@objectql/driver-sql`, `@objectql/driver-mongo`, `@objectql/driver-redis`, `@objectql/driver-fs`, `@objectql/driver-memory`, `@objectql/driver-localstorage`, `@objectql/driver-excel`, `@objectql/sdk`
|
|
44
58
|
- Runtime: `@objectql/server`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import { Data } from '@objectstack/spec';
|
|
2
|
+
type QueryAST = Data.QueryAST;
|
|
3
|
+
/**
|
|
4
|
+
* ObjectQL
|
|
5
|
+
* Copyright (c) 2026-present ObjectStack Inc.
|
|
6
|
+
*
|
|
7
|
+
* This source code is licensed under the MIT license found in the
|
|
8
|
+
* LICENSE file in the root directory of this source tree.
|
|
9
|
+
*/
|
|
1
10
|
/**
|
|
2
11
|
* Excel Driver for ObjectQL (Production-Ready)
|
|
3
12
|
*
|
|
@@ -21,6 +30,31 @@
|
|
|
21
30
|
* - Generate reports in Excel format
|
|
22
31
|
*/
|
|
23
32
|
import { Driver } from '@objectql/types';
|
|
33
|
+
/**
|
|
34
|
+
* Command interface for executeCommand method
|
|
35
|
+
*/
|
|
36
|
+
export interface Command {
|
|
37
|
+
type: 'create' | 'update' | 'delete' | 'bulkCreate' | 'bulkUpdate' | 'bulkDelete';
|
|
38
|
+
object: string;
|
|
39
|
+
data?: any;
|
|
40
|
+
id?: string | number;
|
|
41
|
+
ids?: Array<string | number>;
|
|
42
|
+
records?: any[];
|
|
43
|
+
updates?: Array<{
|
|
44
|
+
id: string | number;
|
|
45
|
+
data: any;
|
|
46
|
+
}>;
|
|
47
|
+
options?: any;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Command result interface
|
|
51
|
+
*/
|
|
52
|
+
export interface CommandResult {
|
|
53
|
+
success: boolean;
|
|
54
|
+
data?: any;
|
|
55
|
+
affected: number;
|
|
56
|
+
error?: string;
|
|
57
|
+
}
|
|
24
58
|
/**
|
|
25
59
|
* File storage mode for the Excel driver.
|
|
26
60
|
*/
|
|
@@ -55,8 +89,27 @@ export interface ExcelDriverConfig {
|
|
|
55
89
|
* in a separate worksheet, with the first row containing column headers.
|
|
56
90
|
*
|
|
57
91
|
* Uses ExcelJS library for secure Excel file operations.
|
|
92
|
+
*
|
|
93
|
+
* Implements both the legacy Driver interface from @objectql/types and
|
|
94
|
+
* the standard DriverInterface from @objectstack/spec for compatibility
|
|
95
|
+
* with the new kernel-based plugin system.
|
|
58
96
|
*/
|
|
59
97
|
export declare class ExcelDriver implements Driver {
|
|
98
|
+
readonly name = "ExcelDriver";
|
|
99
|
+
readonly version = "4.0.0";
|
|
100
|
+
readonly supports: {
|
|
101
|
+
transactions: boolean;
|
|
102
|
+
joins: boolean;
|
|
103
|
+
fullTextSearch: boolean;
|
|
104
|
+
jsonFields: boolean;
|
|
105
|
+
arrayFields: boolean;
|
|
106
|
+
queryFilters: boolean;
|
|
107
|
+
queryAggregations: boolean;
|
|
108
|
+
querySorting: boolean;
|
|
109
|
+
queryPagination: boolean;
|
|
110
|
+
queryWindowFunctions: boolean;
|
|
111
|
+
querySubqueries: boolean;
|
|
112
|
+
};
|
|
60
113
|
private config;
|
|
61
114
|
private workbook;
|
|
62
115
|
private workbooks;
|
|
@@ -70,6 +123,15 @@ export declare class ExcelDriver implements Driver {
|
|
|
70
123
|
* This must be called after construction before using the driver.
|
|
71
124
|
*/
|
|
72
125
|
init(): Promise<void>;
|
|
126
|
+
/**
|
|
127
|
+
* Connect to the database (for DriverInterface compatibility)
|
|
128
|
+
* This calls init() to load the workbook.
|
|
129
|
+
*/
|
|
130
|
+
connect(): Promise<void>;
|
|
131
|
+
/**
|
|
132
|
+
* Check database connection health
|
|
133
|
+
*/
|
|
134
|
+
checkHealth(): Promise<boolean>;
|
|
73
135
|
/**
|
|
74
136
|
* Factory method to create and initialize the driver.
|
|
75
137
|
*/
|
|
@@ -188,6 +250,55 @@ export declare class ExcelDriver implements Driver {
|
|
|
188
250
|
* Disconnect (flush any pending writes).
|
|
189
251
|
*/
|
|
190
252
|
disconnect(): Promise<void>;
|
|
253
|
+
/**
|
|
254
|
+
* Execute a query using QueryAST (DriverInterface v4.0 method)
|
|
255
|
+
*
|
|
256
|
+
* This method handles all query operations using the standard QueryAST format
|
|
257
|
+
* from @objectstack/spec. It converts the AST to the legacy query format
|
|
258
|
+
* and delegates to the existing find() method.
|
|
259
|
+
*
|
|
260
|
+
* @param ast - The query AST to execute
|
|
261
|
+
* @param options - Optional execution options
|
|
262
|
+
* @returns Query results with value array and count
|
|
263
|
+
*/
|
|
264
|
+
executeQuery(ast: QueryAST, options?: any): Promise<{
|
|
265
|
+
value: any[];
|
|
266
|
+
count?: number;
|
|
267
|
+
}>;
|
|
268
|
+
/**
|
|
269
|
+
* Execute a command (DriverInterface v4.0 method)
|
|
270
|
+
*
|
|
271
|
+
* This method handles all mutation operations (create, update, delete)
|
|
272
|
+
* using a unified command interface.
|
|
273
|
+
*
|
|
274
|
+
* @param command - The command to execute
|
|
275
|
+
* @param options - Optional execution options
|
|
276
|
+
* @returns Command execution result
|
|
277
|
+
*/
|
|
278
|
+
executeCommand(command: Command, options?: any): Promise<CommandResult>;
|
|
279
|
+
/**
|
|
280
|
+
* Execute raw command (for compatibility)
|
|
281
|
+
*
|
|
282
|
+
* @param command - Command string or object
|
|
283
|
+
* @param parameters - Command parameters
|
|
284
|
+
* @param options - Execution options
|
|
285
|
+
*/
|
|
286
|
+
execute(command: any, parameters?: any[], options?: any): Promise<any>;
|
|
287
|
+
/**
|
|
288
|
+
* Convert FilterNode from QueryAST to legacy filter format.
|
|
289
|
+
*
|
|
290
|
+
* @param node - The FilterNode to convert
|
|
291
|
+
* @returns Legacy filter array format
|
|
292
|
+
*/
|
|
293
|
+
private convertFilterNodeToLegacy;
|
|
294
|
+
/**
|
|
295
|
+
* Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
|
|
296
|
+
* This ensures backward compatibility while supporting the new @objectstack/spec interface.
|
|
297
|
+
*
|
|
298
|
+
* QueryAST format uses 'top' for limit, while UnifiedQuery uses 'limit'.
|
|
299
|
+
* QueryAST sort is array of {field, order}, while UnifiedQuery is array of [field, order].
|
|
300
|
+
*/
|
|
301
|
+
private normalizeQuery;
|
|
191
302
|
/**
|
|
192
303
|
* Apply filters to an array of records (in-memory filtering).
|
|
193
304
|
*/
|
|
@@ -213,3 +324,4 @@ export declare class ExcelDriver implements Driver {
|
|
|
213
324
|
*/
|
|
214
325
|
private generateId;
|
|
215
326
|
}
|
|
327
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Excel Driver for ObjectQL (Production-Ready)
|
|
4
|
-
*
|
|
5
|
-
* A driver for ObjectQL that reads and writes data from Excel (.xlsx) files.
|
|
6
|
-
* Each worksheet in the Excel file represents an object type, with the first row
|
|
7
|
-
* containing column headers (field names) and subsequent rows containing data.
|
|
8
|
-
*
|
|
9
|
-
* ✅ Features:
|
|
10
|
-
* - Read data from Excel files
|
|
11
|
-
* - Write data back to Excel files
|
|
12
|
-
* - Multiple sheets support (one sheet per object type)
|
|
13
|
-
* - Full CRUD operations
|
|
14
|
-
* - Query support (filters, sorting, pagination)
|
|
15
|
-
* - Automatic data type handling
|
|
16
|
-
* - Secure: Uses ExcelJS (no known vulnerabilities)
|
|
17
|
-
*
|
|
18
|
-
* Use Cases:
|
|
19
|
-
* - Import/export data from Excel spreadsheets
|
|
20
|
-
* - Use Excel as a simple database for prototyping
|
|
21
|
-
* - Data migration from Excel to other databases
|
|
22
|
-
* - Generate reports in Excel format
|
|
23
|
-
*/
|
|
24
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
25
3
|
if (k2 === undefined) k2 = k;
|
|
26
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -56,6 +34,35 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
56
34
|
})();
|
|
57
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
36
|
exports.ExcelDriver = void 0;
|
|
37
|
+
/**
|
|
38
|
+
* ObjectQL
|
|
39
|
+
* Copyright (c) 2026-present ObjectStack Inc.
|
|
40
|
+
*
|
|
41
|
+
* This source code is licensed under the MIT license found in the
|
|
42
|
+
* LICENSE file in the root directory of this source tree.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Excel Driver for ObjectQL (Production-Ready)
|
|
46
|
+
*
|
|
47
|
+
* A driver for ObjectQL that reads and writes data from Excel (.xlsx) files.
|
|
48
|
+
* Each worksheet in the Excel file represents an object type, with the first row
|
|
49
|
+
* containing column headers (field names) and subsequent rows containing data.
|
|
50
|
+
*
|
|
51
|
+
* ✅ Features:
|
|
52
|
+
* - Read data from Excel files
|
|
53
|
+
* - Write data back to Excel files
|
|
54
|
+
* - Multiple sheets support (one sheet per object type)
|
|
55
|
+
* - Full CRUD operations
|
|
56
|
+
* - Query support (filters, sorting, pagination)
|
|
57
|
+
* - Automatic data type handling
|
|
58
|
+
* - Secure: Uses ExcelJS (no known vulnerabilities)
|
|
59
|
+
*
|
|
60
|
+
* Use Cases:
|
|
61
|
+
* - Import/export data from Excel spreadsheets
|
|
62
|
+
* - Use Excel as a simple database for prototyping
|
|
63
|
+
* - Data migration from Excel to other databases
|
|
64
|
+
* - Generate reports in Excel format
|
|
65
|
+
*/
|
|
59
66
|
const types_1 = require("@objectql/types");
|
|
60
67
|
const ExcelJS = __importStar(require("exceljs"));
|
|
61
68
|
const fs = __importStar(require("fs"));
|
|
@@ -67,9 +74,29 @@ const path = __importStar(require("path"));
|
|
|
67
74
|
* in a separate worksheet, with the first row containing column headers.
|
|
68
75
|
*
|
|
69
76
|
* Uses ExcelJS library for secure Excel file operations.
|
|
77
|
+
*
|
|
78
|
+
* Implements both the legacy Driver interface from @objectql/types and
|
|
79
|
+
* the standard DriverInterface from @objectstack/spec for compatibility
|
|
80
|
+
* with the new kernel-based plugin system.
|
|
70
81
|
*/
|
|
71
82
|
class ExcelDriver {
|
|
72
83
|
constructor(config) {
|
|
84
|
+
// Driver metadata (ObjectStack-compatible)
|
|
85
|
+
this.name = 'ExcelDriver';
|
|
86
|
+
this.version = '4.0.0';
|
|
87
|
+
this.supports = {
|
|
88
|
+
transactions: false,
|
|
89
|
+
joins: false,
|
|
90
|
+
fullTextSearch: false,
|
|
91
|
+
jsonFields: true,
|
|
92
|
+
arrayFields: true,
|
|
93
|
+
queryFilters: true,
|
|
94
|
+
queryAggregations: false,
|
|
95
|
+
querySorting: true,
|
|
96
|
+
queryPagination: true,
|
|
97
|
+
queryWindowFunctions: false,
|
|
98
|
+
querySubqueries: false
|
|
99
|
+
};
|
|
73
100
|
this.config = {
|
|
74
101
|
autoSave: true,
|
|
75
102
|
createIfMissing: true,
|
|
@@ -96,6 +123,46 @@ class ExcelDriver {
|
|
|
96
123
|
async init() {
|
|
97
124
|
await this.loadWorkbook();
|
|
98
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Connect to the database (for DriverInterface compatibility)
|
|
128
|
+
* This calls init() to load the workbook.
|
|
129
|
+
*/
|
|
130
|
+
async connect() {
|
|
131
|
+
await this.init();
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Check database connection health
|
|
135
|
+
*/
|
|
136
|
+
async checkHealth() {
|
|
137
|
+
try {
|
|
138
|
+
if (this.fileStorageMode === 'single-file') {
|
|
139
|
+
// Check if file exists or can be created
|
|
140
|
+
if (!fs.existsSync(this.filePath)) {
|
|
141
|
+
if (!this.config.createIfMissing) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
// Check if directory is writable
|
|
145
|
+
const dir = path.dirname(this.filePath);
|
|
146
|
+
if (!fs.existsSync(dir)) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// Check if directory exists or can be created
|
|
154
|
+
if (!fs.existsSync(this.filePath)) {
|
|
155
|
+
if (!this.config.createIfMissing) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
99
166
|
/**
|
|
100
167
|
* Factory method to create and initialize the driver.
|
|
101
168
|
*/
|
|
@@ -459,6 +526,8 @@ class ExcelDriver {
|
|
|
459
526
|
* Find multiple records matching the query criteria.
|
|
460
527
|
*/
|
|
461
528
|
async find(objectName, query = {}, options) {
|
|
529
|
+
// Normalize query to support both legacy and QueryAST formats
|
|
530
|
+
const normalizedQuery = this.normalizeQuery(query);
|
|
462
531
|
let results = this.data.get(objectName) || [];
|
|
463
532
|
// Return empty array if no data
|
|
464
533
|
if (results.length === 0) {
|
|
@@ -467,23 +536,23 @@ class ExcelDriver {
|
|
|
467
536
|
// Deep copy to avoid mutations
|
|
468
537
|
results = results.map(r => ({ ...r }));
|
|
469
538
|
// Apply filters
|
|
470
|
-
if (
|
|
471
|
-
results = this.applyFilters(results,
|
|
539
|
+
if (normalizedQuery.filters) {
|
|
540
|
+
results = this.applyFilters(results, normalizedQuery.filters);
|
|
472
541
|
}
|
|
473
542
|
// Apply sorting
|
|
474
|
-
if (
|
|
475
|
-
results = this.applySort(results,
|
|
543
|
+
if (normalizedQuery.sort && Array.isArray(normalizedQuery.sort)) {
|
|
544
|
+
results = this.applySort(results, normalizedQuery.sort);
|
|
476
545
|
}
|
|
477
546
|
// Apply pagination
|
|
478
|
-
if (
|
|
479
|
-
results = results.slice(
|
|
547
|
+
if (normalizedQuery.skip) {
|
|
548
|
+
results = results.slice(normalizedQuery.skip);
|
|
480
549
|
}
|
|
481
|
-
if (
|
|
482
|
-
results = results.slice(0,
|
|
550
|
+
if (normalizedQuery.limit) {
|
|
551
|
+
results = results.slice(0, normalizedQuery.limit);
|
|
483
552
|
}
|
|
484
553
|
// Apply field projection
|
|
485
|
-
if (
|
|
486
|
-
results = results.map(doc => this.projectFields(doc,
|
|
554
|
+
if (normalizedQuery.fields && Array.isArray(normalizedQuery.fields)) {
|
|
555
|
+
results = results.map(doc => this.projectFields(doc, normalizedQuery.fields));
|
|
487
556
|
}
|
|
488
557
|
return results;
|
|
489
558
|
}
|
|
@@ -690,7 +759,226 @@ class ExcelDriver {
|
|
|
690
759
|
await this.save();
|
|
691
760
|
}
|
|
692
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Execute a query using QueryAST (DriverInterface v4.0 method)
|
|
764
|
+
*
|
|
765
|
+
* This method handles all query operations using the standard QueryAST format
|
|
766
|
+
* from @objectstack/spec. It converts the AST to the legacy query format
|
|
767
|
+
* and delegates to the existing find() method.
|
|
768
|
+
*
|
|
769
|
+
* @param ast - The query AST to execute
|
|
770
|
+
* @param options - Optional execution options
|
|
771
|
+
* @returns Query results with value array and count
|
|
772
|
+
*/
|
|
773
|
+
async executeQuery(ast, options) {
|
|
774
|
+
var _a;
|
|
775
|
+
const objectName = ast.object || '';
|
|
776
|
+
// Convert QueryAST to legacy query format
|
|
777
|
+
const legacyQuery = {
|
|
778
|
+
fields: ast.fields,
|
|
779
|
+
filters: this.convertFilterNodeToLegacy(ast.filters),
|
|
780
|
+
sort: (_a = ast.sort) === null || _a === void 0 ? void 0 : _a.map((s) => [s.field, s.order]),
|
|
781
|
+
limit: ast.top,
|
|
782
|
+
skip: ast.skip,
|
|
783
|
+
};
|
|
784
|
+
// Use existing find method
|
|
785
|
+
const results = await this.find(objectName, legacyQuery, options);
|
|
786
|
+
return {
|
|
787
|
+
value: results,
|
|
788
|
+
count: results.length
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Execute a command (DriverInterface v4.0 method)
|
|
793
|
+
*
|
|
794
|
+
* This method handles all mutation operations (create, update, delete)
|
|
795
|
+
* using a unified command interface.
|
|
796
|
+
*
|
|
797
|
+
* @param command - The command to execute
|
|
798
|
+
* @param options - Optional execution options
|
|
799
|
+
* @returns Command execution result
|
|
800
|
+
*/
|
|
801
|
+
async executeCommand(command, options) {
|
|
802
|
+
try {
|
|
803
|
+
const cmdOptions = { ...options, ...command.options };
|
|
804
|
+
switch (command.type) {
|
|
805
|
+
case 'create':
|
|
806
|
+
if (!command.data) {
|
|
807
|
+
throw new Error('Create command requires data');
|
|
808
|
+
}
|
|
809
|
+
const created = await this.create(command.object, command.data, cmdOptions);
|
|
810
|
+
return {
|
|
811
|
+
success: true,
|
|
812
|
+
data: created,
|
|
813
|
+
affected: 1
|
|
814
|
+
};
|
|
815
|
+
case 'update':
|
|
816
|
+
if (!command.id || !command.data) {
|
|
817
|
+
throw new Error('Update command requires id and data');
|
|
818
|
+
}
|
|
819
|
+
const updated = await this.update(command.object, command.id, command.data, cmdOptions);
|
|
820
|
+
return {
|
|
821
|
+
success: true,
|
|
822
|
+
data: updated,
|
|
823
|
+
affected: 1
|
|
824
|
+
};
|
|
825
|
+
case 'delete':
|
|
826
|
+
if (!command.id) {
|
|
827
|
+
throw new Error('Delete command requires id');
|
|
828
|
+
}
|
|
829
|
+
await this.delete(command.object, command.id, cmdOptions);
|
|
830
|
+
return {
|
|
831
|
+
success: true,
|
|
832
|
+
affected: 1
|
|
833
|
+
};
|
|
834
|
+
case 'bulkCreate':
|
|
835
|
+
if (!command.records || !Array.isArray(command.records)) {
|
|
836
|
+
throw new Error('BulkCreate command requires records array');
|
|
837
|
+
}
|
|
838
|
+
const bulkCreated = [];
|
|
839
|
+
for (const record of command.records) {
|
|
840
|
+
const created = await this.create(command.object, record, cmdOptions);
|
|
841
|
+
bulkCreated.push(created);
|
|
842
|
+
}
|
|
843
|
+
return {
|
|
844
|
+
success: true,
|
|
845
|
+
data: bulkCreated,
|
|
846
|
+
affected: command.records.length
|
|
847
|
+
};
|
|
848
|
+
case 'bulkUpdate':
|
|
849
|
+
if (!command.updates || !Array.isArray(command.updates)) {
|
|
850
|
+
throw new Error('BulkUpdate command requires updates array');
|
|
851
|
+
}
|
|
852
|
+
const updateResults = [];
|
|
853
|
+
for (const update of command.updates) {
|
|
854
|
+
const result = await this.update(command.object, update.id, update.data, cmdOptions);
|
|
855
|
+
updateResults.push(result);
|
|
856
|
+
}
|
|
857
|
+
return {
|
|
858
|
+
success: true,
|
|
859
|
+
data: updateResults,
|
|
860
|
+
affected: command.updates.length
|
|
861
|
+
};
|
|
862
|
+
case 'bulkDelete':
|
|
863
|
+
if (!command.ids || !Array.isArray(command.ids)) {
|
|
864
|
+
throw new Error('BulkDelete command requires ids array');
|
|
865
|
+
}
|
|
866
|
+
let deleted = 0;
|
|
867
|
+
for (const id of command.ids) {
|
|
868
|
+
const result = await this.delete(command.object, id, cmdOptions);
|
|
869
|
+
if (result)
|
|
870
|
+
deleted++;
|
|
871
|
+
}
|
|
872
|
+
return {
|
|
873
|
+
success: true,
|
|
874
|
+
affected: deleted
|
|
875
|
+
};
|
|
876
|
+
default:
|
|
877
|
+
throw new Error(`Unsupported command type: ${command.type}`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
catch (error) {
|
|
881
|
+
return {
|
|
882
|
+
success: false,
|
|
883
|
+
affected: 0,
|
|
884
|
+
error: error.message || 'Unknown error occurred'
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Execute raw command (for compatibility)
|
|
890
|
+
*
|
|
891
|
+
* @param command - Command string or object
|
|
892
|
+
* @param parameters - Command parameters
|
|
893
|
+
* @param options - Execution options
|
|
894
|
+
*/
|
|
895
|
+
async execute(command, parameters, options) {
|
|
896
|
+
throw new Error('Excel driver does not support raw command execution. Use executeCommand() instead.');
|
|
897
|
+
}
|
|
693
898
|
// ========== Helper Methods ==========
|
|
899
|
+
/**
|
|
900
|
+
* Convert FilterNode from QueryAST to legacy filter format.
|
|
901
|
+
*
|
|
902
|
+
* @param node - The FilterNode to convert
|
|
903
|
+
* @returns Legacy filter array format
|
|
904
|
+
*/
|
|
905
|
+
convertFilterNodeToLegacy(node) {
|
|
906
|
+
if (!node)
|
|
907
|
+
return undefined;
|
|
908
|
+
switch (node.type) {
|
|
909
|
+
case 'comparison':
|
|
910
|
+
// Convert comparison node to [field, operator, value] format
|
|
911
|
+
const operator = node.operator || '=';
|
|
912
|
+
return [[node.field, operator, node.value]];
|
|
913
|
+
case 'and':
|
|
914
|
+
// Convert AND node to array with 'and' separator
|
|
915
|
+
if (!node.children || node.children.length === 0)
|
|
916
|
+
return undefined;
|
|
917
|
+
const andResults = [];
|
|
918
|
+
for (const child of node.children) {
|
|
919
|
+
const converted = this.convertFilterNodeToLegacy(child);
|
|
920
|
+
if (converted) {
|
|
921
|
+
if (andResults.length > 0) {
|
|
922
|
+
andResults.push('and');
|
|
923
|
+
}
|
|
924
|
+
andResults.push(...(Array.isArray(converted) ? converted : [converted]));
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return andResults.length > 0 ? andResults : undefined;
|
|
928
|
+
case 'or':
|
|
929
|
+
// Convert OR node to array with 'or' separator
|
|
930
|
+
if (!node.children || node.children.length === 0)
|
|
931
|
+
return undefined;
|
|
932
|
+
const orResults = [];
|
|
933
|
+
for (const child of node.children) {
|
|
934
|
+
const converted = this.convertFilterNodeToLegacy(child);
|
|
935
|
+
if (converted) {
|
|
936
|
+
if (orResults.length > 0) {
|
|
937
|
+
orResults.push('or');
|
|
938
|
+
}
|
|
939
|
+
orResults.push(...(Array.isArray(converted) ? converted : [converted]));
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return orResults.length > 0 ? orResults : undefined;
|
|
943
|
+
case 'not':
|
|
944
|
+
// NOT is complex - we'll just process the first child for now
|
|
945
|
+
if (node.children && node.children.length > 0) {
|
|
946
|
+
return this.convertFilterNodeToLegacy(node.children[0]);
|
|
947
|
+
}
|
|
948
|
+
return undefined;
|
|
949
|
+
default:
|
|
950
|
+
return undefined;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
|
|
955
|
+
* This ensures backward compatibility while supporting the new @objectstack/spec interface.
|
|
956
|
+
*
|
|
957
|
+
* QueryAST format uses 'top' for limit, while UnifiedQuery uses 'limit'.
|
|
958
|
+
* QueryAST sort is array of {field, order}, while UnifiedQuery is array of [field, order].
|
|
959
|
+
*/
|
|
960
|
+
normalizeQuery(query) {
|
|
961
|
+
if (!query)
|
|
962
|
+
return {};
|
|
963
|
+
const normalized = { ...query };
|
|
964
|
+
// Normalize limit/top
|
|
965
|
+
if (normalized.top !== undefined && normalized.limit === undefined) {
|
|
966
|
+
normalized.limit = normalized.top;
|
|
967
|
+
}
|
|
968
|
+
// Normalize sort format
|
|
969
|
+
if (normalized.sort && Array.isArray(normalized.sort)) {
|
|
970
|
+
// Check if it's already in the array format [field, order]
|
|
971
|
+
const firstSort = normalized.sort[0];
|
|
972
|
+
if (firstSort && typeof firstSort === 'object' && !Array.isArray(firstSort)) {
|
|
973
|
+
// Convert from QueryAST format {field, order} to internal format [field, order]
|
|
974
|
+
normalized.sort = normalized.sort.map((item) => [
|
|
975
|
+
item.field,
|
|
976
|
+
item.order || item.direction || item.dir || 'asc'
|
|
977
|
+
]);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return normalized;
|
|
981
|
+
}
|
|
694
982
|
/**
|
|
695
983
|
* Apply filters to an array of records (in-memory filtering).
|
|
696
984
|
*/
|