@objectql/core 4.1.0 → 4.2.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 (107) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +42 -0
  3. package/dist/app.d.ts +61 -51
  4. package/dist/app.js +101 -431
  5. package/dist/app.js.map +1 -1
  6. package/dist/index.d.ts +4 -11
  7. package/dist/index.js +13 -9
  8. package/dist/index.js.map +1 -1
  9. package/dist/kernel-factory.d.ts +38 -0
  10. package/dist/kernel-factory.js +38 -0
  11. package/dist/kernel-factory.js.map +1 -0
  12. package/dist/plugin.d.ts +10 -16
  13. package/dist/plugin.js +104 -58
  14. package/dist/plugin.js.map +1 -1
  15. package/dist/repository.d.ts +4 -31
  16. package/dist/repository.js +7 -275
  17. package/dist/repository.js.map +1 -1
  18. package/dist/util.js +4 -2
  19. package/dist/util.js.map +1 -1
  20. package/package.json +19 -11
  21. package/src/app.ts +157 -531
  22. package/src/index.ts +8 -23
  23. package/src/kernel-factory.ts +47 -0
  24. package/src/plugin.ts +116 -94
  25. package/src/repository.ts +4 -313
  26. package/src/util.ts +5 -3
  27. package/test/__mocks__/@objectstack/core.ts +250 -1
  28. package/test/__mocks__/@objectstack/objectql.ts +127 -0
  29. package/test/__mocks__/@objectstack/runtime.ts +14 -5
  30. package/test/introspection.test.ts +1 -1
  31. package/test/mock-driver.ts +5 -5
  32. package/test/optimizations.test.ts +2 -2
  33. package/test/plugin-integration.test.ts +4 -5
  34. package/test/utils.ts +6 -6
  35. package/tsconfig.json +3 -1
  36. package/tsconfig.tsbuildinfo +1 -1
  37. package/dist/gateway.d.ts +0 -36
  38. package/dist/gateway.js +0 -89
  39. package/dist/gateway.js.map +0 -1
  40. package/dist/optimizations/CompiledHookManager.d.ts +0 -55
  41. package/dist/optimizations/CompiledHookManager.js +0 -164
  42. package/dist/optimizations/CompiledHookManager.js.map +0 -1
  43. package/dist/optimizations/DependencyGraph.d.ts +0 -82
  44. package/dist/optimizations/DependencyGraph.js +0 -211
  45. package/dist/optimizations/DependencyGraph.js.map +0 -1
  46. package/dist/optimizations/GlobalConnectionPool.d.ts +0 -89
  47. package/dist/optimizations/GlobalConnectionPool.js +0 -193
  48. package/dist/optimizations/GlobalConnectionPool.js.map +0 -1
  49. package/dist/optimizations/LazyMetadataLoader.d.ts +0 -75
  50. package/dist/optimizations/LazyMetadataLoader.js +0 -149
  51. package/dist/optimizations/LazyMetadataLoader.js.map +0 -1
  52. package/dist/optimizations/OptimizedMetadataRegistry.d.ts +0 -26
  53. package/dist/optimizations/OptimizedMetadataRegistry.js +0 -117
  54. package/dist/optimizations/OptimizedMetadataRegistry.js.map +0 -1
  55. package/dist/optimizations/OptimizedValidationEngine.d.ts +0 -73
  56. package/dist/optimizations/OptimizedValidationEngine.js +0 -141
  57. package/dist/optimizations/OptimizedValidationEngine.js.map +0 -1
  58. package/dist/optimizations/QueryCompiler.d.ts +0 -51
  59. package/dist/optimizations/QueryCompiler.js +0 -216
  60. package/dist/optimizations/QueryCompiler.js.map +0 -1
  61. package/dist/optimizations/SQLQueryOptimizer.d.ts +0 -96
  62. package/dist/optimizations/SQLQueryOptimizer.js +0 -265
  63. package/dist/optimizations/SQLQueryOptimizer.js.map +0 -1
  64. package/dist/optimizations/index.d.ts +0 -32
  65. package/dist/optimizations/index.js +0 -44
  66. package/dist/optimizations/index.js.map +0 -1
  67. package/dist/protocol.d.ts +0 -180
  68. package/dist/protocol.js +0 -260
  69. package/dist/protocol.js.map +0 -1
  70. package/dist/query/filter-translator.d.ts +0 -27
  71. package/dist/query/filter-translator.js +0 -38
  72. package/dist/query/filter-translator.js.map +0 -1
  73. package/dist/query/index.d.ts +0 -22
  74. package/dist/query/index.js +0 -39
  75. package/dist/query/index.js.map +0 -1
  76. package/dist/query/query-analyzer.d.ts +0 -188
  77. package/dist/query/query-analyzer.js +0 -348
  78. package/dist/query/query-analyzer.js.map +0 -1
  79. package/dist/query/query-builder.d.ts +0 -35
  80. package/dist/query/query-builder.js +0 -61
  81. package/dist/query/query-builder.js.map +0 -1
  82. package/dist/query/query-service.d.ts +0 -153
  83. package/dist/query/query-service.js +0 -272
  84. package/dist/query/query-service.js.map +0 -1
  85. package/jest.config.js +0 -29
  86. package/src/gateway.ts +0 -101
  87. package/src/optimizations/CompiledHookManager.ts +0 -185
  88. package/src/optimizations/DependencyGraph.ts +0 -255
  89. package/src/optimizations/GlobalConnectionPool.ts +0 -251
  90. package/src/optimizations/LazyMetadataLoader.ts +0 -180
  91. package/src/optimizations/OptimizedMetadataRegistry.ts +0 -132
  92. package/src/optimizations/OptimizedValidationEngine.ts +0 -172
  93. package/src/optimizations/QueryCompiler.ts +0 -242
  94. package/src/optimizations/SQLQueryOptimizer.ts +0 -329
  95. package/src/optimizations/index.ts +0 -34
  96. package/src/protocol.ts +0 -291
  97. package/src/query/filter-translator.ts +0 -41
  98. package/src/query/index.ts +0 -24
  99. package/src/query/query-analyzer.ts +0 -533
  100. package/src/query/query-builder.ts +0 -64
  101. package/src/query/query-service.ts +0 -398
  102. package/test/app.test.ts +0 -578
  103. package/test/filter-syntax.test.ts +0 -233
  104. package/test/gateway.test.ts +0 -88
  105. package/test/protocol.test.ts +0 -143
  106. package/test/repository-validation.test.ts +0 -351
  107. package/test/repository.test.ts +0 -151
package/src/repository.ts CHANGED
@@ -1,315 +1,6 @@
1
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.
2
+ * @deprecated This file is dead code. Delete it.
3
+ * ObjectRepository class now comes from @objectstack/objectql.
4
+ * Re-exported via index.ts for backward compatibility.
7
5
  */
8
-
9
- import { ObjectQLContext, IObjectQL, ObjectConfig, Driver, UnifiedQuery, ActionContext, HookAPI, RetrievalHookContext, MutationHookContext, UpdateHookContext, ValidationContext, ValidationError, ValidationRuleResult, FormulaContext, Filter } from '@objectql/types';
10
- import type { ObjectKernel } from '@objectstack/runtime';
11
- import { Data } from '@objectstack/spec';
12
- type QueryAST = Data.QueryAST;
13
- type SortNode = Data.SortNode;
14
-
15
- export class ObjectRepository {
16
-
17
- constructor(
18
- private objectName: string,
19
- private context: ObjectQLContext,
20
- private app: IObjectQL
21
- ) {
22
- }
23
-
24
- private getDriver(): Driver {
25
- const obj = this.getSchema();
26
- const datasourceName = obj.datasource || 'default';
27
- return this.app.datasource(datasourceName);
28
- }
29
-
30
- private getKernel(): ObjectKernel {
31
- return this.app.getKernel();
32
- }
33
-
34
- private getOptions(extra: Record<string, unknown> = {}) {
35
- return {
36
- transaction: this.context.transactionHandle,
37
- ...extra
38
- };
39
- }
40
-
41
- getSchema(): ObjectConfig {
42
- const obj = this.app.getObject(this.objectName);
43
- if (!obj) {
44
- throw new Error(`Object '${this.objectName}' not found`);
45
- }
46
- return obj;
47
- }
48
-
49
- private getHookAPI(): HookAPI {
50
- return {
51
- find: (obj, q) => this.context.object(obj).find(q),
52
- findOne: (obj, id) => this.context.object(obj).findOne(id),
53
- count: (obj, q) => this.context.object(obj).count(q),
54
- create: (obj, data) => this.context.object(obj).create(data),
55
- update: (obj, id, data) => this.context.object(obj).update(id, data),
56
- delete: (obj, id) => this.context.object(obj).delete(id)
57
- };
58
- }
59
-
60
- private getUserFromContext() {
61
- if (!this.context.userId) {
62
- return undefined;
63
- }
64
- // Construct user object from context, including relevant properties
65
- return {
66
- id: this.context.userId,
67
- spaceId: this.context.spaceId,
68
- roles: this.context.roles,
69
- isSystem: this.context.isSystem
70
- };
71
- }
72
-
73
-
74
- async find(query: UnifiedQuery = {}): Promise<any[]> {
75
- const hookCtx: RetrievalHookContext = {
76
- ...this.context,
77
- objectName: this.objectName,
78
- operation: 'find',
79
- api: this.getHookAPI(),
80
- user: this.getUserFromContext(),
81
- state: {},
82
- query
83
- };
84
- await this.app.triggerHook('beforeFind', this.objectName, hookCtx);
85
-
86
- // Execute via kernel (delegates to QueryService)
87
- const kernelResult = await (this.getKernel() as any).find(this.objectName, hookCtx.query || {});
88
- const results = kernelResult.value;
89
-
90
- // Formula evaluation moved to FormulaPlugin hook
91
- hookCtx.result = results;
92
- await this.app.triggerHook('afterFind', this.objectName, hookCtx);
93
-
94
- return hookCtx.result as any[];
95
- }
96
-
97
- async findOne(idOrQuery: string | number | UnifiedQuery): Promise<any> {
98
- if (typeof idOrQuery === 'string' || typeof idOrQuery === 'number') {
99
- const hookCtx: RetrievalHookContext = {
100
- ...this.context,
101
- objectName: this.objectName,
102
- operation: 'find',
103
- api: this.getHookAPI(),
104
- user: this.getUserFromContext(),
105
- state: {},
106
- query: { _id: idOrQuery }
107
- };
108
- await this.app.triggerHook('beforeFind', this.objectName, hookCtx);
109
-
110
- // Use kernel.get() for direct ID lookup
111
- const result = await (this.getKernel() as any).get(this.objectName, String(idOrQuery));
112
-
113
- // Formula evaluation moved to FormulaPlugin hook
114
- hookCtx.result = result;
115
- await this.app.triggerHook('afterFind', this.objectName, hookCtx);
116
- return hookCtx.result;
117
- } else {
118
- const results = await this.find(idOrQuery);
119
- return results[0] || null;
120
- }
121
- }
122
-
123
- async count(filters: any): Promise<number> {
124
- // Normalize filters to UnifiedQuery format
125
- // If filters is an array, wrap it in a query object
126
- // If filters is already a UnifiedQuery (has UnifiedQuery-specific properties), use it as-is
127
- let query: UnifiedQuery;
128
- if (Array.isArray(filters)) {
129
- query = { where: filters };
130
- } else if (filters && typeof filters === 'object' && (filters.fields || filters.orderBy || filters.limit !== undefined || filters.offset !== undefined)) {
131
- // It's already a UnifiedQuery object
132
- query = filters;
133
- } else if (filters) {
134
- // It's a raw filter object, wrap it
135
- query = { where: filters };
136
- } else {
137
- query = {};
138
- }
139
-
140
- const hookCtx: RetrievalHookContext = {
141
- ...this.context,
142
- objectName: this.objectName,
143
- operation: 'count',
144
- api: this.getHookAPI(),
145
- user: this.getUserFromContext(),
146
- state: {},
147
- query
148
- };
149
- await this.app.triggerHook('beforeCount', this.objectName, hookCtx);
150
-
151
- // Execute via kernel (delegates to QueryService)
152
- const result = await (this.getKernel() as any).count(this.objectName, hookCtx.query || {});
153
-
154
- hookCtx.result = result;
155
- await this.app.triggerHook('afterCount', this.objectName, hookCtx);
156
- return hookCtx.result as number;
157
- }
158
-
159
- async create(doc: any): Promise<any> {
160
- const hookCtx: MutationHookContext = {
161
- ...this.context,
162
- objectName: this.objectName,
163
- operation: 'create',
164
- state: {},
165
- api: this.getHookAPI(),
166
- user: this.getUserFromContext(),
167
- data: doc
168
- };
169
- await this.app.triggerHook('beforeCreate', this.objectName, hookCtx);
170
- const finalDoc = hookCtx.data || doc;
171
-
172
- if (this.context.userId) finalDoc.created_by = this.context.userId;
173
- if (this.context.spaceId) finalDoc.space_id = this.context.spaceId;
174
-
175
- // Validation moved to ValidatorPlugin hook
176
-
177
- // Execute via kernel
178
- const result = await (this.getKernel() as any).create(this.objectName, finalDoc, this.getOptions());
179
-
180
- hookCtx.result = result;
181
- await this.app.triggerHook('afterCreate', this.objectName, hookCtx);
182
- return hookCtx.result;
183
- }
184
-
185
- async update(id: string | number, doc: any, options?: any): Promise<any> {
186
- const previousData = await this.findOne(id);
187
- const hookCtx: UpdateHookContext = {
188
- ...this.context,
189
- objectName: this.objectName,
190
- operation: 'update',
191
- state: {},
192
- api: this.getHookAPI(),
193
- user: this.getUserFromContext(),
194
- id,
195
- data: doc,
196
- previousData,
197
- isModified: (field) => hookCtx.data ? Object.prototype.hasOwnProperty.call(hookCtx.data, field) : false
198
- };
199
- await this.app.triggerHook('beforeUpdate', this.objectName, hookCtx);
200
-
201
- // Validation moved to ValidatorPlugin hook
202
-
203
- // Execute via kernel
204
- const result = await (this.getKernel() as any).update(this.objectName, String(id), hookCtx.data, this.getOptions());
205
-
206
- hookCtx.result = result;
207
- await this.app.triggerHook('afterUpdate', this.objectName, hookCtx);
208
- return hookCtx.result;
209
- }
210
-
211
- async delete(id: string | number): Promise<any> {
212
- const previousData = await this.findOne(id);
213
- const hookCtx: MutationHookContext = {
214
- ...this.context,
215
- objectName: this.objectName,
216
- operation: 'delete',
217
- state: {},
218
- api: this.getHookAPI(),
219
- user: this.getUserFromContext(),
220
- id,
221
- previousData
222
- };
223
- await this.app.triggerHook('beforeDelete', this.objectName, hookCtx);
224
-
225
- // Execute via kernel
226
- const result = await (this.getKernel() as any).delete(this.objectName, String(id), this.getOptions());
227
-
228
- hookCtx.result = result;
229
- await this.app.triggerHook('afterDelete', this.objectName, hookCtx);
230
- return hookCtx.result;
231
- }
232
-
233
- async aggregate(query: any): Promise<any> {
234
- const driver = this.getDriver();
235
- if (!driver.aggregate) throw new Error("Driver does not support aggregate");
236
-
237
- return driver.aggregate(this.objectName, query, this.getOptions());
238
- }
239
-
240
- async distinct(field: string, filters?: any): Promise<any[]> {
241
- const driver = this.getDriver();
242
- if (!driver.distinct) throw new Error("Driver does not support distinct");
243
-
244
- return driver.distinct(this.objectName, field, filters, this.getOptions());
245
- }
246
-
247
- async findOneAndUpdate(filters: any, update: any, options?: any): Promise<any> {
248
- const driver = this.getDriver();
249
- if (!driver.findOneAndUpdate) throw new Error("Driver does not support findOneAndUpdate");
250
- return driver.findOneAndUpdate(this.objectName, filters, update, this.getOptions(options));
251
- }
252
-
253
- async createMany(data: any[]): Promise<any> {
254
- const driver = this.getDriver();
255
-
256
- // Always use fallback to ensure validation and hooks are executed
257
- // This maintains ObjectQL's metadata-driven architecture where
258
- // validation rules, hooks, and permissions are enforced consistently
259
- const results = [];
260
- for (const item of data) {
261
- results.push(await this.create(item));
262
- }
263
- return results;
264
- }
265
-
266
- async updateMany(filters: any, data: any): Promise<any> {
267
- // Find all matching records and update them individually
268
- // to ensure validation and hooks are executed
269
- const records = await this.find({ where: filters });
270
- let count = 0;
271
- for (const record of records) {
272
- if (record && record._id) {
273
- await this.update(record._id, data);
274
- count++;
275
- }
276
- }
277
- return count;
278
- }
279
-
280
- async deleteMany(filters: any): Promise<any> {
281
- // Find all matching records and delete them individually
282
- // to ensure hooks are executed
283
- const records = await this.find({ where: filters });
284
- let count = 0;
285
- for (const record of records) {
286
- if (record && record._id) {
287
- await this.delete(record._id);
288
- count++;
289
- }
290
- }
291
- return count;
292
- }
293
-
294
- async execute(actionName: string, id: string | number | undefined, params: any): Promise<any> {
295
- const api: HookAPI = {
296
- find: (obj, q) => this.context.object(obj).find(q),
297
- findOne: (obj, id) => this.context.object(obj).findOne(id),
298
- count: (obj, q) => this.context.object(obj).count(q),
299
- create: (obj, data) => this.context.object(obj).create(data),
300
- update: (obj, id, data) => this.context.object(obj).update(id, data),
301
- delete: (obj, id) => this.context.object(obj).delete(id)
302
- };
303
-
304
- const ctx: ActionContext = {
305
- ...this.context,
306
- objectName: this.objectName,
307
- actionName,
308
- id,
309
- input: params,
310
- api,
311
- user: this.getUserFromContext()
312
- };
313
- return await this.app.executeAction(this.objectName, actionName, ctx);
314
- }
315
- }
6
+ export { ObjectRepository } from '@objectstack/objectql';
package/src/util.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
 
9
- import { ObjectConfig, FieldConfig, FieldType, IntrospectedSchema, IntrospectedColumn, IntrospectedTable } from '@objectql/types';
9
+ import { ObjectConfig, FieldConfig, FieldType, IntrospectedSchema } from '@objectql/types';
10
10
 
11
11
  export function toTitleCase(str: string): string {
12
12
  return str
@@ -118,7 +118,8 @@ export function convertIntrospectedSchemaToObjects(
118
118
  reference_to: foreignKey.referencedTable,
119
119
  label: toTitleCase(column.name),
120
120
  required: !column.nullable,
121
- searchable: false
121
+ searchable: false,
122
+ sortable: false
122
123
  };
123
124
  } else {
124
125
  // Regular field
@@ -131,7 +132,8 @@ export function convertIntrospectedSchemaToObjects(
131
132
  type: fieldType,
132
133
  label: toTitleCase(column.name),
133
134
  required: !column.nullable,
134
- searchable: false
135
+ searchable: false,
136
+ sortable: false
135
137
  };
136
138
 
137
139
  // Add unique constraint
@@ -22,6 +22,255 @@ export const createLogger = jest.fn(() => ({
22
22
  fatal: jest.fn(),
23
23
  }));
24
24
 
25
- export const ObjectKernel = jest.fn();
25
+ class MockMetadataRegistry {
26
+ private store = new Map<string, Map<string, any>>();
27
+
28
+ register(type: string, nameOrConfig: string | any, config?: any): void {
29
+ if (!this.store.has(type)) {
30
+ this.store.set(type, new Map());
31
+ }
32
+ const typeMap = this.store.get(type)!;
33
+ let name: string;
34
+ let item: any;
35
+ if (config) {
36
+ name = nameOrConfig as string;
37
+ item = config;
38
+ } else {
39
+ item = nameOrConfig;
40
+ name = item.id || item.name;
41
+ }
42
+ typeMap.set(name, item);
43
+ }
44
+
45
+ get<T = any>(type: string, id: string): T | undefined {
46
+ const typeMap = this.store.get(type);
47
+ const item = typeMap?.get(id);
48
+ return item?.content as T;
49
+ }
50
+
51
+ list<T = any>(type: string): T[] {
52
+ const typeMap = this.store.get(type);
53
+ if (!typeMap) return [];
54
+ return Array.from(typeMap.values()).map(item => item.content as T);
55
+ }
56
+
57
+ unregister(type: string, id: string): boolean {
58
+ const typeMap = this.store.get(type);
59
+ if (!typeMap) return false;
60
+ return typeMap.delete(id);
61
+ }
62
+
63
+ getTypes(): string[] {
64
+ return Array.from(this.store.keys());
65
+ }
66
+
67
+ getEntry(type: string, id: string): any | undefined {
68
+ const typeMap = this.store.get(type);
69
+ return typeMap ? typeMap.get(id) : undefined;
70
+ }
71
+
72
+ unregisterPackage(packageName: string): void {
73
+ for (const [_type, typeMap] of this.store.entries()) {
74
+ const toDelete: string[] = [];
75
+ for (const [id, item] of typeMap.entries()) {
76
+ if (item.packageName === packageName || item.package === packageName) {
77
+ toDelete.push(id);
78
+ }
79
+ }
80
+ toDelete.forEach(id => typeMap.delete(id));
81
+ }
82
+ }
83
+ }
84
+
85
+ class MockHookManager {
86
+ removePackage(_packageName: string): void {
87
+ // Mock implementation
88
+ }
89
+ clear(): void {
90
+ // Mock implementation
91
+ }
92
+ }
93
+
94
+ class MockActionManager {
95
+ removePackage(_packageName: string): void {
96
+ // Mock implementation
97
+ }
98
+ clear(): void {
99
+ // Mock implementation
100
+ }
101
+ }
102
+
103
+ export class ObjectKernel {
104
+ public ql: unknown = null;
105
+ public metadata: MockMetadataRegistry;
106
+ public hooks: MockHookManager;
107
+ public actions: MockActionManager;
108
+ private plugins: any[] = [];
109
+ private driver: any = null;
110
+
111
+ constructor(plugins: any[] = []) {
112
+ this.plugins = plugins;
113
+ this.metadata = new MockMetadataRegistry();
114
+ this.hooks = new MockHookManager();
115
+ this.actions = new MockActionManager();
116
+ }
117
+
118
+ setDriver(driver: any): void {
119
+ this.driver = driver;
120
+ }
121
+
122
+ async start(): Promise<void> {
123
+ for (const plugin of this.plugins) {
124
+ if (plugin.install) {
125
+ await plugin.install({ engine: this });
126
+ }
127
+ }
128
+ for (const plugin of this.plugins) {
129
+ if (plugin.onStart) {
130
+ await plugin.onStart({ engine: this });
131
+ }
132
+ }
133
+ }
134
+
135
+ async stop(): Promise<void> {
136
+ for (const plugin of this.plugins) {
137
+ if (plugin.onStop) {
138
+ await plugin.onStop({ engine: this });
139
+ }
140
+ }
141
+ }
142
+
143
+ getDriver(): any {
144
+ const driver = this.plugins.find(p =>
145
+ p.constructor.name?.includes('Driver') ||
146
+ (typeof p.find === 'function' && typeof p.create === 'function')
147
+ );
148
+ return driver || this.driver;
149
+ }
150
+
151
+ async seed(): Promise<void> {
152
+ // Mock implementation
153
+ }
154
+
155
+ async find(objectName: string, query: any): Promise<{ value: Record<string, any>[]; count: number }> {
156
+ const driver = this.getDriver();
157
+ if (driver) {
158
+ const normalizedQuery: any = {};
159
+ if (query.filters) {
160
+ normalizedQuery.where = query.filters;
161
+ } else if (query.filter) {
162
+ normalizedQuery.where = query.filter;
163
+ } else if (query.where) {
164
+ normalizedQuery.where = query.where;
165
+ }
166
+ if (query.fields) {
167
+ normalizedQuery.fields = query.fields;
168
+ }
169
+ if (query.sort) {
170
+ if (Array.isArray(query.sort) && query.sort.length > 0) {
171
+ const firstItem = query.sort[0];
172
+ if (firstItem !== undefined) {
173
+ if (Array.isArray(firstItem)) {
174
+ normalizedQuery.orderBy = query.sort.map((s: any) => ({
175
+ field: s[0],
176
+ order: s[1]
177
+ }));
178
+ } else if (typeof firstItem === 'object') {
179
+ normalizedQuery.orderBy = query.sort.flatMap((s: any) =>
180
+ Object.entries(s).map(([field, order]) => ({
181
+ field,
182
+ order: (order === -1 || order === 'desc' || order === 'DESC') ? 'desc' : 'asc'
183
+ }))
184
+ );
185
+ }
186
+ }
187
+ } else if (query.sort) {
188
+ normalizedQuery.orderBy = query.sort;
189
+ }
190
+ } else if (query.orderBy) {
191
+ normalizedQuery.orderBy = query.orderBy;
192
+ }
193
+ if (query.limit !== undefined) {
194
+ normalizedQuery.limit = query.limit;
195
+ } else if (query.top !== undefined) {
196
+ normalizedQuery.limit = query.top;
197
+ }
198
+ if (query.offset !== undefined) {
199
+ normalizedQuery.offset = query.offset;
200
+ } else if (query.skip !== undefined) {
201
+ normalizedQuery.offset = query.skip;
202
+ }
203
+ if (query.aggregations) {
204
+ normalizedQuery.aggregate = query.aggregations.map((agg: any) => ({
205
+ func: agg.function,
206
+ field: agg.field,
207
+ alias: agg.alias
208
+ }));
209
+ }
210
+ if (query.groupBy) {
211
+ normalizedQuery.groupBy = query.groupBy;
212
+ }
213
+ const results = await driver.find(objectName, normalizedQuery, {});
214
+ return { value: results, count: results.length };
215
+ }
216
+ return { value: [], count: 0 };
217
+ }
218
+
219
+ async get(objectName: string, id: string): Promise<Record<string, any>> {
220
+ const driver = this.getDriver();
221
+ if (driver) {
222
+ return await driver.findOne(objectName, id, {}, {});
223
+ }
224
+ return {};
225
+ }
226
+
227
+ async create(objectName: string, data: any): Promise<Record<string, any>> {
228
+ const driver = this.getDriver();
229
+ if (driver) {
230
+ return await driver.create(objectName, data, {});
231
+ }
232
+ return data;
233
+ }
234
+
235
+ async update(objectName: string, id: string, data: any): Promise<Record<string, any>> {
236
+ const driver = this.getDriver();
237
+ if (driver) {
238
+ return await driver.update(objectName, id, data, {});
239
+ }
240
+ return data;
241
+ }
242
+
243
+ async delete(objectName: string, id: string): Promise<boolean> {
244
+ const driver = this.getDriver();
245
+ if (driver) {
246
+ const result = await driver.delete(objectName, id, {});
247
+ return result > 0;
248
+ }
249
+ return true;
250
+ }
251
+
252
+ getMetadata(_objectName: string): any {
253
+ return {};
254
+ }
255
+
256
+ getView(_objectName: string, _viewType?: 'list' | 'form'): any {
257
+ return null;
258
+ }
259
+ }
260
+
261
+ export class ObjectStackProtocolImplementation {}
262
+
263
+ export interface PluginContext {
264
+ engine: ObjectKernel;
265
+ }
266
+
267
+ export interface ObjectQLPlugin {
268
+ name: string;
269
+ install?: (ctx: PluginContext) => void | Promise<void>;
270
+ onStart?: (ctx: PluginContext) => void | Promise<void>;
271
+ }
272
+
273
+ export { MockMetadataRegistry as MetadataRegistry };
274
+
26
275
  export const LiteKernel = jest.fn();
27
276
  export const createApiRegistryPlugin = jest.fn();