@objectstack/objectql 0.6.1 → 0.7.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/CHANGELOG.md +20 -0
- package/dist/engine.d.ts +137 -47
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +238 -155
- package/dist/plugin.js +5 -10
- package/dist/protocol.d.ts +103 -33
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +224 -46
- package/package.json +5 -5
- package/src/engine.ts +256 -176
- package/src/plugin.ts +5 -5
- package/src/protocol.ts +255 -49
package/dist/engine.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createLogger } from '@objectstack/core';
|
|
1
2
|
import { SchemaRegistry } from './registry';
|
|
2
3
|
/**
|
|
3
4
|
* ObjectQL Engine
|
|
@@ -18,12 +19,18 @@ export class ObjectQL {
|
|
|
18
19
|
// Host provided context additions (e.g. Server router)
|
|
19
20
|
this.hostContext = {};
|
|
20
21
|
this.hostContext = hostContext;
|
|
21
|
-
|
|
22
|
+
// Use provided logger or create a new one
|
|
23
|
+
this.logger = hostContext.logger || createLogger({ level: 'info', format: 'pretty' });
|
|
24
|
+
this.logger.info('ObjectQL Engine Instance Created');
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
27
|
* Load and Register a Plugin
|
|
25
28
|
*/
|
|
26
29
|
async use(manifestPart, runtimePart) {
|
|
30
|
+
this.logger.debug('Loading plugin', {
|
|
31
|
+
hasManifest: !!manifestPart,
|
|
32
|
+
hasRuntime: !!runtimePart
|
|
33
|
+
});
|
|
27
34
|
// 1. Validate / Register Manifest
|
|
28
35
|
if (manifestPart) {
|
|
29
36
|
this.registerApp(manifestPart);
|
|
@@ -32,9 +39,10 @@ export class ObjectQL {
|
|
|
32
39
|
if (runtimePart) {
|
|
33
40
|
const pluginDef = runtimePart.default || runtimePart;
|
|
34
41
|
if (pluginDef.onEnable) {
|
|
42
|
+
this.logger.debug('Executing plugin runtime onEnable');
|
|
35
43
|
const context = {
|
|
36
44
|
ql: this,
|
|
37
|
-
logger:
|
|
45
|
+
logger: this.logger,
|
|
38
46
|
// Expose the driver registry helper explicitly if needed
|
|
39
47
|
drivers: {
|
|
40
48
|
register: (driver) => this.registerDriver(driver)
|
|
@@ -42,6 +50,7 @@ export class ObjectQL {
|
|
|
42
50
|
...this.hostContext
|
|
43
51
|
};
|
|
44
52
|
await pluginDef.onEnable(context);
|
|
53
|
+
this.logger.debug('Plugin runtime onEnable completed');
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
56
|
}
|
|
@@ -55,44 +64,50 @@ export class ObjectQL {
|
|
|
55
64
|
this.hooks[event] = [];
|
|
56
65
|
}
|
|
57
66
|
this.hooks[event].push(handler);
|
|
58
|
-
|
|
67
|
+
this.logger.debug('Registered hook', { event, totalHandlers: this.hooks[event].length });
|
|
59
68
|
}
|
|
60
69
|
async triggerHooks(event, context) {
|
|
61
70
|
const handlers = this.hooks[event] || [];
|
|
71
|
+
if (handlers.length === 0) {
|
|
72
|
+
this.logger.debug('No hooks registered for event', { event });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.logger.debug('Triggering hooks', { event, count: handlers.length });
|
|
62
76
|
for (const handler of handlers) {
|
|
63
|
-
// In a real system, we might want to catch errors here or allow them to bubble up
|
|
64
77
|
await handler(context);
|
|
65
78
|
}
|
|
66
79
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// In a real scenario, we might strictly parse this using Zod
|
|
75
|
-
// For now, simple ID check
|
|
76
|
-
const id = manifest.id || manifest.name;
|
|
77
|
-
if (!id) {
|
|
78
|
-
console.warn(`[ObjectQL] Plugin manifest missing ID (keys: ${Object.keys(manifest)})`, manifest);
|
|
79
|
-
// Don't return, try to proceed if it looks like an App (Apps might use 'name' instead of 'id')
|
|
80
|
-
// return;
|
|
81
|
-
}
|
|
82
|
-
console.log(`[ObjectQL] Loading App: ${id}`);
|
|
83
|
-
SchemaRegistry.registerPlugin(manifest);
|
|
84
|
-
// Register Objects from App/Plugin
|
|
80
|
+
/**
|
|
81
|
+
* Register contribution (Manifest)
|
|
82
|
+
*/
|
|
83
|
+
registerApp(manifest) {
|
|
84
|
+
const id = manifest.id;
|
|
85
|
+
this.logger.debug('Registering app manifest', { id });
|
|
86
|
+
// Register objects
|
|
85
87
|
if (manifest.objects) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
if (Array.isArray(manifest.objects)) {
|
|
89
|
+
this.logger.debug('Registering objects from manifest (Array)', { id, objectCount: manifest.objects.length });
|
|
90
|
+
for (const objDef of manifest.objects) {
|
|
91
|
+
SchemaRegistry.registerObject(objDef);
|
|
92
|
+
this.logger.debug('Registered Object', { object: objDef.name, from: id });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
this.logger.debug('Registering objects from manifest (Map)', { id, objectCount: Object.keys(manifest.objects).length });
|
|
97
|
+
for (const [name, objDef] of Object.entries(manifest.objects)) {
|
|
98
|
+
// Ensure name in definition matches key
|
|
99
|
+
objDef.name = name;
|
|
100
|
+
SchemaRegistry.registerObject(objDef);
|
|
101
|
+
this.logger.debug('Registered Object', { object: name, from: id });
|
|
102
|
+
}
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
// Register contributions
|
|
93
106
|
if (manifest.contributes?.kinds) {
|
|
107
|
+
this.logger.debug('Registering kinds from manifest', { id, kindCount: manifest.contributes.kinds.length });
|
|
94
108
|
for (const kind of manifest.contributes.kinds) {
|
|
95
109
|
SchemaRegistry.registerKind(kind);
|
|
110
|
+
this.logger.debug('Registered Kind', { kind: kind.name || kind.type, from: id });
|
|
96
111
|
}
|
|
97
112
|
}
|
|
98
113
|
}
|
|
@@ -101,13 +116,17 @@ export class ObjectQL {
|
|
|
101
116
|
*/
|
|
102
117
|
registerDriver(driver, isDefault = false) {
|
|
103
118
|
if (this.drivers.has(driver.name)) {
|
|
104
|
-
|
|
119
|
+
this.logger.warn('Driver already registered, skipping', { driverName: driver.name });
|
|
105
120
|
return;
|
|
106
121
|
}
|
|
107
122
|
this.drivers.set(driver.name, driver);
|
|
108
|
-
|
|
123
|
+
this.logger.info('Registered driver', {
|
|
124
|
+
driverName: driver.name,
|
|
125
|
+
version: driver.version
|
|
126
|
+
});
|
|
109
127
|
if (isDefault || this.drivers.size === 1) {
|
|
110
128
|
this.defaultDriver = driver.name;
|
|
129
|
+
this.logger.info('Set default driver', { driverName: driver.name });
|
|
111
130
|
}
|
|
112
131
|
}
|
|
113
132
|
/**
|
|
@@ -129,25 +148,17 @@ export class ObjectQL {
|
|
|
129
148
|
if (this.defaultDriver && this.drivers.has(this.defaultDriver)) {
|
|
130
149
|
return this.drivers.get(this.defaultDriver);
|
|
131
150
|
}
|
|
132
|
-
// Fallback: If 'default' not explicitly set, use the first available driver?
|
|
133
|
-
// Better to be strict.
|
|
134
151
|
}
|
|
135
152
|
else {
|
|
136
153
|
// Specific datasource requested
|
|
137
154
|
if (this.drivers.has(datasourceName)) {
|
|
138
155
|
return this.drivers.get(datasourceName);
|
|
139
156
|
}
|
|
140
|
-
// If not found, fall back to default? Or error?
|
|
141
|
-
// Standard behavior: Error if specific datasource is missing.
|
|
142
157
|
throw new Error(`[ObjectQL] Datasource '${datasourceName}' configured for object '${objectName}' is not registered.`);
|
|
143
158
|
}
|
|
144
159
|
}
|
|
145
160
|
// 2. Fallback for ad-hoc objects or missing definitions
|
|
146
|
-
// If we have a default driver, use it.
|
|
147
161
|
if (this.defaultDriver) {
|
|
148
|
-
if (!object) {
|
|
149
|
-
console.warn(`[ObjectQL] Object '${objectName}' not found in registry. Using default driver.`);
|
|
150
|
-
}
|
|
151
162
|
return this.drivers.get(this.defaultDriver);
|
|
152
163
|
}
|
|
153
164
|
throw new Error(`[ObjectQL] No driver available for object '${objectName}'`);
|
|
@@ -156,185 +167,257 @@ export class ObjectQL {
|
|
|
156
167
|
* Initialize the engine and all registered drivers
|
|
157
168
|
*/
|
|
158
169
|
async init() {
|
|
159
|
-
|
|
170
|
+
this.logger.info('Initializing ObjectQL engine', {
|
|
171
|
+
driverCount: this.drivers.size,
|
|
172
|
+
drivers: Array.from(this.drivers.keys())
|
|
173
|
+
});
|
|
160
174
|
for (const [name, driver] of this.drivers) {
|
|
161
175
|
try {
|
|
162
176
|
await driver.connect();
|
|
177
|
+
this.logger.info('Driver connected successfully', { driverName: name });
|
|
163
178
|
}
|
|
164
179
|
catch (e) {
|
|
165
|
-
|
|
180
|
+
this.logger.error('Failed to connect driver', e, { driverName: name });
|
|
166
181
|
}
|
|
167
182
|
}
|
|
168
|
-
|
|
183
|
+
this.logger.info('ObjectQL engine initialization complete');
|
|
169
184
|
}
|
|
170
185
|
async destroy() {
|
|
171
|
-
|
|
172
|
-
|
|
186
|
+
this.logger.info('Destroying ObjectQL engine', { driverCount: this.drivers.size });
|
|
187
|
+
for (const [name, driver] of this.drivers.entries()) {
|
|
188
|
+
try {
|
|
189
|
+
await driver.disconnect();
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
this.logger.error('Error disconnecting driver', e, { driverName: name });
|
|
193
|
+
}
|
|
173
194
|
}
|
|
195
|
+
this.logger.info('ObjectQL engine destroyed');
|
|
174
196
|
}
|
|
175
197
|
// ============================================
|
|
176
|
-
//
|
|
198
|
+
// Helper: Query Conversion
|
|
177
199
|
// ============================================
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
ast.
|
|
193
|
-
}
|
|
194
|
-
if (query.select) {
|
|
195
|
-
ast.fields = query.select;
|
|
200
|
+
toQueryAST(object, options) {
|
|
201
|
+
const ast = { object };
|
|
202
|
+
if (!options)
|
|
203
|
+
return ast;
|
|
204
|
+
if (options.filter) {
|
|
205
|
+
ast.where = options.filter;
|
|
206
|
+
}
|
|
207
|
+
if (options.select) {
|
|
208
|
+
ast.fields = options.select;
|
|
209
|
+
}
|
|
210
|
+
if (options.sort) {
|
|
211
|
+
// Support DataEngineSortSchema variant
|
|
212
|
+
if (Array.isArray(options.sort)) {
|
|
213
|
+
// [{ field: 'a', order: 'asc' }]
|
|
214
|
+
ast.orderBy = options.sort;
|
|
196
215
|
}
|
|
197
|
-
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
ast.orderBy = Object.entries(query.sort).map(([field, order]) => ({
|
|
216
|
+
else {
|
|
217
|
+
// Record<string, 'asc' | 'desc' | 1 | -1>
|
|
218
|
+
ast.orderBy = Object.entries(options.sort).map(([field, order]) => ({
|
|
201
219
|
field,
|
|
202
220
|
order: (order === -1 || order === 'desc') ? 'desc' : 'asc'
|
|
203
221
|
}));
|
|
204
222
|
}
|
|
205
|
-
// Handle both limit and top (top takes precedence)
|
|
206
|
-
if (query.top !== undefined) {
|
|
207
|
-
ast.limit = query.top;
|
|
208
|
-
}
|
|
209
|
-
else if (query.limit !== undefined) {
|
|
210
|
-
ast.limit = query.limit;
|
|
211
|
-
}
|
|
212
|
-
if (query.skip !== undefined) {
|
|
213
|
-
ast.offset = query.skip;
|
|
214
|
-
}
|
|
215
223
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
224
|
+
if (options.top !== undefined)
|
|
225
|
+
ast.limit = options.top;
|
|
226
|
+
else if (options.limit !== undefined)
|
|
227
|
+
ast.limit = options.limit;
|
|
228
|
+
if (options.skip !== undefined)
|
|
229
|
+
ast.offset = options.skip;
|
|
230
|
+
// TODO: Handle populate/joins mapping if Driver supports it in QueryAST
|
|
231
|
+
return ast;
|
|
232
|
+
}
|
|
233
|
+
// ============================================
|
|
234
|
+
// Data Access Methods (IDataEngine Interface)
|
|
235
|
+
// ============================================
|
|
236
|
+
async find(object, query) {
|
|
237
|
+
this.logger.debug('Find operation starting', { object, query });
|
|
238
|
+
const driver = this.getDriver(object);
|
|
239
|
+
const ast = this.toQueryAST(object, query);
|
|
220
240
|
const hookContext = {
|
|
221
241
|
object,
|
|
222
242
|
event: 'beforeFind',
|
|
223
|
-
input: { ast, options: undefined },
|
|
243
|
+
input: { ast, options: undefined }, // Should map options?
|
|
224
244
|
ql: this
|
|
225
245
|
};
|
|
226
246
|
await this.triggerHooks('beforeFind', hookContext);
|
|
227
247
|
try {
|
|
228
248
|
const result = await driver.find(object, hookContext.input.ast, hookContext.input.options);
|
|
229
|
-
// Trigger After Hook
|
|
230
249
|
hookContext.event = 'afterFind';
|
|
231
250
|
hookContext.result = result;
|
|
232
251
|
await this.triggerHooks('afterFind', hookContext);
|
|
233
252
|
return hookContext.result;
|
|
234
253
|
}
|
|
235
254
|
catch (e) {
|
|
236
|
-
|
|
255
|
+
this.logger.error('Find operation failed', e, { object });
|
|
237
256
|
throw e;
|
|
238
257
|
}
|
|
239
258
|
}
|
|
240
|
-
async findOne(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
ast = {
|
|
245
|
-
object,
|
|
246
|
-
where: { _id: idOrQuery }
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
// Assume query object
|
|
251
|
-
// reuse logic from find() or just wrap it
|
|
252
|
-
if (idOrQuery.where || idOrQuery.fields) {
|
|
253
|
-
ast = { object, ...idOrQuery };
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
ast = { object, where: idOrQuery };
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
// Limit 1 for findOne
|
|
259
|
+
async findOne(objectName, query) {
|
|
260
|
+
this.logger.debug('FindOne operation', { objectName });
|
|
261
|
+
const driver = this.getDriver(objectName);
|
|
262
|
+
const ast = this.toQueryAST(objectName, query);
|
|
260
263
|
ast.limit = 1;
|
|
261
|
-
|
|
264
|
+
// Reuse find logic or call generic driver.findOne if available
|
|
265
|
+
// Assuming driver has findOne
|
|
266
|
+
return driver.findOne(objectName, ast);
|
|
262
267
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
*
|
|
266
|
-
* @param object - Object name
|
|
267
|
-
* @param data - Data to insert
|
|
268
|
-
* @returns Promise resolving to the created record
|
|
269
|
-
*/
|
|
270
|
-
async insert(object, data) {
|
|
268
|
+
async insert(object, data, options) {
|
|
269
|
+
this.logger.debug('Insert operation starting', { object, isBatch: Array.isArray(data) });
|
|
271
270
|
const driver = this.getDriver(object);
|
|
272
|
-
// 1. Get Schema
|
|
273
|
-
const schema = SchemaRegistry.getObject(object);
|
|
274
|
-
if (schema) {
|
|
275
|
-
// TODO: Validation Logic
|
|
276
|
-
// validate(schema, data);
|
|
277
|
-
}
|
|
278
|
-
// 2. Trigger Before Hook
|
|
279
271
|
const hookContext = {
|
|
280
272
|
object,
|
|
281
273
|
event: 'beforeInsert',
|
|
282
|
-
input: { data, options
|
|
274
|
+
input: { data, options },
|
|
283
275
|
ql: this
|
|
284
276
|
};
|
|
285
277
|
await this.triggerHooks('beforeInsert', hookContext);
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
278
|
+
try {
|
|
279
|
+
let result;
|
|
280
|
+
if (Array.isArray(hookContext.input.data)) {
|
|
281
|
+
// Bulk Create
|
|
282
|
+
if (driver.bulkCreate) {
|
|
283
|
+
result = await driver.bulkCreate(object, hookContext.input.data, hookContext.input.options);
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// Fallback loop
|
|
287
|
+
result = await Promise.all(hookContext.input.data.map((item) => driver.create(object, item, hookContext.input.options)));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
result = await driver.create(object, hookContext.input.data, hookContext.input.options);
|
|
292
|
+
}
|
|
293
|
+
hookContext.event = 'afterInsert';
|
|
294
|
+
hookContext.result = result;
|
|
295
|
+
await this.triggerHooks('afterInsert', hookContext);
|
|
296
|
+
return hookContext.result;
|
|
297
|
+
}
|
|
298
|
+
catch (e) {
|
|
299
|
+
this.logger.error('Insert operation failed', e, { object });
|
|
300
|
+
throw e;
|
|
301
|
+
}
|
|
293
302
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
* @param id - Record ID
|
|
299
|
-
* @param data - Updated data
|
|
300
|
-
* @returns Promise resolving to the updated record
|
|
301
|
-
*/
|
|
302
|
-
async update(object, id, data) {
|
|
303
|
+
async update(object, data, options) {
|
|
304
|
+
// NOTE: This signature is tricky because Driver expects (obj, id, data) usually.
|
|
305
|
+
// DataEngine protocol puts filter in options.
|
|
306
|
+
this.logger.debug('Update operation starting', { object });
|
|
303
307
|
const driver = this.getDriver(object);
|
|
308
|
+
// 1. Extract ID from data or filter if it's a single update by ID
|
|
309
|
+
// This is a simplification. Real implementation needs robust filter handling.
|
|
310
|
+
let id = data.id || data._id;
|
|
311
|
+
if (!id && options?.filter) {
|
|
312
|
+
// Optimization: If filter is simple ID check, extract it
|
|
313
|
+
if (typeof options.filter === 'string')
|
|
314
|
+
id = options.filter;
|
|
315
|
+
else if (options.filter._id)
|
|
316
|
+
id = options.filter._id;
|
|
317
|
+
else if (options.filter.id)
|
|
318
|
+
id = options.filter.id;
|
|
319
|
+
}
|
|
304
320
|
const hookContext = {
|
|
305
321
|
object,
|
|
306
322
|
event: 'beforeUpdate',
|
|
307
|
-
input: { id, data, options
|
|
323
|
+
input: { id, data, options },
|
|
308
324
|
ql: this
|
|
309
325
|
};
|
|
310
326
|
await this.triggerHooks('beforeUpdate', hookContext);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
327
|
+
try {
|
|
328
|
+
let result;
|
|
329
|
+
if (hookContext.input.id) {
|
|
330
|
+
// Single update by ID
|
|
331
|
+
result = await driver.update(object, hookContext.input.id, hookContext.input.data, hookContext.input.options);
|
|
332
|
+
}
|
|
333
|
+
else if (options?.multi && driver.updateMany) {
|
|
334
|
+
// Bulk update by Query
|
|
335
|
+
const ast = this.toQueryAST(object, { filter: options.filter });
|
|
336
|
+
result = await driver.updateMany(object, ast, hookContext.input.data, hookContext.input.options);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
throw new Error('Update requires an ID or options.multi=true');
|
|
340
|
+
}
|
|
341
|
+
hookContext.event = 'afterUpdate';
|
|
342
|
+
hookContext.result = result;
|
|
343
|
+
await this.triggerHooks('afterUpdate', hookContext);
|
|
344
|
+
return hookContext.result;
|
|
345
|
+
}
|
|
346
|
+
catch (e) {
|
|
347
|
+
this.logger.error('Update operation failed', e, { object });
|
|
348
|
+
throw e;
|
|
349
|
+
}
|
|
316
350
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
*
|
|
320
|
-
* @param object - Object name
|
|
321
|
-
* @param id - Record ID
|
|
322
|
-
* @returns Promise resolving to true if deleted, false otherwise
|
|
323
|
-
*/
|
|
324
|
-
async delete(object, id) {
|
|
351
|
+
async delete(object, options) {
|
|
352
|
+
this.logger.debug('Delete operation starting', { object });
|
|
325
353
|
const driver = this.getDriver(object);
|
|
354
|
+
// Extract ID logic similar to update
|
|
355
|
+
let id = undefined;
|
|
356
|
+
if (options?.filter) {
|
|
357
|
+
if (typeof options.filter === 'string')
|
|
358
|
+
id = options.filter;
|
|
359
|
+
else if (options.filter._id)
|
|
360
|
+
id = options.filter._id;
|
|
361
|
+
else if (options.filter.id)
|
|
362
|
+
id = options.filter.id;
|
|
363
|
+
}
|
|
326
364
|
const hookContext = {
|
|
327
365
|
object,
|
|
328
366
|
event: 'beforeDelete',
|
|
329
|
-
input: { id, options
|
|
367
|
+
input: { id, options },
|
|
330
368
|
ql: this
|
|
331
369
|
};
|
|
332
370
|
await this.triggerHooks('beforeDelete', hookContext);
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
371
|
+
try {
|
|
372
|
+
let result;
|
|
373
|
+
if (hookContext.input.id) {
|
|
374
|
+
result = await driver.delete(object, hookContext.input.id, hookContext.input.options);
|
|
375
|
+
}
|
|
376
|
+
else if (options?.multi && driver.deleteMany) {
|
|
377
|
+
const ast = this.toQueryAST(object, { filter: options.filter });
|
|
378
|
+
result = await driver.deleteMany(object, ast, hookContext.input.options);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
throw new Error('Delete requires an ID or options.multi=true');
|
|
382
|
+
}
|
|
383
|
+
hookContext.event = 'afterDelete';
|
|
384
|
+
hookContext.result = result;
|
|
385
|
+
await this.triggerHooks('afterDelete', hookContext);
|
|
386
|
+
return hookContext.result;
|
|
387
|
+
}
|
|
388
|
+
catch (e) {
|
|
389
|
+
this.logger.error('Delete operation failed', e, { object });
|
|
390
|
+
throw e;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
async count(object, query) {
|
|
394
|
+
const driver = this.getDriver(object);
|
|
395
|
+
if (driver.count) {
|
|
396
|
+
const ast = this.toQueryAST(object, { filter: query?.filter });
|
|
397
|
+
return driver.count(object, ast);
|
|
398
|
+
}
|
|
399
|
+
// Fallback to find().length
|
|
400
|
+
const res = await this.find(object, { filter: query?.filter, select: ['_id'] });
|
|
401
|
+
return res.length;
|
|
402
|
+
}
|
|
403
|
+
async aggregate(object, query) {
|
|
404
|
+
const driver = this.getDriver(object);
|
|
405
|
+
this.logger.debug(`Aggregate on ${object} using ${driver.name}`, query);
|
|
406
|
+
// Driver needs support for raw aggregation or mapped aggregation
|
|
407
|
+
// For now, if driver supports 'execute', we might pass it down, or we need to add 'aggregate' to DriverInterface
|
|
408
|
+
// In this version, we'll assume driver might handle it via special 'find' or throw not implemented
|
|
409
|
+
throw new Error('Aggregate not yet fully implemented in ObjectQL->Driver mapping');
|
|
410
|
+
}
|
|
411
|
+
async execute(command, options) {
|
|
412
|
+
// Direct pass-through implies we know which driver to use?
|
|
413
|
+
// Usually execute is tied to a specific object context OR we need a way to select driver.
|
|
414
|
+
// If command has 'object', we use that.
|
|
415
|
+
if (options?.object) {
|
|
416
|
+
const driver = this.getDriver(options.object);
|
|
417
|
+
if (driver.execute) {
|
|
418
|
+
return driver.execute(command, undefined, options);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
throw new Error('Execute requires options.object to select driver');
|
|
339
422
|
}
|
|
340
423
|
}
|
package/dist/plugin.js
CHANGED
|
@@ -18,20 +18,17 @@ export class ObjectQLPlugin {
|
|
|
18
18
|
this.ql = new ObjectQL(this.hostContext);
|
|
19
19
|
}
|
|
20
20
|
ctx.registerService('objectql', this.ql);
|
|
21
|
-
|
|
22
|
-
ctx.logger.log(`[ObjectQLPlugin] ObjectQL engine registered as service`);
|
|
21
|
+
ctx.logger.info('ObjectQL engine registered as service');
|
|
23
22
|
// Register Protocol Implementation
|
|
24
23
|
if (!this.ql) {
|
|
25
24
|
throw new Error('ObjectQL engine not initialized');
|
|
26
25
|
}
|
|
27
26
|
const protocolShim = new ObjectStackProtocolImplementation(this.ql);
|
|
28
27
|
ctx.registerService('protocol', protocolShim);
|
|
29
|
-
|
|
30
|
-
ctx.logger.log(`[ObjectQLPlugin] Protocol service registered`);
|
|
28
|
+
ctx.logger.info('Protocol service registered');
|
|
31
29
|
}
|
|
32
30
|
async start(ctx) {
|
|
33
|
-
|
|
34
|
-
ctx.logger.log(`[ObjectQLPlugin] ObjectQL engine initialized`);
|
|
31
|
+
ctx.logger.info('ObjectQL engine initialized');
|
|
35
32
|
// Discover features from Kernel Services
|
|
36
33
|
if (ctx.getServices && this.ql) {
|
|
37
34
|
const services = ctx.getServices();
|
|
@@ -39,14 +36,12 @@ export class ObjectQLPlugin {
|
|
|
39
36
|
if (name.startsWith('driver.')) {
|
|
40
37
|
// Register Driver
|
|
41
38
|
this.ql.registerDriver(service);
|
|
42
|
-
|
|
43
|
-
ctx.logger.log(`[ObjectQLPlugin] Discovered and registered driver service: ${name}`);
|
|
39
|
+
ctx.logger.debug('Discovered and registered driver service', { serviceName: name });
|
|
44
40
|
}
|
|
45
41
|
if (name.startsWith('app.')) {
|
|
46
42
|
// Register App
|
|
47
43
|
this.ql.registerApp(service); // service is Manifest
|
|
48
|
-
|
|
49
|
-
ctx.logger.log(`[ObjectQLPlugin] Discovered and registered app service: ${name}`);
|
|
44
|
+
ctx.logger.debug('Discovered and registered app service', { serviceName: name });
|
|
50
45
|
}
|
|
51
46
|
}
|
|
52
47
|
}
|