@objectql/driver-excel 3.0.0 → 4.0.0

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 CHANGED
@@ -1,5 +1,26 @@
1
1
  # @objectql/driver-excel
2
2
 
3
+ ## 3.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 79d04e1: Patch release for January 2026 updates
8
+
9
+ This patch includes minor improvements and maintenance updates:
10
+
11
+ - Enhanced type safety across core packages
12
+ - Improved error handling in drivers
13
+ - Documentation updates
14
+ - Performance optimizations
15
+
16
+ - faeef39: Release version 1.9.2 with latest improvements and bug fixes
17
+
18
+ This patch release includes stability improvements and bug fixes backported from the development branch.
19
+
20
+ - Updated dependencies [79d04e1]
21
+ - Updated dependencies [faeef39]
22
+ - @objectql/types@3.0.1
23
+
3
24
  ## 3.0.0
4
25
 
5
26
  ### Major Changes
@@ -49,6 +70,17 @@
49
70
  - Updated dependencies [38b01f4]
50
71
  - @objectql/types@3.0.0
51
72
 
73
+ ## 1.9.2
74
+
75
+ ### Patch Changes
76
+
77
+ - Release version 1.9.2 with latest improvements and bug fixes
78
+
79
+ This patch release includes stability improvements and bug fixes backported from the development branch.
80
+
81
+ - Updated dependencies
82
+ - @objectql/types@1.9.2
83
+
52
84
  ## 0.2.1
53
85
 
54
86
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,3 +1,10 @@
1
+ /**
2
+ * ObjectQL
3
+ * Copyright (c) 2026-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
1
8
  /**
2
9
  * Excel Driver for ObjectQL (Production-Ready)
3
10
  *
@@ -21,6 +28,32 @@
21
28
  * - Generate reports in Excel format
22
29
  */
23
30
  import { Driver } from '@objectql/types';
31
+ import { DriverInterface, QueryAST } from '@objectstack/spec';
32
+ /**
33
+ * Command interface for executeCommand method
34
+ */
35
+ export interface Command {
36
+ type: 'create' | 'update' | 'delete' | 'bulkCreate' | 'bulkUpdate' | 'bulkDelete';
37
+ object: string;
38
+ data?: any;
39
+ id?: string | number;
40
+ ids?: Array<string | number>;
41
+ records?: any[];
42
+ updates?: Array<{
43
+ id: string | number;
44
+ data: any;
45
+ }>;
46
+ options?: any;
47
+ }
48
+ /**
49
+ * Command result interface
50
+ */
51
+ export interface CommandResult {
52
+ success: boolean;
53
+ data?: any;
54
+ affected: number;
55
+ error?: string;
56
+ }
24
57
  /**
25
58
  * File storage mode for the Excel driver.
26
59
  */
@@ -55,8 +88,21 @@ export interface ExcelDriverConfig {
55
88
  * in a separate worksheet, with the first row containing column headers.
56
89
  *
57
90
  * Uses ExcelJS library for secure Excel file operations.
91
+ *
92
+ * Implements both the legacy Driver interface from @objectql/types and
93
+ * the standard DriverInterface from @objectstack/spec for compatibility
94
+ * with the new kernel-based plugin system.
58
95
  */
59
- export declare class ExcelDriver implements Driver {
96
+ export declare class ExcelDriver implements Driver, DriverInterface {
97
+ readonly name = "ExcelDriver";
98
+ readonly version = "4.0.0";
99
+ readonly supports: {
100
+ transactions: boolean;
101
+ joins: boolean;
102
+ fullTextSearch: boolean;
103
+ jsonFields: boolean;
104
+ arrayFields: boolean;
105
+ };
60
106
  private config;
61
107
  private workbook;
62
108
  private workbooks;
@@ -70,6 +116,15 @@ export declare class ExcelDriver implements Driver {
70
116
  * This must be called after construction before using the driver.
71
117
  */
72
118
  init(): Promise<void>;
119
+ /**
120
+ * Connect to the database (for DriverInterface compatibility)
121
+ * This calls init() to load the workbook.
122
+ */
123
+ connect(): Promise<void>;
124
+ /**
125
+ * Check database connection health
126
+ */
127
+ checkHealth(): Promise<boolean>;
73
128
  /**
74
129
  * Factory method to create and initialize the driver.
75
130
  */
@@ -188,6 +243,55 @@ export declare class ExcelDriver implements Driver {
188
243
  * Disconnect (flush any pending writes).
189
244
  */
190
245
  disconnect(): Promise<void>;
246
+ /**
247
+ * Execute a query using QueryAST (DriverInterface v4.0 method)
248
+ *
249
+ * This method handles all query operations using the standard QueryAST format
250
+ * from @objectstack/spec. It converts the AST to the legacy query format
251
+ * and delegates to the existing find() method.
252
+ *
253
+ * @param ast - The query AST to execute
254
+ * @param options - Optional execution options
255
+ * @returns Query results with value array and count
256
+ */
257
+ executeQuery(ast: QueryAST, options?: any): Promise<{
258
+ value: any[];
259
+ count?: number;
260
+ }>;
261
+ /**
262
+ * Execute a command (DriverInterface v4.0 method)
263
+ *
264
+ * This method handles all mutation operations (create, update, delete)
265
+ * using a unified command interface.
266
+ *
267
+ * @param command - The command to execute
268
+ * @param options - Optional execution options
269
+ * @returns Command execution result
270
+ */
271
+ executeCommand(command: Command, options?: any): Promise<CommandResult>;
272
+ /**
273
+ * Execute raw command (for compatibility)
274
+ *
275
+ * @param command - Command string or object
276
+ * @param parameters - Command parameters
277
+ * @param options - Execution options
278
+ */
279
+ execute(command: any, parameters?: any[], options?: any): Promise<any>;
280
+ /**
281
+ * Convert FilterNode from QueryAST to legacy filter format.
282
+ *
283
+ * @param node - The FilterNode to convert
284
+ * @returns Legacy filter array format
285
+ */
286
+ private convertFilterNodeToLegacy;
287
+ /**
288
+ * Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
289
+ * This ensures backward compatibility while supporting the new @objectstack/spec interface.
290
+ *
291
+ * QueryAST format uses 'top' for limit, while UnifiedQuery uses 'limit'.
292
+ * QueryAST sort is array of {field, order}, while UnifiedQuery is array of [field, order].
293
+ */
294
+ private normalizeQuery;
191
295
  /**
192
296
  * Apply filters to an array of records (in-memory filtering).
193
297
  */
package/dist/index.js CHANGED
@@ -1,25 +1,10 @@
1
1
  "use strict";
2
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)
3
+ * ObjectQL
4
+ * Copyright (c) 2026-present ObjectStack Inc.
17
5
  *
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
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
23
8
  */
24
9
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
25
10
  if (k2 === undefined) k2 = k;
@@ -56,6 +41,28 @@ var __importStar = (this && this.__importStar) || (function () {
56
41
  })();
57
42
  Object.defineProperty(exports, "__esModule", { value: true });
58
43
  exports.ExcelDriver = void 0;
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,23 @@ 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
+ };
73
94
  this.config = {
74
95
  autoSave: true,
75
96
  createIfMissing: true,
@@ -96,6 +117,46 @@ class ExcelDriver {
96
117
  async init() {
97
118
  await this.loadWorkbook();
98
119
  }
120
+ /**
121
+ * Connect to the database (for DriverInterface compatibility)
122
+ * This calls init() to load the workbook.
123
+ */
124
+ async connect() {
125
+ await this.init();
126
+ }
127
+ /**
128
+ * Check database connection health
129
+ */
130
+ async checkHealth() {
131
+ try {
132
+ if (this.fileStorageMode === 'single-file') {
133
+ // Check if file exists or can be created
134
+ if (!fs.existsSync(this.filePath)) {
135
+ if (!this.config.createIfMissing) {
136
+ return false;
137
+ }
138
+ // Check if directory is writable
139
+ const dir = path.dirname(this.filePath);
140
+ if (!fs.existsSync(dir)) {
141
+ return false;
142
+ }
143
+ }
144
+ return true;
145
+ }
146
+ else {
147
+ // Check if directory exists or can be created
148
+ if (!fs.existsSync(this.filePath)) {
149
+ if (!this.config.createIfMissing) {
150
+ return false;
151
+ }
152
+ }
153
+ return true;
154
+ }
155
+ }
156
+ catch (error) {
157
+ return false;
158
+ }
159
+ }
99
160
  /**
100
161
  * Factory method to create and initialize the driver.
101
162
  */
@@ -459,6 +520,8 @@ class ExcelDriver {
459
520
  * Find multiple records matching the query criteria.
460
521
  */
461
522
  async find(objectName, query = {}, options) {
523
+ // Normalize query to support both legacy and QueryAST formats
524
+ const normalizedQuery = this.normalizeQuery(query);
462
525
  let results = this.data.get(objectName) || [];
463
526
  // Return empty array if no data
464
527
  if (results.length === 0) {
@@ -467,23 +530,23 @@ class ExcelDriver {
467
530
  // Deep copy to avoid mutations
468
531
  results = results.map(r => ({ ...r }));
469
532
  // Apply filters
470
- if (query.filters) {
471
- results = this.applyFilters(results, query.filters);
533
+ if (normalizedQuery.filters) {
534
+ results = this.applyFilters(results, normalizedQuery.filters);
472
535
  }
473
536
  // Apply sorting
474
- if (query.sort && Array.isArray(query.sort)) {
475
- results = this.applySort(results, query.sort);
537
+ if (normalizedQuery.sort && Array.isArray(normalizedQuery.sort)) {
538
+ results = this.applySort(results, normalizedQuery.sort);
476
539
  }
477
540
  // Apply pagination
478
- if (query.skip) {
479
- results = results.slice(query.skip);
541
+ if (normalizedQuery.skip) {
542
+ results = results.slice(normalizedQuery.skip);
480
543
  }
481
- if (query.limit) {
482
- results = results.slice(0, query.limit);
544
+ if (normalizedQuery.limit) {
545
+ results = results.slice(0, normalizedQuery.limit);
483
546
  }
484
547
  // Apply field projection
485
- if (query.fields && Array.isArray(query.fields)) {
486
- results = results.map(doc => this.projectFields(doc, query.fields));
548
+ if (normalizedQuery.fields && Array.isArray(normalizedQuery.fields)) {
549
+ results = results.map(doc => this.projectFields(doc, normalizedQuery.fields));
487
550
  }
488
551
  return results;
489
552
  }
@@ -690,7 +753,226 @@ class ExcelDriver {
690
753
  await this.save();
691
754
  }
692
755
  }
756
+ /**
757
+ * Execute a query using QueryAST (DriverInterface v4.0 method)
758
+ *
759
+ * This method handles all query operations using the standard QueryAST format
760
+ * from @objectstack/spec. It converts the AST to the legacy query format
761
+ * and delegates to the existing find() method.
762
+ *
763
+ * @param ast - The query AST to execute
764
+ * @param options - Optional execution options
765
+ * @returns Query results with value array and count
766
+ */
767
+ async executeQuery(ast, options) {
768
+ var _a;
769
+ const objectName = ast.object || '';
770
+ // Convert QueryAST to legacy query format
771
+ const legacyQuery = {
772
+ fields: ast.fields,
773
+ filters: this.convertFilterNodeToLegacy(ast.filters),
774
+ sort: (_a = ast.sort) === null || _a === void 0 ? void 0 : _a.map((s) => [s.field, s.order]),
775
+ limit: ast.top,
776
+ skip: ast.skip,
777
+ };
778
+ // Use existing find method
779
+ const results = await this.find(objectName, legacyQuery, options);
780
+ return {
781
+ value: results,
782
+ count: results.length
783
+ };
784
+ }
785
+ /**
786
+ * Execute a command (DriverInterface v4.0 method)
787
+ *
788
+ * This method handles all mutation operations (create, update, delete)
789
+ * using a unified command interface.
790
+ *
791
+ * @param command - The command to execute
792
+ * @param options - Optional execution options
793
+ * @returns Command execution result
794
+ */
795
+ async executeCommand(command, options) {
796
+ try {
797
+ const cmdOptions = { ...options, ...command.options };
798
+ switch (command.type) {
799
+ case 'create':
800
+ if (!command.data) {
801
+ throw new Error('Create command requires data');
802
+ }
803
+ const created = await this.create(command.object, command.data, cmdOptions);
804
+ return {
805
+ success: true,
806
+ data: created,
807
+ affected: 1
808
+ };
809
+ case 'update':
810
+ if (!command.id || !command.data) {
811
+ throw new Error('Update command requires id and data');
812
+ }
813
+ const updated = await this.update(command.object, command.id, command.data, cmdOptions);
814
+ return {
815
+ success: true,
816
+ data: updated,
817
+ affected: 1
818
+ };
819
+ case 'delete':
820
+ if (!command.id) {
821
+ throw new Error('Delete command requires id');
822
+ }
823
+ await this.delete(command.object, command.id, cmdOptions);
824
+ return {
825
+ success: true,
826
+ affected: 1
827
+ };
828
+ case 'bulkCreate':
829
+ if (!command.records || !Array.isArray(command.records)) {
830
+ throw new Error('BulkCreate command requires records array');
831
+ }
832
+ const bulkCreated = [];
833
+ for (const record of command.records) {
834
+ const created = await this.create(command.object, record, cmdOptions);
835
+ bulkCreated.push(created);
836
+ }
837
+ return {
838
+ success: true,
839
+ data: bulkCreated,
840
+ affected: command.records.length
841
+ };
842
+ case 'bulkUpdate':
843
+ if (!command.updates || !Array.isArray(command.updates)) {
844
+ throw new Error('BulkUpdate command requires updates array');
845
+ }
846
+ const updateResults = [];
847
+ for (const update of command.updates) {
848
+ const result = await this.update(command.object, update.id, update.data, cmdOptions);
849
+ updateResults.push(result);
850
+ }
851
+ return {
852
+ success: true,
853
+ data: updateResults,
854
+ affected: command.updates.length
855
+ };
856
+ case 'bulkDelete':
857
+ if (!command.ids || !Array.isArray(command.ids)) {
858
+ throw new Error('BulkDelete command requires ids array');
859
+ }
860
+ let deleted = 0;
861
+ for (const id of command.ids) {
862
+ const result = await this.delete(command.object, id, cmdOptions);
863
+ if (result)
864
+ deleted++;
865
+ }
866
+ return {
867
+ success: true,
868
+ affected: deleted
869
+ };
870
+ default:
871
+ throw new Error(`Unsupported command type: ${command.type}`);
872
+ }
873
+ }
874
+ catch (error) {
875
+ return {
876
+ success: false,
877
+ affected: 0,
878
+ error: error.message || 'Unknown error occurred'
879
+ };
880
+ }
881
+ }
882
+ /**
883
+ * Execute raw command (for compatibility)
884
+ *
885
+ * @param command - Command string or object
886
+ * @param parameters - Command parameters
887
+ * @param options - Execution options
888
+ */
889
+ async execute(command, parameters, options) {
890
+ throw new Error('Excel driver does not support raw command execution. Use executeCommand() instead.');
891
+ }
693
892
  // ========== Helper Methods ==========
893
+ /**
894
+ * Convert FilterNode from QueryAST to legacy filter format.
895
+ *
896
+ * @param node - The FilterNode to convert
897
+ * @returns Legacy filter array format
898
+ */
899
+ convertFilterNodeToLegacy(node) {
900
+ if (!node)
901
+ return undefined;
902
+ switch (node.type) {
903
+ case 'comparison':
904
+ // Convert comparison node to [field, operator, value] format
905
+ const operator = node.operator || '=';
906
+ return [[node.field, operator, node.value]];
907
+ case 'and':
908
+ // Convert AND node to array with 'and' separator
909
+ if (!node.children || node.children.length === 0)
910
+ return undefined;
911
+ const andResults = [];
912
+ for (const child of node.children) {
913
+ const converted = this.convertFilterNodeToLegacy(child);
914
+ if (converted) {
915
+ if (andResults.length > 0) {
916
+ andResults.push('and');
917
+ }
918
+ andResults.push(...(Array.isArray(converted) ? converted : [converted]));
919
+ }
920
+ }
921
+ return andResults.length > 0 ? andResults : undefined;
922
+ case 'or':
923
+ // Convert OR node to array with 'or' separator
924
+ if (!node.children || node.children.length === 0)
925
+ return undefined;
926
+ const orResults = [];
927
+ for (const child of node.children) {
928
+ const converted = this.convertFilterNodeToLegacy(child);
929
+ if (converted) {
930
+ if (orResults.length > 0) {
931
+ orResults.push('or');
932
+ }
933
+ orResults.push(...(Array.isArray(converted) ? converted : [converted]));
934
+ }
935
+ }
936
+ return orResults.length > 0 ? orResults : undefined;
937
+ case 'not':
938
+ // NOT is complex - we'll just process the first child for now
939
+ if (node.children && node.children.length > 0) {
940
+ return this.convertFilterNodeToLegacy(node.children[0]);
941
+ }
942
+ return undefined;
943
+ default:
944
+ return undefined;
945
+ }
946
+ }
947
+ /**
948
+ * Normalizes query format to support both legacy UnifiedQuery and QueryAST formats.
949
+ * This ensures backward compatibility while supporting the new @objectstack/spec interface.
950
+ *
951
+ * QueryAST format uses 'top' for limit, while UnifiedQuery uses 'limit'.
952
+ * QueryAST sort is array of {field, order}, while UnifiedQuery is array of [field, order].
953
+ */
954
+ normalizeQuery(query) {
955
+ if (!query)
956
+ return {};
957
+ const normalized = { ...query };
958
+ // Normalize limit/top
959
+ if (normalized.top !== undefined && normalized.limit === undefined) {
960
+ normalized.limit = normalized.top;
961
+ }
962
+ // Normalize sort format
963
+ if (normalized.sort && Array.isArray(normalized.sort)) {
964
+ // Check if it's already in the array format [field, order]
965
+ const firstSort = normalized.sort[0];
966
+ if (firstSort && typeof firstSort === 'object' && !Array.isArray(firstSort)) {
967
+ // Convert from QueryAST format {field, order} to internal format [field, order]
968
+ normalized.sort = normalized.sort.map((item) => [
969
+ item.field,
970
+ item.order || item.direction || item.dir || 'asc'
971
+ ]);
972
+ }
973
+ }
974
+ return normalized;
975
+ }
694
976
  /**
695
977
  * Apply filters to an array of records (in-memory filtering).
696
978
  */