@objectql/core 4.2.0 → 4.2.2
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +34 -0
- package/README.md +5 -2
- package/dist/app.d.ts +61 -52
- package/dist/app.js +100 -435
- package/dist/app.js.map +1 -1
- package/dist/index.d.ts +14 -20
- package/dist/index.js +21 -20
- package/dist/index.js.map +1 -1
- package/dist/kernel-factory.d.ts +38 -0
- package/dist/kernel-factory.js +38 -0
- package/dist/kernel-factory.js.map +1 -0
- package/dist/plugin.d.ts +9 -16
- package/dist/plugin.js +71 -48
- package/dist/plugin.js.map +1 -1
- package/dist/repository.d.ts +4 -31
- package/dist/repository.js +7 -283
- package/dist/repository.js.map +1 -1
- package/dist/util.js +4 -2
- package/dist/util.js.map +1 -1
- package/package.json +14 -12
- package/src/app.ts +156 -539
- package/src/index.ts +18 -42
- package/src/kernel-factory.ts +47 -0
- package/src/plugin.ts +77 -85
- package/src/repository.ts +4 -320
- package/src/util.ts +5 -3
- package/test/__mocks__/@objectstack/core.ts +250 -1
- package/test/__mocks__/@objectstack/objectql.ts +273 -0
- package/test/__mocks__/@objectstack/runtime.ts +14 -5
- package/test/introspection.test.ts +1 -1
- package/test/mock-driver.ts +5 -5
- package/test/optimizations.test.ts +2 -2
- package/test/plugin-integration.test.ts +1 -3
- package/test/utils.ts +6 -6
- package/tsconfig.json +3 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/ai/index.d.ts +0 -8
- package/dist/ai/index.js +0 -25
- package/dist/ai/index.js.map +0 -1
- package/dist/ai/registry.d.ts +0 -23
- package/dist/ai/registry.js +0 -78
- package/dist/ai/registry.js.map +0 -1
- package/dist/gateway.d.ts +0 -37
- package/dist/gateway.js +0 -93
- package/dist/gateway.js.map +0 -1
- package/dist/optimizations/CompiledHookManager.d.ts +0 -56
- package/dist/optimizations/CompiledHookManager.js +0 -170
- package/dist/optimizations/CompiledHookManager.js.map +0 -1
- package/dist/optimizations/DependencyGraph.d.ts +0 -82
- package/dist/optimizations/DependencyGraph.js +0 -211
- package/dist/optimizations/DependencyGraph.js.map +0 -1
- package/dist/optimizations/GlobalConnectionPool.d.ts +0 -89
- package/dist/optimizations/GlobalConnectionPool.js +0 -193
- package/dist/optimizations/GlobalConnectionPool.js.map +0 -1
- package/dist/optimizations/LazyMetadataLoader.d.ts +0 -75
- package/dist/optimizations/LazyMetadataLoader.js +0 -149
- package/dist/optimizations/LazyMetadataLoader.js.map +0 -1
- package/dist/optimizations/OptimizedMetadataRegistry.d.ts +0 -26
- package/dist/optimizations/OptimizedMetadataRegistry.js +0 -117
- package/dist/optimizations/OptimizedMetadataRegistry.js.map +0 -1
- package/dist/optimizations/OptimizedValidationEngine.d.ts +0 -73
- package/dist/optimizations/OptimizedValidationEngine.js +0 -141
- package/dist/optimizations/OptimizedValidationEngine.js.map +0 -1
- package/dist/optimizations/QueryCompiler.d.ts +0 -51
- package/dist/optimizations/QueryCompiler.js +0 -216
- package/dist/optimizations/QueryCompiler.js.map +0 -1
- package/dist/optimizations/SQLQueryOptimizer.d.ts +0 -96
- package/dist/optimizations/SQLQueryOptimizer.js +0 -265
- package/dist/optimizations/SQLQueryOptimizer.js.map +0 -1
- package/dist/optimizations/index.d.ts +0 -32
- package/dist/optimizations/index.js +0 -44
- package/dist/optimizations/index.js.map +0 -1
- package/dist/protocol.d.ts +0 -191
- package/dist/protocol.js +0 -272
- package/dist/protocol.js.map +0 -1
- package/dist/query/filter-translator.d.ts +0 -24
- package/dist/query/filter-translator.js +0 -38
- package/dist/query/filter-translator.js.map +0 -1
- package/dist/query/index.d.ts +0 -22
- package/dist/query/index.js +0 -39
- package/dist/query/index.js.map +0 -1
- package/dist/query/query-analyzer.d.ts +0 -186
- package/dist/query/query-analyzer.js +0 -348
- package/dist/query/query-analyzer.js.map +0 -1
- package/dist/query/query-builder.d.ts +0 -27
- package/dist/query/query-builder.js +0 -69
- package/dist/query/query-builder.js.map +0 -1
- package/dist/query/query-service.d.ts +0 -151
- package/dist/query/query-service.js +0 -272
- package/dist/query/query-service.js.map +0 -1
- package/src/ai/index.ts +0 -9
- package/src/ai/registry.ts +0 -81
- package/src/gateway.ts +0 -105
- package/src/optimizations/CompiledHookManager.ts +0 -193
- package/src/optimizations/DependencyGraph.ts +0 -255
- package/src/optimizations/GlobalConnectionPool.ts +0 -251
- package/src/optimizations/LazyMetadataLoader.ts +0 -180
- package/src/optimizations/OptimizedMetadataRegistry.ts +0 -132
- package/src/optimizations/OptimizedValidationEngine.ts +0 -172
- package/src/optimizations/QueryCompiler.ts +0 -242
- package/src/optimizations/SQLQueryOptimizer.ts +0 -329
- package/src/optimizations/index.ts +0 -34
- package/src/protocol.ts +0 -304
- package/src/query/filter-translator.ts +0 -41
- package/src/query/index.ts +0 -24
- package/src/query/query-analyzer.ts +0 -532
- package/src/query/query-builder.ts +0 -64
- package/src/query/query-service.ts +0 -397
- package/test/ai-registry.test.ts +0 -42
- package/test/app.test.ts +0 -578
- package/test/filter-syntax.test.ts +0 -233
- package/test/gateway.test.ts +0 -88
- package/test/protocol.test.ts +0 -143
- package/test/repository-validation.test.ts +0 -351
- package/test/repository.test.ts +0 -151
package/src/repository.ts
CHANGED
|
@@ -1,322 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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, QueryAST } from '@objectql/types';
|
|
10
|
-
import type { ObjectKernel } from '@objectstack/runtime';
|
|
11
|
-
import { Data } from '@objectstack/spec';
|
|
12
|
-
import { z } from 'zod';
|
|
13
|
-
type SortNode = z.infer<typeof Data.SortNodeSchema>;
|
|
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
|
-
let result: number;
|
|
153
|
-
const kernel = this.getKernel() as any;
|
|
154
|
-
if (typeof kernel.count === 'function') {
|
|
155
|
-
result = await kernel.count(this.objectName, hookCtx.query || {});
|
|
156
|
-
} else {
|
|
157
|
-
// Fallback to driver if kernel doesn't support count
|
|
158
|
-
result = await this.getDriver().count(this.objectName, hookCtx.query || {});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
hookCtx.result = result;
|
|
162
|
-
await this.app.triggerHook('afterCount', this.objectName, hookCtx);
|
|
163
|
-
return hookCtx.result as number;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async create(doc: any): Promise<any> {
|
|
167
|
-
const hookCtx: MutationHookContext = {
|
|
168
|
-
...this.context,
|
|
169
|
-
objectName: this.objectName,
|
|
170
|
-
operation: 'create',
|
|
171
|
-
state: {},
|
|
172
|
-
api: this.getHookAPI(),
|
|
173
|
-
user: this.getUserFromContext(),
|
|
174
|
-
data: doc
|
|
175
|
-
};
|
|
176
|
-
await this.app.triggerHook('beforeCreate', this.objectName, hookCtx);
|
|
177
|
-
const finalDoc = hookCtx.data || doc;
|
|
178
|
-
|
|
179
|
-
if (this.context.userId) finalDoc.created_by = this.context.userId;
|
|
180
|
-
if (this.context.spaceId) finalDoc.space_id = this.context.spaceId;
|
|
181
|
-
|
|
182
|
-
// Validation moved to ValidatorPlugin hook
|
|
183
|
-
|
|
184
|
-
// Execute via kernel
|
|
185
|
-
const result = await (this.getKernel() as any).create(this.objectName, finalDoc, this.getOptions());
|
|
186
|
-
|
|
187
|
-
hookCtx.result = result;
|
|
188
|
-
await this.app.triggerHook('afterCreate', this.objectName, hookCtx);
|
|
189
|
-
return hookCtx.result;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async update(id: string | number, doc: any, options?: any): Promise<any> {
|
|
193
|
-
const previousData = await this.findOne(id);
|
|
194
|
-
const hookCtx: UpdateHookContext = {
|
|
195
|
-
...this.context,
|
|
196
|
-
objectName: this.objectName,
|
|
197
|
-
operation: 'update',
|
|
198
|
-
state: {},
|
|
199
|
-
api: this.getHookAPI(),
|
|
200
|
-
user: this.getUserFromContext(),
|
|
201
|
-
id,
|
|
202
|
-
data: doc,
|
|
203
|
-
previousData,
|
|
204
|
-
isModified: (field) => hookCtx.data ? Object.prototype.hasOwnProperty.call(hookCtx.data, field) : false
|
|
205
|
-
};
|
|
206
|
-
await this.app.triggerHook('beforeUpdate', this.objectName, hookCtx);
|
|
207
|
-
|
|
208
|
-
// Validation moved to ValidatorPlugin hook
|
|
209
|
-
|
|
210
|
-
// Execute via kernel
|
|
211
|
-
const result = await (this.getKernel() as any).update(this.objectName, String(id), hookCtx.data, this.getOptions());
|
|
212
|
-
|
|
213
|
-
hookCtx.result = result;
|
|
214
|
-
await this.app.triggerHook('afterUpdate', this.objectName, hookCtx);
|
|
215
|
-
return hookCtx.result;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async delete(id: string | number): Promise<any> {
|
|
219
|
-
const previousData = await this.findOne(id);
|
|
220
|
-
const hookCtx: MutationHookContext = {
|
|
221
|
-
...this.context,
|
|
222
|
-
objectName: this.objectName,
|
|
223
|
-
operation: 'delete',
|
|
224
|
-
state: {},
|
|
225
|
-
api: this.getHookAPI(),
|
|
226
|
-
user: this.getUserFromContext(),
|
|
227
|
-
id,
|
|
228
|
-
previousData
|
|
229
|
-
};
|
|
230
|
-
await this.app.triggerHook('beforeDelete', this.objectName, hookCtx);
|
|
231
|
-
|
|
232
|
-
// Execute via kernel
|
|
233
|
-
const result = await (this.getKernel() as any).delete(this.objectName, String(id), this.getOptions());
|
|
234
|
-
|
|
235
|
-
hookCtx.result = result;
|
|
236
|
-
await this.app.triggerHook('afterDelete', this.objectName, hookCtx);
|
|
237
|
-
return hookCtx.result;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async aggregate(query: any): Promise<any> {
|
|
241
|
-
const driver = this.getDriver();
|
|
242
|
-
if (!driver.aggregate) throw new Error("Driver does not support aggregate");
|
|
243
|
-
|
|
244
|
-
return driver.aggregate(this.objectName, query, this.getOptions());
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
async distinct(field: string, filters?: any): Promise<any[]> {
|
|
248
|
-
const driver = this.getDriver();
|
|
249
|
-
if (!driver.distinct) throw new Error("Driver does not support distinct");
|
|
250
|
-
|
|
251
|
-
return driver.distinct(this.objectName, field, filters, this.getOptions());
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
async findOneAndUpdate(filters: any, update: any, options?: any): Promise<any> {
|
|
255
|
-
const driver = this.getDriver();
|
|
256
|
-
if (!driver.findOneAndUpdate) throw new Error("Driver does not support findOneAndUpdate");
|
|
257
|
-
return driver.findOneAndUpdate(this.objectName, filters, update, this.getOptions(options));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
async createMany(data: any[]): Promise<any> {
|
|
261
|
-
const driver = this.getDriver();
|
|
262
|
-
|
|
263
|
-
// Always use fallback to ensure validation and hooks are executed
|
|
264
|
-
// This maintains ObjectQL's metadata-driven architecture where
|
|
265
|
-
// validation rules, hooks, and permissions are enforced consistently
|
|
266
|
-
const results = [];
|
|
267
|
-
for (const item of data) {
|
|
268
|
-
results.push(await this.create(item));
|
|
269
|
-
}
|
|
270
|
-
return results;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
async updateMany(filters: any, data: any): Promise<any> {
|
|
274
|
-
// Find all matching records and update them individually
|
|
275
|
-
// to ensure validation and hooks are executed
|
|
276
|
-
const records = await this.find({ where: filters });
|
|
277
|
-
let count = 0;
|
|
278
|
-
for (const record of records) {
|
|
279
|
-
if (record && record._id) {
|
|
280
|
-
await this.update(record._id, data);
|
|
281
|
-
count++;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
return count;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async deleteMany(filters: any): Promise<any> {
|
|
288
|
-
// Find all matching records and delete them individually
|
|
289
|
-
// to ensure hooks are executed
|
|
290
|
-
const records = await this.find({ where: filters });
|
|
291
|
-
let count = 0;
|
|
292
|
-
for (const record of records) {
|
|
293
|
-
if (record && record._id) {
|
|
294
|
-
await this.delete(record._id);
|
|
295
|
-
count++;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return count;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async execute(actionName: string, id: string | number | undefined, params: any): Promise<any> {
|
|
302
|
-
const api: HookAPI = {
|
|
303
|
-
find: (obj, q) => this.context.object(obj).find(q),
|
|
304
|
-
findOne: (obj, id) => this.context.object(obj).findOne(id),
|
|
305
|
-
count: (obj, q) => this.context.object(obj).count(q),
|
|
306
|
-
create: (obj, data) => this.context.object(obj).create(data),
|
|
307
|
-
update: (obj, id, data) => this.context.object(obj).update(id, data),
|
|
308
|
-
delete: (obj, id) => this.context.object(obj).delete(id)
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
const ctx: ActionContext = {
|
|
312
|
-
...this.context,
|
|
313
|
-
objectName: this.objectName,
|
|
314
|
-
actionName,
|
|
315
|
-
id,
|
|
316
|
-
input: params,
|
|
317
|
-
api,
|
|
318
|
-
user: this.getUserFromContext()
|
|
319
|
-
};
|
|
320
|
-
return await this.app.executeAction(this.objectName, actionName, ctx);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
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
|
|
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
|
-
|
|
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();
|