@objectstack/objectql 1.0.2 → 1.0.5
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 +22 -0
- package/CHANGELOG.md +42 -0
- package/dist/{registry.d.ts → index.d.mts} +433 -3
- package/dist/index.d.ts +999 -6
- package/dist/index.js +798 -9
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +768 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +5 -5
- package/src/plugin.ts +8 -4
- package/tsconfig.json +1 -3
- package/dist/engine.d.ts +0 -304
- package/dist/engine.d.ts.map +0 -1
- package/dist/engine.js +0 -445
- package/dist/index.d.ts.map +0 -1
- package/dist/plugin.d.ts +0 -14
- package/dist/plugin.d.ts.map +0 -1
- package/dist/plugin.js +0 -49
- package/dist/protocol.d.ts +0 -119
- package/dist/protocol.d.ts.map +0 -1
- package/dist/protocol.js +0 -247
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js +0 -119
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
// src/registry.ts
|
|
2
|
+
import { ObjectSchema } from "@objectstack/spec/data";
|
|
3
|
+
import { ManifestSchema } from "@objectstack/spec/kernel";
|
|
4
|
+
import { AppSchema } from "@objectstack/spec/ui";
|
|
5
|
+
var SchemaRegistry = class {
|
|
6
|
+
/**
|
|
7
|
+
* Universal Register Method
|
|
8
|
+
* @param type The category of metadata (e.g., 'object', 'app', 'plugin')
|
|
9
|
+
* @param item The metadata item itself
|
|
10
|
+
* @param keyField The property to use as the unique key (default: 'name')
|
|
11
|
+
*/
|
|
12
|
+
static registerItem(type, item, keyField = "name") {
|
|
13
|
+
if (!this.metadata.has(type)) {
|
|
14
|
+
this.metadata.set(type, /* @__PURE__ */ new Map());
|
|
15
|
+
}
|
|
16
|
+
const collection = this.metadata.get(type);
|
|
17
|
+
const key = String(item[keyField]);
|
|
18
|
+
try {
|
|
19
|
+
this.validate(type, item);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.error(`[Registry] Validation failed for ${type} ${key}: ${e.message}`);
|
|
22
|
+
}
|
|
23
|
+
if (collection.has(key)) {
|
|
24
|
+
console.warn(`[Registry] Overwriting ${type}: ${key}`);
|
|
25
|
+
}
|
|
26
|
+
collection.set(key, item);
|
|
27
|
+
console.log(`[Registry] Registered ${type}: ${key}`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validate Metadata against Spec Zod Schemas
|
|
31
|
+
*/
|
|
32
|
+
static validate(type, item) {
|
|
33
|
+
if (type === "object") {
|
|
34
|
+
return ObjectSchema.parse(item);
|
|
35
|
+
}
|
|
36
|
+
if (type === "app") {
|
|
37
|
+
return AppSchema.parse(item);
|
|
38
|
+
}
|
|
39
|
+
if (type === "plugin") {
|
|
40
|
+
return ManifestSchema.parse(item);
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Universal Unregister Method
|
|
46
|
+
*/
|
|
47
|
+
static unregisterItem(type, name) {
|
|
48
|
+
const collection = this.metadata.get(type);
|
|
49
|
+
if (collection && collection.has(name)) {
|
|
50
|
+
collection.delete(name);
|
|
51
|
+
console.log(`[Registry] Unregistered ${type}: ${name}`);
|
|
52
|
+
} else {
|
|
53
|
+
console.warn(`[Registry] Attempted to unregister non-existent ${type}: ${name}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Universal Get Method
|
|
58
|
+
*/
|
|
59
|
+
static getItem(type, name) {
|
|
60
|
+
return this.metadata.get(type)?.get(name);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Universal List Method
|
|
64
|
+
*/
|
|
65
|
+
static listItems(type) {
|
|
66
|
+
return Array.from(this.metadata.get(type)?.values() || []);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get all registered metadata types (Kinds)
|
|
70
|
+
*/
|
|
71
|
+
static getRegisteredTypes() {
|
|
72
|
+
return Array.from(this.metadata.keys());
|
|
73
|
+
}
|
|
74
|
+
// ==========================================
|
|
75
|
+
// Typed Helper Methods (Shortcuts)
|
|
76
|
+
// ==========================================
|
|
77
|
+
/**
|
|
78
|
+
* Object Helpers
|
|
79
|
+
*/
|
|
80
|
+
static registerObject(schema) {
|
|
81
|
+
this.registerItem("object", schema, "name");
|
|
82
|
+
}
|
|
83
|
+
static getObject(name) {
|
|
84
|
+
return this.getItem("object", name);
|
|
85
|
+
}
|
|
86
|
+
static getAllObjects() {
|
|
87
|
+
return this.listItems("object");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Plugin Helpers
|
|
91
|
+
*/
|
|
92
|
+
static registerPlugin(manifest) {
|
|
93
|
+
this.registerItem("plugin", manifest, "id");
|
|
94
|
+
}
|
|
95
|
+
static getAllPlugins() {
|
|
96
|
+
return this.listItems("plugin");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Kind (Metadata Type) Helpers
|
|
100
|
+
*/
|
|
101
|
+
static registerKind(kind) {
|
|
102
|
+
this.registerItem("kind", kind, "id");
|
|
103
|
+
}
|
|
104
|
+
static getAllKinds() {
|
|
105
|
+
return this.listItems("kind");
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
// Nested Map: Type -> Name/ID -> MetadataItem
|
|
109
|
+
SchemaRegistry.metadata = /* @__PURE__ */ new Map();
|
|
110
|
+
|
|
111
|
+
// src/protocol.ts
|
|
112
|
+
function simpleHash(str) {
|
|
113
|
+
let hash = 0;
|
|
114
|
+
for (let i = 0; i < str.length; i++) {
|
|
115
|
+
const char = str.charCodeAt(i);
|
|
116
|
+
hash = (hash << 5) - hash + char;
|
|
117
|
+
hash = hash & hash;
|
|
118
|
+
}
|
|
119
|
+
return Math.abs(hash).toString(16);
|
|
120
|
+
}
|
|
121
|
+
var ObjectStackProtocolImplementation = class {
|
|
122
|
+
constructor(engine) {
|
|
123
|
+
this.engine = engine;
|
|
124
|
+
}
|
|
125
|
+
async getDiscovery(_request) {
|
|
126
|
+
return {
|
|
127
|
+
version: "1.0",
|
|
128
|
+
apiName: "ObjectStack API",
|
|
129
|
+
capabilities: {
|
|
130
|
+
graphql: false,
|
|
131
|
+
search: false,
|
|
132
|
+
websockets: false,
|
|
133
|
+
files: true,
|
|
134
|
+
analytics: false,
|
|
135
|
+
hub: false
|
|
136
|
+
},
|
|
137
|
+
endpoints: {
|
|
138
|
+
data: "/api/data",
|
|
139
|
+
metadata: "/api/meta",
|
|
140
|
+
auth: "/api/auth"
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async getMetaTypes(_request) {
|
|
145
|
+
return {
|
|
146
|
+
types: SchemaRegistry.getRegisteredTypes()
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
async getMetaItems(request) {
|
|
150
|
+
return {
|
|
151
|
+
type: request.type,
|
|
152
|
+
items: SchemaRegistry.listItems(request.type)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
async getMetaItem(request) {
|
|
156
|
+
return {
|
|
157
|
+
type: request.type,
|
|
158
|
+
name: request.name,
|
|
159
|
+
item: SchemaRegistry.getItem(request.type, request.name)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async getUiView(request) {
|
|
163
|
+
const schema = SchemaRegistry.getObject(request.object);
|
|
164
|
+
if (!schema) throw new Error(`Object ${request.object} not found`);
|
|
165
|
+
let view;
|
|
166
|
+
if (request.type === "list") {
|
|
167
|
+
view = {
|
|
168
|
+
type: "list",
|
|
169
|
+
object: request.object,
|
|
170
|
+
columns: Object.keys(schema.fields || {}).slice(0, 5).map((f) => ({
|
|
171
|
+
field: f,
|
|
172
|
+
label: schema.fields[f].label || f
|
|
173
|
+
}))
|
|
174
|
+
};
|
|
175
|
+
} else {
|
|
176
|
+
view = {
|
|
177
|
+
type: "form",
|
|
178
|
+
object: request.object,
|
|
179
|
+
sections: [
|
|
180
|
+
{
|
|
181
|
+
label: "General",
|
|
182
|
+
fields: Object.keys(schema.fields || {}).map((f) => ({
|
|
183
|
+
field: f
|
|
184
|
+
}))
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return view;
|
|
190
|
+
}
|
|
191
|
+
async findData(request) {
|
|
192
|
+
const options = { ...request.query };
|
|
193
|
+
if (options.top) options.top = Number(options.top);
|
|
194
|
+
if (options.skip) options.skip = Number(options.skip);
|
|
195
|
+
if (options.limit) options.limit = Number(options.limit);
|
|
196
|
+
const records = await this.engine.find(request.object, options);
|
|
197
|
+
return {
|
|
198
|
+
object: request.object,
|
|
199
|
+
value: records,
|
|
200
|
+
// OData compaibility
|
|
201
|
+
records,
|
|
202
|
+
// Legacy
|
|
203
|
+
total: records.length,
|
|
204
|
+
hasMore: false
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
async getData(request) {
|
|
208
|
+
const result = await this.engine.findOne(request.object, {
|
|
209
|
+
filter: { _id: request.id }
|
|
210
|
+
});
|
|
211
|
+
if (result) {
|
|
212
|
+
return {
|
|
213
|
+
object: request.object,
|
|
214
|
+
id: request.id,
|
|
215
|
+
record: result
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
throw new Error(`Record ${request.id} not found in ${request.object}`);
|
|
219
|
+
}
|
|
220
|
+
async createData(request) {
|
|
221
|
+
const result = await this.engine.insert(request.object, request.data);
|
|
222
|
+
return {
|
|
223
|
+
object: request.object,
|
|
224
|
+
id: result._id || result.id,
|
|
225
|
+
record: result
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
async updateData(request) {
|
|
229
|
+
const result = await this.engine.update(request.object, request.data, { filter: { _id: request.id } });
|
|
230
|
+
return {
|
|
231
|
+
object: request.object,
|
|
232
|
+
id: request.id,
|
|
233
|
+
record: result
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
async deleteData(request) {
|
|
237
|
+
await this.engine.delete(request.object, { filter: { _id: request.id } });
|
|
238
|
+
return {
|
|
239
|
+
object: request.object,
|
|
240
|
+
id: request.id,
|
|
241
|
+
success: true
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
// ==========================================
|
|
245
|
+
// Metadata Caching
|
|
246
|
+
// ==========================================
|
|
247
|
+
async getMetaItemCached(request) {
|
|
248
|
+
try {
|
|
249
|
+
const item = SchemaRegistry.getItem(request.type, request.name);
|
|
250
|
+
if (!item) {
|
|
251
|
+
throw new Error(`Metadata item ${request.type}/${request.name} not found`);
|
|
252
|
+
}
|
|
253
|
+
const content = JSON.stringify(item);
|
|
254
|
+
const hash = simpleHash(content);
|
|
255
|
+
const etag = { value: hash, weak: false };
|
|
256
|
+
if (request.cacheRequest?.ifNoneMatch) {
|
|
257
|
+
const clientEtag = request.cacheRequest.ifNoneMatch.replace(/^"(.*)"$/, "$1").replace(/^W\/"(.*)"$/, "$1");
|
|
258
|
+
if (clientEtag === hash) {
|
|
259
|
+
return {
|
|
260
|
+
notModified: true,
|
|
261
|
+
etag
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
data: item,
|
|
267
|
+
etag,
|
|
268
|
+
lastModified: (/* @__PURE__ */ new Date()).toISOString(),
|
|
269
|
+
cacheControl: {
|
|
270
|
+
directives: ["public", "max-age"],
|
|
271
|
+
maxAge: 3600
|
|
272
|
+
// 1 hour
|
|
273
|
+
},
|
|
274
|
+
notModified: false
|
|
275
|
+
};
|
|
276
|
+
} catch (error) {
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// ==========================================
|
|
281
|
+
// Batch Operations
|
|
282
|
+
// ==========================================
|
|
283
|
+
async batchData(_request) {
|
|
284
|
+
throw new Error("Batch operations not yet fully implemented in protocol adapter");
|
|
285
|
+
}
|
|
286
|
+
async createManyData(request) {
|
|
287
|
+
const records = await this.engine.insert(request.object, request.records);
|
|
288
|
+
return {
|
|
289
|
+
object: request.object,
|
|
290
|
+
records,
|
|
291
|
+
count: records.length
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async updateManyData(_request) {
|
|
295
|
+
throw new Error("updateManyData not implemented");
|
|
296
|
+
}
|
|
297
|
+
async analyticsQuery(_request) {
|
|
298
|
+
throw new Error("analyticsQuery not implemented");
|
|
299
|
+
}
|
|
300
|
+
async getAnalyticsMeta(_request) {
|
|
301
|
+
throw new Error("getAnalyticsMeta not implemented");
|
|
302
|
+
}
|
|
303
|
+
async triggerAutomation(_request) {
|
|
304
|
+
throw new Error("triggerAutomation not implemented");
|
|
305
|
+
}
|
|
306
|
+
async listSpaces(_request) {
|
|
307
|
+
throw new Error("listSpaces not implemented");
|
|
308
|
+
}
|
|
309
|
+
async createSpace(_request) {
|
|
310
|
+
throw new Error("createSpace not implemented");
|
|
311
|
+
}
|
|
312
|
+
async installPlugin(_request) {
|
|
313
|
+
throw new Error("installPlugin not implemented");
|
|
314
|
+
}
|
|
315
|
+
async deleteManyData(request) {
|
|
316
|
+
return this.engine.delete(request.object, {
|
|
317
|
+
filter: { _id: { $in: request.ids } },
|
|
318
|
+
...request.options
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
async saveMetaItem(request) {
|
|
322
|
+
if (!request.item) {
|
|
323
|
+
throw new Error("Item data is required");
|
|
324
|
+
}
|
|
325
|
+
SchemaRegistry.registerItem(request.type, request.item, "name");
|
|
326
|
+
return {
|
|
327
|
+
success: true,
|
|
328
|
+
message: "Saved to memory registry"
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// src/engine.ts
|
|
334
|
+
import { createLogger } from "@objectstack/core";
|
|
335
|
+
import { CoreServiceName } from "@objectstack/spec/system";
|
|
336
|
+
var ObjectQL = class {
|
|
337
|
+
constructor(hostContext = {}) {
|
|
338
|
+
this.drivers = /* @__PURE__ */ new Map();
|
|
339
|
+
this.defaultDriver = null;
|
|
340
|
+
// Hooks Registry
|
|
341
|
+
this.hooks = {
|
|
342
|
+
"beforeFind": [],
|
|
343
|
+
"afterFind": [],
|
|
344
|
+
"beforeInsert": [],
|
|
345
|
+
"afterInsert": [],
|
|
346
|
+
"beforeUpdate": [],
|
|
347
|
+
"afterUpdate": [],
|
|
348
|
+
"beforeDelete": [],
|
|
349
|
+
"afterDelete": []
|
|
350
|
+
};
|
|
351
|
+
// Host provided context additions (e.g. Server router)
|
|
352
|
+
this.hostContext = {};
|
|
353
|
+
this.hostContext = hostContext;
|
|
354
|
+
this.logger = hostContext.logger || createLogger({ level: "info", format: "pretty" });
|
|
355
|
+
this.logger.info("ObjectQL Engine Instance Created");
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Service Status Report
|
|
359
|
+
* Used by Kernel to verify health and capabilities.
|
|
360
|
+
*/
|
|
361
|
+
getStatus() {
|
|
362
|
+
return {
|
|
363
|
+
name: CoreServiceName.enum.data,
|
|
364
|
+
status: "running",
|
|
365
|
+
version: "0.9.0",
|
|
366
|
+
features: ["crud", "query", "aggregate", "transactions", "metadata"]
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Expose the SchemaRegistry for plugins to register metadata
|
|
371
|
+
*/
|
|
372
|
+
get registry() {
|
|
373
|
+
return SchemaRegistry;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Load and Register a Plugin
|
|
377
|
+
*/
|
|
378
|
+
async use(manifestPart, runtimePart) {
|
|
379
|
+
this.logger.debug("Loading plugin", {
|
|
380
|
+
hasManifest: !!manifestPart,
|
|
381
|
+
hasRuntime: !!runtimePart
|
|
382
|
+
});
|
|
383
|
+
if (manifestPart) {
|
|
384
|
+
this.registerApp(manifestPart);
|
|
385
|
+
}
|
|
386
|
+
if (runtimePart) {
|
|
387
|
+
const pluginDef = runtimePart.default || runtimePart;
|
|
388
|
+
if (pluginDef.onEnable) {
|
|
389
|
+
this.logger.debug("Executing plugin runtime onEnable");
|
|
390
|
+
const context = {
|
|
391
|
+
ql: this,
|
|
392
|
+
logger: this.logger,
|
|
393
|
+
// Expose the driver registry helper explicitly if needed
|
|
394
|
+
drivers: {
|
|
395
|
+
register: (driver) => this.registerDriver(driver)
|
|
396
|
+
},
|
|
397
|
+
...this.hostContext
|
|
398
|
+
};
|
|
399
|
+
await pluginDef.onEnable(context);
|
|
400
|
+
this.logger.debug("Plugin runtime onEnable completed");
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Register a hook
|
|
406
|
+
* @param event The event name (e.g. 'beforeFind', 'afterInsert')
|
|
407
|
+
* @param handler The handler function
|
|
408
|
+
*/
|
|
409
|
+
registerHook(event, handler) {
|
|
410
|
+
if (!this.hooks[event]) {
|
|
411
|
+
this.hooks[event] = [];
|
|
412
|
+
}
|
|
413
|
+
this.hooks[event].push(handler);
|
|
414
|
+
this.logger.debug("Registered hook", { event, totalHandlers: this.hooks[event].length });
|
|
415
|
+
}
|
|
416
|
+
async triggerHooks(event, context) {
|
|
417
|
+
const handlers = this.hooks[event] || [];
|
|
418
|
+
if (handlers.length === 0) {
|
|
419
|
+
this.logger.debug("No hooks registered for event", { event });
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
this.logger.debug("Triggering hooks", { event, count: handlers.length });
|
|
423
|
+
for (const handler of handlers) {
|
|
424
|
+
await handler(context);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Register contribution (Manifest)
|
|
429
|
+
*/
|
|
430
|
+
registerApp(manifest) {
|
|
431
|
+
const id = manifest.id;
|
|
432
|
+
this.logger.debug("Registering app manifest", { id });
|
|
433
|
+
if (manifest.objects) {
|
|
434
|
+
if (Array.isArray(manifest.objects)) {
|
|
435
|
+
this.logger.debug("Registering objects from manifest (Array)", { id, objectCount: manifest.objects.length });
|
|
436
|
+
for (const objDef of manifest.objects) {
|
|
437
|
+
SchemaRegistry.registerObject(objDef);
|
|
438
|
+
this.logger.debug("Registered Object", { object: objDef.name, from: id });
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
this.logger.debug("Registering objects from manifest (Map)", { id, objectCount: Object.keys(manifest.objects).length });
|
|
442
|
+
for (const [name, objDef] of Object.entries(manifest.objects)) {
|
|
443
|
+
objDef.name = name;
|
|
444
|
+
SchemaRegistry.registerObject(objDef);
|
|
445
|
+
this.logger.debug("Registered Object", { object: name, from: id });
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (manifest.contributes?.kinds) {
|
|
450
|
+
this.logger.debug("Registering kinds from manifest", { id, kindCount: manifest.contributes.kinds.length });
|
|
451
|
+
for (const kind of manifest.contributes.kinds) {
|
|
452
|
+
SchemaRegistry.registerKind(kind);
|
|
453
|
+
this.logger.debug("Registered Kind", { kind: kind.name || kind.type, from: id });
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Register a new storage driver
|
|
459
|
+
*/
|
|
460
|
+
registerDriver(driver, isDefault = false) {
|
|
461
|
+
if (this.drivers.has(driver.name)) {
|
|
462
|
+
this.logger.warn("Driver already registered, skipping", { driverName: driver.name });
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
this.drivers.set(driver.name, driver);
|
|
466
|
+
this.logger.info("Registered driver", {
|
|
467
|
+
driverName: driver.name,
|
|
468
|
+
version: driver.version
|
|
469
|
+
});
|
|
470
|
+
if (isDefault || this.drivers.size === 1) {
|
|
471
|
+
this.defaultDriver = driver.name;
|
|
472
|
+
this.logger.info("Set default driver", { driverName: driver.name });
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Helper to get object definition
|
|
477
|
+
*/
|
|
478
|
+
getSchema(objectName) {
|
|
479
|
+
return SchemaRegistry.getObject(objectName);
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Helper to get the target driver
|
|
483
|
+
*/
|
|
484
|
+
getDriver(objectName) {
|
|
485
|
+
const object = SchemaRegistry.getObject(objectName);
|
|
486
|
+
if (object) {
|
|
487
|
+
const datasourceName = object.datasource || "default";
|
|
488
|
+
if (datasourceName === "default") {
|
|
489
|
+
if (this.defaultDriver && this.drivers.has(this.defaultDriver)) {
|
|
490
|
+
return this.drivers.get(this.defaultDriver);
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
if (this.drivers.has(datasourceName)) {
|
|
494
|
+
return this.drivers.get(datasourceName);
|
|
495
|
+
}
|
|
496
|
+
throw new Error(`[ObjectQL] Datasource '${datasourceName}' configured for object '${objectName}' is not registered.`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
if (this.defaultDriver) {
|
|
500
|
+
return this.drivers.get(this.defaultDriver);
|
|
501
|
+
}
|
|
502
|
+
throw new Error(`[ObjectQL] No driver available for object '${objectName}'`);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Initialize the engine and all registered drivers
|
|
506
|
+
*/
|
|
507
|
+
async init() {
|
|
508
|
+
this.logger.info("Initializing ObjectQL engine", {
|
|
509
|
+
driverCount: this.drivers.size,
|
|
510
|
+
drivers: Array.from(this.drivers.keys())
|
|
511
|
+
});
|
|
512
|
+
for (const [name, driver] of this.drivers) {
|
|
513
|
+
try {
|
|
514
|
+
await driver.connect();
|
|
515
|
+
this.logger.info("Driver connected successfully", { driverName: name });
|
|
516
|
+
} catch (e) {
|
|
517
|
+
this.logger.error("Failed to connect driver", e, { driverName: name });
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
this.logger.info("ObjectQL engine initialization complete");
|
|
521
|
+
}
|
|
522
|
+
async destroy() {
|
|
523
|
+
this.logger.info("Destroying ObjectQL engine", { driverCount: this.drivers.size });
|
|
524
|
+
for (const [name, driver] of this.drivers.entries()) {
|
|
525
|
+
try {
|
|
526
|
+
await driver.disconnect();
|
|
527
|
+
} catch (e) {
|
|
528
|
+
this.logger.error("Error disconnecting driver", e, { driverName: name });
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
this.logger.info("ObjectQL engine destroyed");
|
|
532
|
+
}
|
|
533
|
+
// ============================================
|
|
534
|
+
// Helper: Query Conversion
|
|
535
|
+
// ============================================
|
|
536
|
+
toQueryAST(object, options) {
|
|
537
|
+
const ast = { object };
|
|
538
|
+
if (!options) return ast;
|
|
539
|
+
if (options.filter) {
|
|
540
|
+
ast.where = options.filter;
|
|
541
|
+
}
|
|
542
|
+
if (options.select) {
|
|
543
|
+
ast.fields = options.select;
|
|
544
|
+
}
|
|
545
|
+
if (options.sort) {
|
|
546
|
+
if (Array.isArray(options.sort)) {
|
|
547
|
+
ast.orderBy = options.sort;
|
|
548
|
+
} else {
|
|
549
|
+
ast.orderBy = Object.entries(options.sort).map(([field, order]) => ({
|
|
550
|
+
field,
|
|
551
|
+
order: order === -1 || order === "desc" ? "desc" : "asc"
|
|
552
|
+
}));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (options.top !== void 0) ast.limit = options.top;
|
|
556
|
+
else if (options.limit !== void 0) ast.limit = options.limit;
|
|
557
|
+
if (options.skip !== void 0) ast.offset = options.skip;
|
|
558
|
+
return ast;
|
|
559
|
+
}
|
|
560
|
+
// ============================================
|
|
561
|
+
// Data Access Methods (IDataEngine Interface)
|
|
562
|
+
// ============================================
|
|
563
|
+
async find(object, query) {
|
|
564
|
+
this.logger.debug("Find operation starting", { object, query });
|
|
565
|
+
const driver = this.getDriver(object);
|
|
566
|
+
const ast = this.toQueryAST(object, query);
|
|
567
|
+
const hookContext = {
|
|
568
|
+
object,
|
|
569
|
+
event: "beforeFind",
|
|
570
|
+
input: { ast, options: void 0 },
|
|
571
|
+
// Should map options?
|
|
572
|
+
ql: this
|
|
573
|
+
};
|
|
574
|
+
await this.triggerHooks("beforeFind", hookContext);
|
|
575
|
+
try {
|
|
576
|
+
const result = await driver.find(object, hookContext.input.ast, hookContext.input.options);
|
|
577
|
+
hookContext.event = "afterFind";
|
|
578
|
+
hookContext.result = result;
|
|
579
|
+
await this.triggerHooks("afterFind", hookContext);
|
|
580
|
+
return hookContext.result;
|
|
581
|
+
} catch (e) {
|
|
582
|
+
this.logger.error("Find operation failed", e, { object });
|
|
583
|
+
throw e;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
async findOne(objectName, query) {
|
|
587
|
+
this.logger.debug("FindOne operation", { objectName });
|
|
588
|
+
const driver = this.getDriver(objectName);
|
|
589
|
+
const ast = this.toQueryAST(objectName, query);
|
|
590
|
+
ast.limit = 1;
|
|
591
|
+
return driver.findOne(objectName, ast);
|
|
592
|
+
}
|
|
593
|
+
async insert(object, data, options) {
|
|
594
|
+
this.logger.debug("Insert operation starting", { object, isBatch: Array.isArray(data) });
|
|
595
|
+
const driver = this.getDriver(object);
|
|
596
|
+
const hookContext = {
|
|
597
|
+
object,
|
|
598
|
+
event: "beforeInsert",
|
|
599
|
+
input: { data, options },
|
|
600
|
+
ql: this
|
|
601
|
+
};
|
|
602
|
+
await this.triggerHooks("beforeInsert", hookContext);
|
|
603
|
+
try {
|
|
604
|
+
let result;
|
|
605
|
+
if (Array.isArray(hookContext.input.data)) {
|
|
606
|
+
if (driver.bulkCreate) {
|
|
607
|
+
result = await driver.bulkCreate(object, hookContext.input.data, hookContext.input.options);
|
|
608
|
+
} else {
|
|
609
|
+
result = await Promise.all(hookContext.input.data.map((item) => driver.create(object, item, hookContext.input.options)));
|
|
610
|
+
}
|
|
611
|
+
} else {
|
|
612
|
+
result = await driver.create(object, hookContext.input.data, hookContext.input.options);
|
|
613
|
+
}
|
|
614
|
+
hookContext.event = "afterInsert";
|
|
615
|
+
hookContext.result = result;
|
|
616
|
+
await this.triggerHooks("afterInsert", hookContext);
|
|
617
|
+
return hookContext.result;
|
|
618
|
+
} catch (e) {
|
|
619
|
+
this.logger.error("Insert operation failed", e, { object });
|
|
620
|
+
throw e;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
async update(object, data, options) {
|
|
624
|
+
this.logger.debug("Update operation starting", { object });
|
|
625
|
+
const driver = this.getDriver(object);
|
|
626
|
+
let id = data.id || data._id;
|
|
627
|
+
if (!id && options?.filter) {
|
|
628
|
+
if (typeof options.filter === "string") id = options.filter;
|
|
629
|
+
else if (options.filter._id) id = options.filter._id;
|
|
630
|
+
else if (options.filter.id) id = options.filter.id;
|
|
631
|
+
}
|
|
632
|
+
const hookContext = {
|
|
633
|
+
object,
|
|
634
|
+
event: "beforeUpdate",
|
|
635
|
+
input: { id, data, options },
|
|
636
|
+
ql: this
|
|
637
|
+
};
|
|
638
|
+
await this.triggerHooks("beforeUpdate", hookContext);
|
|
639
|
+
try {
|
|
640
|
+
let result;
|
|
641
|
+
if (hookContext.input.id) {
|
|
642
|
+
result = await driver.update(object, hookContext.input.id, hookContext.input.data, hookContext.input.options);
|
|
643
|
+
} else if (options?.multi && driver.updateMany) {
|
|
644
|
+
const ast = this.toQueryAST(object, { filter: options.filter });
|
|
645
|
+
result = await driver.updateMany(object, ast, hookContext.input.data, hookContext.input.options);
|
|
646
|
+
} else {
|
|
647
|
+
throw new Error("Update requires an ID or options.multi=true");
|
|
648
|
+
}
|
|
649
|
+
hookContext.event = "afterUpdate";
|
|
650
|
+
hookContext.result = result;
|
|
651
|
+
await this.triggerHooks("afterUpdate", hookContext);
|
|
652
|
+
return hookContext.result;
|
|
653
|
+
} catch (e) {
|
|
654
|
+
this.logger.error("Update operation failed", e, { object });
|
|
655
|
+
throw e;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
async delete(object, options) {
|
|
659
|
+
this.logger.debug("Delete operation starting", { object });
|
|
660
|
+
const driver = this.getDriver(object);
|
|
661
|
+
let id = void 0;
|
|
662
|
+
if (options?.filter) {
|
|
663
|
+
if (typeof options.filter === "string") id = options.filter;
|
|
664
|
+
else if (options.filter._id) id = options.filter._id;
|
|
665
|
+
else if (options.filter.id) id = options.filter.id;
|
|
666
|
+
}
|
|
667
|
+
const hookContext = {
|
|
668
|
+
object,
|
|
669
|
+
event: "beforeDelete",
|
|
670
|
+
input: { id, options },
|
|
671
|
+
ql: this
|
|
672
|
+
};
|
|
673
|
+
await this.triggerHooks("beforeDelete", hookContext);
|
|
674
|
+
try {
|
|
675
|
+
let result;
|
|
676
|
+
if (hookContext.input.id) {
|
|
677
|
+
result = await driver.delete(object, hookContext.input.id, hookContext.input.options);
|
|
678
|
+
} else if (options?.multi && driver.deleteMany) {
|
|
679
|
+
const ast = this.toQueryAST(object, { filter: options.filter });
|
|
680
|
+
result = await driver.deleteMany(object, ast, hookContext.input.options);
|
|
681
|
+
} else {
|
|
682
|
+
throw new Error("Delete requires an ID or options.multi=true");
|
|
683
|
+
}
|
|
684
|
+
hookContext.event = "afterDelete";
|
|
685
|
+
hookContext.result = result;
|
|
686
|
+
await this.triggerHooks("afterDelete", hookContext);
|
|
687
|
+
return hookContext.result;
|
|
688
|
+
} catch (e) {
|
|
689
|
+
this.logger.error("Delete operation failed", e, { object });
|
|
690
|
+
throw e;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
async count(object, query) {
|
|
694
|
+
const driver = this.getDriver(object);
|
|
695
|
+
if (driver.count) {
|
|
696
|
+
const ast = this.toQueryAST(object, { filter: query?.filter });
|
|
697
|
+
return driver.count(object, ast);
|
|
698
|
+
}
|
|
699
|
+
const res = await this.find(object, { filter: query?.filter, select: ["_id"] });
|
|
700
|
+
return res.length;
|
|
701
|
+
}
|
|
702
|
+
async aggregate(object, query) {
|
|
703
|
+
const driver = this.getDriver(object);
|
|
704
|
+
this.logger.debug(`Aggregate on ${object} using ${driver.name}`, query);
|
|
705
|
+
throw new Error("Aggregate not yet fully implemented in ObjectQL->Driver mapping");
|
|
706
|
+
}
|
|
707
|
+
async execute(command, options) {
|
|
708
|
+
if (options?.object) {
|
|
709
|
+
const driver = this.getDriver(options.object);
|
|
710
|
+
if (driver.execute) {
|
|
711
|
+
return driver.execute(command, void 0, options);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
throw new Error("Execute requires options.object to select driver");
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
// src/plugin.ts
|
|
719
|
+
var ObjectQLPlugin = class {
|
|
720
|
+
constructor(ql, hostContext) {
|
|
721
|
+
this.name = "com.objectstack.engine.objectql";
|
|
722
|
+
this.type = "objectql";
|
|
723
|
+
this.version = "1.0.0";
|
|
724
|
+
this.init = async (ctx) => {
|
|
725
|
+
if (!this.ql) {
|
|
726
|
+
this.ql = new ObjectQL(this.hostContext);
|
|
727
|
+
}
|
|
728
|
+
ctx.registerService("objectql", this.ql);
|
|
729
|
+
ctx.registerService("metadata", this.ql);
|
|
730
|
+
ctx.registerService("data", this.ql);
|
|
731
|
+
ctx.registerService("auth", this.ql);
|
|
732
|
+
ctx.logger.info("ObjectQL engine registered as service", {
|
|
733
|
+
provides: ["objectql", "metadata", "data", "auth"]
|
|
734
|
+
});
|
|
735
|
+
const protocolShim = new ObjectStackProtocolImplementation(this.ql);
|
|
736
|
+
ctx.registerService("protocol", protocolShim);
|
|
737
|
+
ctx.logger.info("Protocol service registered");
|
|
738
|
+
};
|
|
739
|
+
this.start = async (ctx) => {
|
|
740
|
+
ctx.logger.info("ObjectQL engine initialized");
|
|
741
|
+
if (ctx.getServices && this.ql) {
|
|
742
|
+
const services = ctx.getServices();
|
|
743
|
+
for (const [name, service] of services.entries()) {
|
|
744
|
+
if (name.startsWith("driver.")) {
|
|
745
|
+
this.ql.registerDriver(service);
|
|
746
|
+
ctx.logger.debug("Discovered and registered driver service", { serviceName: name });
|
|
747
|
+
}
|
|
748
|
+
if (name.startsWith("app.")) {
|
|
749
|
+
this.ql.registerApp(service);
|
|
750
|
+
ctx.logger.debug("Discovered and registered app service", { serviceName: name });
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
if (ql) {
|
|
756
|
+
this.ql = ql;
|
|
757
|
+
} else {
|
|
758
|
+
this.hostContext = hostContext;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
export {
|
|
763
|
+
ObjectQL,
|
|
764
|
+
ObjectQLPlugin,
|
|
765
|
+
ObjectStackProtocolImplementation,
|
|
766
|
+
SchemaRegistry
|
|
767
|
+
};
|
|
768
|
+
//# sourceMappingURL=index.mjs.map
|