@objectstack/objectql 1.0.4 → 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/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