@objectql/core 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.
Files changed (96) hide show
  1. package/CHANGELOG.md +17 -3
  2. package/README.md +31 -9
  3. package/dist/ai-agent.d.ts +4 -3
  4. package/dist/ai-agent.js +10 -3
  5. package/dist/ai-agent.js.map +1 -1
  6. package/dist/app.d.ts +29 -6
  7. package/dist/app.js +117 -58
  8. package/dist/app.js.map +1 -1
  9. package/dist/formula-engine.d.ts +7 -0
  10. package/dist/formula-engine.js +9 -2
  11. package/dist/formula-engine.js.map +1 -1
  12. package/dist/formula-plugin.d.ts +52 -0
  13. package/dist/formula-plugin.js +107 -0
  14. package/dist/formula-plugin.js.map +1 -0
  15. package/dist/index.d.ts +16 -3
  16. package/dist/index.js +14 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/plugin.d.ts +89 -0
  19. package/dist/plugin.js +136 -0
  20. package/dist/plugin.js.map +1 -0
  21. package/dist/query/filter-translator.d.ts +39 -0
  22. package/dist/query/filter-translator.js +135 -0
  23. package/dist/query/filter-translator.js.map +1 -0
  24. package/dist/query/index.d.ts +22 -0
  25. package/dist/query/index.js +39 -0
  26. package/dist/query/index.js.map +1 -0
  27. package/dist/query/query-analyzer.d.ts +188 -0
  28. package/dist/query/query-analyzer.js +349 -0
  29. package/dist/query/query-analyzer.js.map +1 -0
  30. package/dist/query/query-builder.d.ts +29 -0
  31. package/dist/query/query-builder.js +71 -0
  32. package/dist/query/query-builder.js.map +1 -0
  33. package/dist/query/query-service.d.ts +152 -0
  34. package/dist/query/query-service.js +268 -0
  35. package/dist/query/query-service.js.map +1 -0
  36. package/dist/repository.d.ts +23 -2
  37. package/dist/repository.js +81 -14
  38. package/dist/repository.js.map +1 -1
  39. package/dist/util.d.ts +7 -0
  40. package/dist/util.js +18 -3
  41. package/dist/util.js.map +1 -1
  42. package/dist/validator-plugin.d.ts +56 -0
  43. package/dist/validator-plugin.js +106 -0
  44. package/dist/validator-plugin.js.map +1 -0
  45. package/dist/validator.d.ts +7 -0
  46. package/dist/validator.js +10 -8
  47. package/dist/validator.js.map +1 -1
  48. package/jest.config.js +16 -0
  49. package/package.json +7 -5
  50. package/src/ai-agent.ts +8 -0
  51. package/src/app.ts +136 -72
  52. package/src/formula-engine.ts +8 -0
  53. package/src/formula-plugin.ts +141 -0
  54. package/src/index.ts +28 -3
  55. package/src/plugin.ts +224 -0
  56. package/src/query/filter-translator.ts +148 -0
  57. package/src/query/index.ts +24 -0
  58. package/src/query/query-analyzer.ts +537 -0
  59. package/src/query/query-builder.ts +81 -0
  60. package/src/query/query-service.ts +393 -0
  61. package/src/repository.ts +101 -18
  62. package/src/util.ts +19 -3
  63. package/src/validator-plugin.ts +140 -0
  64. package/src/validator.ts +12 -5
  65. package/test/__mocks__/@objectstack/runtime.ts +255 -0
  66. package/test/app.test.ts +23 -35
  67. package/test/filter-syntax.test.ts +233 -0
  68. package/test/formula-engine.test.ts +8 -0
  69. package/test/formula-integration.test.ts +8 -0
  70. package/test/formula-plugin.test.ts +197 -0
  71. package/test/introspection.test.ts +8 -0
  72. package/test/mock-driver.ts +8 -0
  73. package/test/plugin-integration.test.ts +213 -0
  74. package/test/repository-validation.test.ts +8 -0
  75. package/test/repository.test.ts +8 -0
  76. package/test/util.test.ts +9 -1
  77. package/test/utils.ts +8 -0
  78. package/test/validator-plugin.test.ts +126 -0
  79. package/test/validator.test.ts +8 -0
  80. package/tsconfig.json +8 -0
  81. package/tsconfig.tsbuildinfo +1 -1
  82. package/dist/action.d.ts +0 -7
  83. package/dist/action.js +0 -23
  84. package/dist/action.js.map +0 -1
  85. package/dist/hook.d.ts +0 -8
  86. package/dist/hook.js +0 -25
  87. package/dist/hook.js.map +0 -1
  88. package/dist/object.d.ts +0 -3
  89. package/dist/object.js +0 -28
  90. package/dist/object.js.map +0 -1
  91. package/src/action.ts +0 -40
  92. package/src/hook.ts +0 -42
  93. package/src/object.ts +0 -26
  94. package/test/action.test.ts +0 -276
  95. package/test/hook.test.ts +0 -343
  96. package/test/object.test.ts +0 -183
package/src/plugin.ts ADDED
@@ -0,0 +1,224 @@
1
+ /**
2
+ * ObjectQL Plugin
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
+ */
8
+
9
+ import type { RuntimePlugin, RuntimeContext } from '@objectql/runtime';
10
+ import type { ObjectStackKernel } from '@objectql/runtime';
11
+ import { ValidatorPlugin, ValidatorPluginConfig } from './validator-plugin';
12
+ import { FormulaPlugin, FormulaPluginConfig } from './formula-plugin';
13
+ import { QueryService } from './query/query-service';
14
+ import { QueryAnalyzer } from './query/query-analyzer';
15
+ import type { Driver } from '@objectql/types';
16
+
17
+ /**
18
+ * Extended ObjectStack Kernel with ObjectQL services
19
+ */
20
+ interface ExtendedKernel extends ObjectStackKernel {
21
+ queryService?: QueryService;
22
+ queryAnalyzer?: QueryAnalyzer;
23
+ }
24
+
25
+ /**
26
+ * Configuration for the ObjectQL Plugin
27
+ */
28
+ export interface ObjectQLPluginConfig {
29
+ /**
30
+ * Enable repository pattern for data access
31
+ * @default true
32
+ */
33
+ enableRepository?: boolean;
34
+
35
+ /**
36
+ * Enable validation engine
37
+ * @default true
38
+ */
39
+ enableValidator?: boolean;
40
+
41
+ /**
42
+ * Validator plugin configuration
43
+ * Only used if enableValidator is not false
44
+ */
45
+ validatorConfig?: ValidatorPluginConfig;
46
+
47
+ /**
48
+ * Enable formula engine
49
+ * @default true
50
+ */
51
+ enableFormulas?: boolean;
52
+
53
+ /**
54
+ * Formula plugin configuration
55
+ * Only used if enableFormulas is not false
56
+ */
57
+ formulaConfig?: FormulaPluginConfig;
58
+
59
+ /**
60
+ * Enable AI integration
61
+ * @default true
62
+ */
63
+ enableAI?: boolean;
64
+
65
+ /**
66
+ * Enable query service and analyzer
67
+ * @default true
68
+ */
69
+ enableQueryService?: boolean;
70
+
71
+ /**
72
+ * Datasources for query service
73
+ * Required if enableQueryService is true
74
+ */
75
+ datasources?: Record<string, Driver>;
76
+ }
77
+
78
+ /**
79
+ * ObjectQL Plugin
80
+ *
81
+ * Implements the RuntimePlugin interface from @objectql/runtime
82
+ * to provide ObjectQL's enhanced features (Repository, Validator, Formula, AI)
83
+ * on top of the ObjectStack kernel.
84
+ */
85
+ export class ObjectQLPlugin implements RuntimePlugin {
86
+ name = '@objectql/core';
87
+ version = '4.0.0';
88
+
89
+ constructor(private config: ObjectQLPluginConfig = {}) {
90
+ // Set defaults
91
+ this.config = {
92
+ enableRepository: true,
93
+ enableValidator: true,
94
+ enableFormulas: true,
95
+ enableAI: true,
96
+ enableQueryService: true,
97
+ ...config
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Install the plugin into the kernel
103
+ * This is called during kernel initialization
104
+ */
105
+ async install(ctx: RuntimeContext): Promise<void> {
106
+ console.log(`[${this.name}] Installing plugin...`);
107
+
108
+ const kernel = ctx.engine as ExtendedKernel;
109
+
110
+ // Register QueryService and QueryAnalyzer if enabled
111
+ if (this.config.enableQueryService !== false && this.config.datasources) {
112
+ const queryService = new QueryService(
113
+ this.config.datasources,
114
+ kernel.metadata
115
+ );
116
+ kernel.queryService = queryService;
117
+
118
+ const queryAnalyzer = new QueryAnalyzer(
119
+ queryService,
120
+ kernel.metadata
121
+ );
122
+ kernel.queryAnalyzer = queryAnalyzer;
123
+
124
+ console.log(`[${this.name}] QueryService and QueryAnalyzer registered`);
125
+ }
126
+
127
+ // Register components based on configuration
128
+ if (this.config.enableRepository !== false) {
129
+ await this.registerRepository(ctx.engine);
130
+ }
131
+
132
+ // Install validator plugin if enabled
133
+ if (this.config.enableValidator !== false) {
134
+ const validatorPlugin = new ValidatorPlugin(this.config.validatorConfig || {});
135
+ await validatorPlugin.install(ctx);
136
+ }
137
+
138
+ // Install formula plugin if enabled
139
+ if (this.config.enableFormulas !== false) {
140
+ const formulaPlugin = new FormulaPlugin(this.config.formulaConfig || {});
141
+ await formulaPlugin.install(ctx);
142
+ }
143
+
144
+ if (this.config.enableAI !== false) {
145
+ await this.registerAI(ctx.engine);
146
+ }
147
+
148
+ console.log(`[${this.name}] Plugin installed successfully`);
149
+ }
150
+
151
+ /**
152
+ * Called when the kernel starts
153
+ * This is the initialization phase
154
+ */
155
+ async onStart(ctx: RuntimeContext): Promise<void> {
156
+ console.log(`[${this.name}] Starting plugin...`);
157
+ // Additional startup logic can be added here
158
+ }
159
+
160
+ /**
161
+ * Register the Repository pattern
162
+ * @private
163
+ */
164
+ private async registerRepository(kernel: ObjectStackKernel): Promise<void> {
165
+ if (!this.config.datasources) {
166
+ console.log(`[${this.name}] No datasources configured, skipping repository registration`);
167
+ return;
168
+ }
169
+
170
+ const datasources = this.config.datasources;
171
+
172
+ // Helper function to get the driver for an object
173
+ const getDriver = (objectName: string): Driver => {
174
+ const objectConfig = kernel.metadata.get<any>('object', objectName);
175
+ const datasourceName = objectConfig?.datasource || 'default';
176
+ const driver = datasources[datasourceName];
177
+ if (!driver) {
178
+ throw new Error(`Datasource '${datasourceName}' not found for object '${objectName}'`);
179
+ }
180
+ return driver;
181
+ };
182
+
183
+ // Override kernel CRUD methods to use drivers
184
+ kernel.create = async (objectName: string, data: any): Promise<any> => {
185
+ const driver = getDriver(objectName);
186
+ return await driver.create(objectName, data, {});
187
+ };
188
+
189
+ kernel.update = async (objectName: string, id: string, data: any): Promise<any> => {
190
+ const driver = getDriver(objectName);
191
+ return await driver.update(objectName, id, data, {});
192
+ };
193
+
194
+ kernel.delete = async (objectName: string, id: string): Promise<boolean> => {
195
+ const driver = getDriver(objectName);
196
+ const result = await driver.delete(objectName, id, {});
197
+ return !!result;
198
+ };
199
+
200
+ kernel.find = async (objectName: string, query: any): Promise<{ value: any[]; count: number }> => {
201
+ const driver = getDriver(objectName);
202
+ const value = await driver.find(objectName, query);
203
+ const count = value.length;
204
+ return { value, count };
205
+ };
206
+
207
+ kernel.get = async (objectName: string, id: string): Promise<any> => {
208
+ const driver = getDriver(objectName);
209
+ return await driver.findOne(objectName, id);
210
+ };
211
+
212
+ console.log(`[${this.name}] Repository pattern registered`);
213
+ }
214
+
215
+ /**
216
+ * Register AI integration
217
+ * @private
218
+ */
219
+ private async registerAI(kernel: ObjectStackKernel): Promise<void> {
220
+ // TODO: Implement AI registration
221
+ // For now, this is a placeholder to establish the structure
222
+ console.log(`[${this.name}] AI integration registered`);
223
+ }
224
+ }
@@ -0,0 +1,148 @@
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
+ */
8
+
9
+ import type { Filter } from '@objectql/types';
10
+ import { Data } from '@objectstack/spec';
11
+ type FilterNode = Data.FilterNode;
12
+ import { ObjectQLError } from '@objectql/types';
13
+
14
+ /**
15
+ * Filter Translator
16
+ *
17
+ * Translates ObjectQL Filter (FilterCondition) to ObjectStack FilterNode format.
18
+ * Converts modern object-based syntax to legacy array-based syntax for backward compatibility.
19
+ *
20
+ * @example
21
+ * Input: { age: { $gte: 18 }, $or: [{ status: "active" }, { role: "admin" }] }
22
+ * Output: [["age", ">=", 18], "or", [["status", "=", "active"], "or", ["role", "=", "admin"]]]
23
+ */
24
+ export class FilterTranslator {
25
+ /**
26
+ * Translate filters from ObjectQL format to ObjectStack FilterNode format
27
+ */
28
+ translate(filters?: Filter): FilterNode | undefined {
29
+ if (!filters) {
30
+ return undefined;
31
+ }
32
+
33
+ // Backward compatibility: if it's already an array (old format), pass through
34
+ if (Array.isArray(filters)) {
35
+ return filters as unknown as FilterNode;
36
+ }
37
+
38
+ // If it's an empty object, return undefined
39
+ if (typeof filters === 'object' && Object.keys(filters).length === 0) {
40
+ return undefined;
41
+ }
42
+
43
+ return this.convertToNode(filters);
44
+ }
45
+
46
+ /**
47
+ * Recursively converts FilterCondition to FilterNode array format
48
+ */
49
+ private convertToNode(filter: Filter): FilterNode {
50
+ const nodes: any[] = [];
51
+
52
+ // Process logical operators first
53
+ if (filter.$and) {
54
+ const andNodes = filter.$and.map((f: Filter) => this.convertToNode(f));
55
+ nodes.push(...this.interleaveWithOperator(andNodes, 'and'));
56
+ }
57
+
58
+ if (filter.$or) {
59
+ const orNodes = filter.$or.map((f: Filter) => this.convertToNode(f));
60
+ if (nodes.length > 0) {
61
+ nodes.push('and');
62
+ }
63
+ nodes.push(...this.interleaveWithOperator(orNodes, 'or'));
64
+ }
65
+
66
+ // Note: $not operator is not currently supported in the legacy FilterNode format
67
+ if (filter.$not) {
68
+ throw new ObjectQLError({
69
+ code: 'UNSUPPORTED_OPERATOR',
70
+ message: '$not operator is not supported. Use $ne for field negation instead.'
71
+ });
72
+ }
73
+
74
+ // Process field conditions
75
+ for (const [field, value] of Object.entries(filter)) {
76
+ if (field.startsWith('$')) {
77
+ continue; // Skip logical operators (already processed)
78
+ }
79
+
80
+ if (nodes.length > 0) {
81
+ nodes.push('and');
82
+ }
83
+
84
+ // Handle field value
85
+ if (value === null || value === undefined) {
86
+ nodes.push([field, '=', value]);
87
+ } else if (typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
88
+ // Explicit operators - multiple operators on same field are AND-ed together
89
+ const entries = Object.entries(value);
90
+ for (let i = 0; i < entries.length; i++) {
91
+ const [op, opValue] = entries[i];
92
+
93
+ // Add 'and' before each operator (except the very first node)
94
+ if (nodes.length > 0 || i > 0) {
95
+ nodes.push('and');
96
+ }
97
+
98
+ const legacyOp = this.mapOperatorToLegacy(op);
99
+ nodes.push([field, legacyOp, opValue]);
100
+ }
101
+ } else {
102
+ // Implicit equality
103
+ nodes.push([field, '=', value]);
104
+ }
105
+ }
106
+
107
+ // Return as FilterNode (type assertion for backward compatibility)
108
+ return (nodes.length === 1 ? nodes[0] : nodes) as unknown as FilterNode;
109
+ }
110
+
111
+ /**
112
+ * Interleaves filter nodes with a logical operator
113
+ */
114
+ private interleaveWithOperator(nodes: FilterNode[], operator: string): any[] {
115
+ if (nodes.length === 0) return [];
116
+ if (nodes.length === 1) return [nodes[0]];
117
+
118
+ const result: any[] = [nodes[0]];
119
+ for (let i = 1; i < nodes.length; i++) {
120
+ result.push(operator, nodes[i]);
121
+ }
122
+ return result;
123
+ }
124
+
125
+ /**
126
+ * Maps modern $-prefixed operators to legacy format
127
+ */
128
+ private mapOperatorToLegacy(operator: string): string {
129
+ const mapping: Record<string, string> = {
130
+ '$eq': '=',
131
+ '$ne': '!=',
132
+ '$gt': '>',
133
+ '$gte': '>=',
134
+ '$lt': '<',
135
+ '$lte': '<=',
136
+ '$in': 'in',
137
+ '$nin': 'nin',
138
+ '$contains': 'contains',
139
+ '$startsWith': 'startswith',
140
+ '$endsWith': 'endswith',
141
+ '$null': 'is_null',
142
+ '$exist': 'is_not_null',
143
+ '$between': 'between',
144
+ };
145
+
146
+ return mapping[operator] || operator.replace('$', '');
147
+ }
148
+ }
@@ -0,0 +1,24 @@
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
+ */
8
+
9
+ /**
10
+ * Query Module
11
+ *
12
+ * This module contains ObjectQL's query-specific functionality:
13
+ * - FilterTranslator: Converts ObjectQL filters to ObjectStack FilterNode
14
+ * - QueryBuilder: Builds ObjectStack QueryAST from ObjectQL UnifiedQuery
15
+ * - QueryService: Executes queries via drivers with profiling support
16
+ * - QueryAnalyzer: Provides query performance analysis and optimization suggestions
17
+ *
18
+ * These are the core components that differentiate ObjectQL from generic runtime systems.
19
+ */
20
+
21
+ export * from './filter-translator';
22
+ export * from './query-builder';
23
+ export * from './query-service';
24
+ export * from './query-analyzer';