@objectstack/objectql 4.0.1 → 4.0.3
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 +11 -11
- package/CHANGELOG.md +23 -0
- package/dist/index.d.mts +60 -68
- package/dist/index.d.ts +60 -68
- package/dist/index.js +336 -84
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +336 -84
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/engine.ts +116 -10
- package/src/plugin.integration.test.ts +245 -13
- package/src/plugin.ts +174 -46
- package/src/protocol.ts +110 -25
- package/src/registry.test.ts +27 -16
- package/src/registry.ts +34 -25
package/dist/index.mjs
CHANGED
|
@@ -53,36 +53,45 @@ var SchemaRegistry = class {
|
|
|
53
53
|
// ==========================================
|
|
54
54
|
/**
|
|
55
55
|
* Register a namespace for a package.
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* @throws Error if namespace is already registered to a different package
|
|
56
|
+
* Multiple packages can share the same namespace (e.g. 'sys').
|
|
59
57
|
*/
|
|
60
58
|
static registerNamespace(namespace, packageId) {
|
|
61
59
|
if (!namespace) return;
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
60
|
+
let owners = this.namespaceRegistry.get(namespace);
|
|
61
|
+
if (!owners) {
|
|
62
|
+
owners = /* @__PURE__ */ new Set();
|
|
63
|
+
this.namespaceRegistry.set(namespace, owners);
|
|
67
64
|
}
|
|
68
|
-
|
|
65
|
+
owners.add(packageId);
|
|
69
66
|
this.log(`[Registry] Registered namespace: ${namespace} \u2192 ${packageId}`);
|
|
70
67
|
}
|
|
71
68
|
/**
|
|
72
69
|
* Unregister a namespace when a package is uninstalled.
|
|
73
70
|
*/
|
|
74
71
|
static unregisterNamespace(namespace, packageId) {
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
const owners = this.namespaceRegistry.get(namespace);
|
|
73
|
+
if (owners) {
|
|
74
|
+
owners.delete(packageId);
|
|
75
|
+
if (owners.size === 0) {
|
|
76
|
+
this.namespaceRegistry.delete(namespace);
|
|
77
|
+
}
|
|
78
|
+
this.log(`[Registry] Unregistered namespace: ${namespace} \u2190 ${packageId}`);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
|
-
* Get the
|
|
82
|
+
* Get the packages that use a namespace.
|
|
83
83
|
*/
|
|
84
84
|
static getNamespaceOwner(namespace) {
|
|
85
|
-
|
|
85
|
+
const owners = this.namespaceRegistry.get(namespace);
|
|
86
|
+
if (!owners || owners.size === 0) return void 0;
|
|
87
|
+
return owners.values().next().value;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get all packages that share a namespace.
|
|
91
|
+
*/
|
|
92
|
+
static getNamespaceOwners(namespace) {
|
|
93
|
+
const owners = this.namespaceRegistry.get(namespace);
|
|
94
|
+
return owners ? Array.from(owners) : [];
|
|
86
95
|
}
|
|
87
96
|
// ==========================================
|
|
88
97
|
// Object Registration (Ownership Model)
|
|
@@ -292,7 +301,7 @@ var SchemaRegistry = class {
|
|
|
292
301
|
if (type === "object") {
|
|
293
302
|
return ObjectSchema.parse(item);
|
|
294
303
|
}
|
|
295
|
-
if (type === "
|
|
304
|
+
if (type === "app") {
|
|
296
305
|
return AppSchema.parse(item);
|
|
297
306
|
}
|
|
298
307
|
if (type === "package") {
|
|
@@ -442,13 +451,13 @@ var SchemaRegistry = class {
|
|
|
442
451
|
// App Helpers
|
|
443
452
|
// ==========================================
|
|
444
453
|
static registerApp(app, packageId) {
|
|
445
|
-
this.registerItem("
|
|
454
|
+
this.registerItem("app", app, "name", packageId);
|
|
446
455
|
}
|
|
447
456
|
static getApp(name) {
|
|
448
|
-
return this.getItem("
|
|
457
|
+
return this.getItem("app", name);
|
|
449
458
|
}
|
|
450
459
|
static getAllApps() {
|
|
451
|
-
return this.listItems("
|
|
460
|
+
return this.listItems("app");
|
|
452
461
|
}
|
|
453
462
|
// ==========================================
|
|
454
463
|
// Plugin Helpers
|
|
@@ -494,7 +503,7 @@ SchemaRegistry._logLevel = "info";
|
|
|
494
503
|
SchemaRegistry.objectContributors = /* @__PURE__ */ new Map();
|
|
495
504
|
/** FQN → Merged ServiceObject (cached, invalidated on changes) */
|
|
496
505
|
SchemaRegistry.mergedObjectCache = /* @__PURE__ */ new Map();
|
|
497
|
-
/** Namespace → PackageId (
|
|
506
|
+
/** Namespace → Set<PackageId> (multiple packages can share a namespace) */
|
|
498
507
|
SchemaRegistry.namespaceRegistry = /* @__PURE__ */ new Map();
|
|
499
508
|
// ==========================================
|
|
500
509
|
// Generic metadata storage (non-object types)
|
|
@@ -504,6 +513,7 @@ SchemaRegistry.metadata = /* @__PURE__ */ new Map();
|
|
|
504
513
|
|
|
505
514
|
// src/protocol.ts
|
|
506
515
|
import { parseFilterAST, isFilterAST } from "@objectstack/spec/data";
|
|
516
|
+
import { PLURAL_TO_SINGULAR, SINGULAR_TO_PLURAL } from "@objectstack/spec/shared";
|
|
507
517
|
function simpleHash(str) {
|
|
508
518
|
let hash = 0;
|
|
509
519
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -631,20 +641,32 @@ var ObjectStackProtocolImplementation = class {
|
|
|
631
641
|
};
|
|
632
642
|
}
|
|
633
643
|
async getMetaTypes() {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
644
|
+
const schemaTypes = SchemaRegistry.getRegisteredTypes();
|
|
645
|
+
let runtimeTypes = [];
|
|
646
|
+
try {
|
|
647
|
+
const services = this.getServicesRegistry?.();
|
|
648
|
+
const metadataService = services?.get("metadata");
|
|
649
|
+
if (metadataService && typeof metadataService.getRegisteredTypes === "function") {
|
|
650
|
+
runtimeTypes = await metadataService.getRegisteredTypes();
|
|
651
|
+
}
|
|
652
|
+
} catch {
|
|
653
|
+
}
|
|
654
|
+
const allTypes = Array.from(/* @__PURE__ */ new Set([...schemaTypes, ...runtimeTypes]));
|
|
655
|
+
return { types: allTypes };
|
|
637
656
|
}
|
|
638
657
|
async getMetaItems(request) {
|
|
639
|
-
|
|
658
|
+
const { packageId } = request;
|
|
659
|
+
let items = SchemaRegistry.listItems(request.type, packageId);
|
|
640
660
|
if (items.length === 0) {
|
|
641
|
-
const alt = request.type
|
|
642
|
-
items = SchemaRegistry.listItems(alt);
|
|
661
|
+
const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];
|
|
662
|
+
if (alt) items = SchemaRegistry.listItems(alt, packageId);
|
|
643
663
|
}
|
|
644
664
|
if (items.length === 0) {
|
|
645
665
|
try {
|
|
666
|
+
const whereClause = { type: request.type, state: "active" };
|
|
667
|
+
if (packageId) whereClause._packageId = packageId;
|
|
646
668
|
const allRecords = await this.engine.find("sys_metadata", {
|
|
647
|
-
where:
|
|
669
|
+
where: whereClause
|
|
648
670
|
});
|
|
649
671
|
if (allRecords && allRecords.length > 0) {
|
|
650
672
|
items = allRecords.map((record) => {
|
|
@@ -653,21 +675,47 @@ var ObjectStackProtocolImplementation = class {
|
|
|
653
675
|
return data;
|
|
654
676
|
});
|
|
655
677
|
} else {
|
|
656
|
-
const alt = request.type
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (altRecords && altRecords.length > 0) {
|
|
661
|
-
items = altRecords.map((record) => {
|
|
662
|
-
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
663
|
-
SchemaRegistry.registerItem(request.type, data, "name");
|
|
664
|
-
return data;
|
|
678
|
+
const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];
|
|
679
|
+
if (alt) {
|
|
680
|
+
const altRecords = await this.engine.find("sys_metadata", {
|
|
681
|
+
where: { type: alt, state: "active" }
|
|
665
682
|
});
|
|
683
|
+
if (altRecords && altRecords.length > 0) {
|
|
684
|
+
items = altRecords.map((record) => {
|
|
685
|
+
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
686
|
+
SchemaRegistry.registerItem(request.type, data, "name");
|
|
687
|
+
return data;
|
|
688
|
+
});
|
|
689
|
+
}
|
|
666
690
|
}
|
|
667
691
|
}
|
|
668
692
|
} catch {
|
|
669
693
|
}
|
|
670
694
|
}
|
|
695
|
+
try {
|
|
696
|
+
const services = this.getServicesRegistry?.();
|
|
697
|
+
const metadataService = services?.get("metadata");
|
|
698
|
+
if (metadataService && typeof metadataService.list === "function") {
|
|
699
|
+
const runtimeItems = await metadataService.list(request.type);
|
|
700
|
+
if (runtimeItems && runtimeItems.length > 0) {
|
|
701
|
+
const itemMap = /* @__PURE__ */ new Map();
|
|
702
|
+
for (const item of items) {
|
|
703
|
+
const entry = item;
|
|
704
|
+
if (entry && typeof entry === "object" && "name" in entry) {
|
|
705
|
+
itemMap.set(entry.name, entry);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
for (const item of runtimeItems) {
|
|
709
|
+
const entry = item;
|
|
710
|
+
if (entry && typeof entry === "object" && "name" in entry) {
|
|
711
|
+
itemMap.set(entry.name, entry);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
items = Array.from(itemMap.values());
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
} catch {
|
|
718
|
+
}
|
|
671
719
|
return {
|
|
672
720
|
type: request.type,
|
|
673
721
|
items
|
|
@@ -676,8 +724,8 @@ var ObjectStackProtocolImplementation = class {
|
|
|
676
724
|
async getMetaItem(request) {
|
|
677
725
|
let item = SchemaRegistry.getItem(request.type, request.name);
|
|
678
726
|
if (item === void 0) {
|
|
679
|
-
const alt = request.type
|
|
680
|
-
item = SchemaRegistry.getItem(alt, request.name);
|
|
727
|
+
const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];
|
|
728
|
+
if (alt) item = SchemaRegistry.getItem(alt, request.name);
|
|
681
729
|
}
|
|
682
730
|
if (item === void 0) {
|
|
683
731
|
try {
|
|
@@ -688,18 +736,30 @@ var ObjectStackProtocolImplementation = class {
|
|
|
688
736
|
item = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
689
737
|
SchemaRegistry.registerItem(request.type, item, "name");
|
|
690
738
|
} else {
|
|
691
|
-
const alt = request.type
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
739
|
+
const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];
|
|
740
|
+
if (alt) {
|
|
741
|
+
const altRecord = await this.engine.findOne("sys_metadata", {
|
|
742
|
+
where: { type: alt, name: request.name, state: "active" }
|
|
743
|
+
});
|
|
744
|
+
if (altRecord) {
|
|
745
|
+
item = typeof altRecord.metadata === "string" ? JSON.parse(altRecord.metadata) : altRecord.metadata;
|
|
746
|
+
SchemaRegistry.registerItem(request.type, item, "name");
|
|
747
|
+
}
|
|
698
748
|
}
|
|
699
749
|
}
|
|
700
750
|
} catch {
|
|
701
751
|
}
|
|
702
752
|
}
|
|
753
|
+
if (item === void 0) {
|
|
754
|
+
try {
|
|
755
|
+
const services = this.getServicesRegistry?.();
|
|
756
|
+
const metadataService = services?.get("metadata");
|
|
757
|
+
if (metadataService && typeof metadataService.get === "function") {
|
|
758
|
+
item = await metadataService.get(request.type, request.name);
|
|
759
|
+
}
|
|
760
|
+
} catch {
|
|
761
|
+
}
|
|
762
|
+
}
|
|
703
763
|
return {
|
|
704
764
|
type: request.type,
|
|
705
765
|
name: request.name,
|
|
@@ -872,10 +932,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
872
932
|
const records = await this.engine.find(request.object, options);
|
|
873
933
|
return {
|
|
874
934
|
object: request.object,
|
|
875
|
-
value: records,
|
|
876
|
-
// OData compatibility
|
|
877
935
|
records,
|
|
878
|
-
// Legacy
|
|
879
936
|
total: records.length,
|
|
880
937
|
hasMore: false
|
|
881
938
|
};
|
|
@@ -933,7 +990,21 @@ var ObjectStackProtocolImplementation = class {
|
|
|
933
990
|
// ==========================================
|
|
934
991
|
async getMetaItemCached(request) {
|
|
935
992
|
try {
|
|
936
|
-
|
|
993
|
+
let item = SchemaRegistry.getItem(request.type, request.name);
|
|
994
|
+
if (!item) {
|
|
995
|
+
const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];
|
|
996
|
+
if (alt) item = SchemaRegistry.getItem(alt, request.name);
|
|
997
|
+
}
|
|
998
|
+
if (!item) {
|
|
999
|
+
try {
|
|
1000
|
+
const services = this.getServicesRegistry?.();
|
|
1001
|
+
const metadataService = services?.get("metadata");
|
|
1002
|
+
if (metadataService && typeof metadataService.get === "function") {
|
|
1003
|
+
item = await metadataService.get(request.type, request.name);
|
|
1004
|
+
}
|
|
1005
|
+
} catch {
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
937
1008
|
if (!item) {
|
|
938
1009
|
throw new Error(`Metadata item ${request.type}/${request.name} not found`);
|
|
939
1010
|
}
|
|
@@ -1285,10 +1356,11 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1285
1356
|
for (const record of records) {
|
|
1286
1357
|
try {
|
|
1287
1358
|
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
1288
|
-
|
|
1359
|
+
const normalizedType = PLURAL_TO_SINGULAR[record.type] ?? record.type;
|
|
1360
|
+
if (normalizedType === "object") {
|
|
1289
1361
|
SchemaRegistry.registerObject(data, record.packageId || "sys_metadata");
|
|
1290
1362
|
} else {
|
|
1291
|
-
SchemaRegistry.registerItem(
|
|
1363
|
+
SchemaRegistry.registerItem(normalizedType, data, "name");
|
|
1292
1364
|
}
|
|
1293
1365
|
loaded++;
|
|
1294
1366
|
} catch (e) {
|
|
@@ -1438,6 +1510,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1438
1510
|
import { ExecutionContextSchema } from "@objectstack/spec/kernel";
|
|
1439
1511
|
import { createLogger } from "@objectstack/core";
|
|
1440
1512
|
import { CoreServiceName } from "@objectstack/spec/system";
|
|
1513
|
+
import { pluralToSingular } from "@objectstack/spec/shared";
|
|
1441
1514
|
var _ObjectQL = class _ObjectQL {
|
|
1442
1515
|
constructor(hostContext = {}) {
|
|
1443
1516
|
this.drivers = /* @__PURE__ */ new Map();
|
|
@@ -1728,7 +1801,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1728
1801
|
for (const item of items) {
|
|
1729
1802
|
const itemName = item.name || item.id;
|
|
1730
1803
|
if (itemName) {
|
|
1731
|
-
SchemaRegistry.registerItem(key, item, "name", id);
|
|
1804
|
+
SchemaRegistry.registerItem(pluralToSingular(key), item, "name", id);
|
|
1732
1805
|
}
|
|
1733
1806
|
}
|
|
1734
1807
|
}
|
|
@@ -1834,7 +1907,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1834
1907
|
for (const item of items) {
|
|
1835
1908
|
const itemName = item.name || item.id;
|
|
1836
1909
|
if (itemName) {
|
|
1837
|
-
SchemaRegistry.registerItem(key, item, "name", ownerId);
|
|
1910
|
+
SchemaRegistry.registerItem(pluralToSingular(key), item, "name", ownerId);
|
|
1838
1911
|
}
|
|
1839
1912
|
}
|
|
1840
1913
|
}
|
|
@@ -1858,6 +1931,16 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1858
1931
|
this.logger.info("Set default driver", { driverName: driver.name });
|
|
1859
1932
|
}
|
|
1860
1933
|
}
|
|
1934
|
+
/**
|
|
1935
|
+
* Set the realtime service for publishing data change events.
|
|
1936
|
+
* Should be called after kernel resolves the realtime service.
|
|
1937
|
+
*
|
|
1938
|
+
* @param service - An IRealtimeService instance for event publishing
|
|
1939
|
+
*/
|
|
1940
|
+
setRealtimeService(service) {
|
|
1941
|
+
this.realtimeService = service;
|
|
1942
|
+
this.logger.info("RealtimeService configured for data events");
|
|
1943
|
+
}
|
|
1861
1944
|
/**
|
|
1862
1945
|
* Helper to get object definition
|
|
1863
1946
|
*/
|
|
@@ -1912,14 +1995,22 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1912
1995
|
driverCount: this.drivers.size,
|
|
1913
1996
|
drivers: Array.from(this.drivers.keys())
|
|
1914
1997
|
});
|
|
1998
|
+
const failedDrivers = [];
|
|
1915
1999
|
for (const [name, driver] of this.drivers) {
|
|
1916
2000
|
try {
|
|
1917
2001
|
await driver.connect();
|
|
1918
2002
|
this.logger.info("Driver connected successfully", { driverName: name });
|
|
1919
2003
|
} catch (e) {
|
|
2004
|
+
failedDrivers.push(name);
|
|
1920
2005
|
this.logger.error("Failed to connect driver", e, { driverName: name });
|
|
1921
2006
|
}
|
|
1922
2007
|
}
|
|
2008
|
+
if (failedDrivers.length > 0) {
|
|
2009
|
+
this.logger.warn(
|
|
2010
|
+
`${failedDrivers.length} of ${this.drivers.size} driver(s) failed initial connect. Operations may recover via lazy reconnection or fail at query time.`,
|
|
2011
|
+
{ failedDrivers }
|
|
2012
|
+
);
|
|
2013
|
+
}
|
|
1923
2014
|
this.logger.info("ObjectQL engine initialization complete");
|
|
1924
2015
|
}
|
|
1925
2016
|
async destroy() {
|
|
@@ -2121,6 +2212,39 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2121
2212
|
hookContext.event = "afterInsert";
|
|
2122
2213
|
hookContext.result = result;
|
|
2123
2214
|
await this.triggerHooks("afterInsert", hookContext);
|
|
2215
|
+
if (this.realtimeService) {
|
|
2216
|
+
try {
|
|
2217
|
+
if (Array.isArray(result)) {
|
|
2218
|
+
for (const record of result) {
|
|
2219
|
+
const event = {
|
|
2220
|
+
type: "data.record.created",
|
|
2221
|
+
object,
|
|
2222
|
+
payload: {
|
|
2223
|
+
recordId: record.id,
|
|
2224
|
+
after: record
|
|
2225
|
+
},
|
|
2226
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2227
|
+
};
|
|
2228
|
+
await this.realtimeService.publish(event);
|
|
2229
|
+
}
|
|
2230
|
+
this.logger.debug(`Published ${result.length} data.record.created events`, { object });
|
|
2231
|
+
} else {
|
|
2232
|
+
const event = {
|
|
2233
|
+
type: "data.record.created",
|
|
2234
|
+
object,
|
|
2235
|
+
payload: {
|
|
2236
|
+
recordId: result.id,
|
|
2237
|
+
after: result
|
|
2238
|
+
},
|
|
2239
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2240
|
+
};
|
|
2241
|
+
await this.realtimeService.publish(event);
|
|
2242
|
+
this.logger.debug("Published data.record.created event", { object, recordId: result.id });
|
|
2243
|
+
}
|
|
2244
|
+
} catch (error) {
|
|
2245
|
+
this.logger.warn("Failed to publish data event", { object, error });
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2124
2248
|
return hookContext.result;
|
|
2125
2249
|
} catch (e) {
|
|
2126
2250
|
this.logger.error("Insert operation failed", e, { object });
|
|
@@ -2167,6 +2291,26 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2167
2291
|
hookContext.event = "afterUpdate";
|
|
2168
2292
|
hookContext.result = result;
|
|
2169
2293
|
await this.triggerHooks("afterUpdate", hookContext);
|
|
2294
|
+
if (this.realtimeService) {
|
|
2295
|
+
try {
|
|
2296
|
+
const resultId = typeof result === "object" && result && "id" in result ? result.id : void 0;
|
|
2297
|
+
const recordId = String(hookContext.input.id || resultId || "");
|
|
2298
|
+
const event = {
|
|
2299
|
+
type: "data.record.updated",
|
|
2300
|
+
object,
|
|
2301
|
+
payload: {
|
|
2302
|
+
recordId,
|
|
2303
|
+
changes: hookContext.input.data,
|
|
2304
|
+
after: result
|
|
2305
|
+
},
|
|
2306
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2307
|
+
};
|
|
2308
|
+
await this.realtimeService.publish(event);
|
|
2309
|
+
this.logger.debug("Published data.record.updated event", { object, recordId });
|
|
2310
|
+
} catch (error) {
|
|
2311
|
+
this.logger.warn("Failed to publish data event", { object, error });
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2170
2314
|
return hookContext.result;
|
|
2171
2315
|
} catch (e) {
|
|
2172
2316
|
this.logger.error("Update operation failed", e, { object });
|
|
@@ -2212,6 +2356,24 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2212
2356
|
hookContext.event = "afterDelete";
|
|
2213
2357
|
hookContext.result = result;
|
|
2214
2358
|
await this.triggerHooks("afterDelete", hookContext);
|
|
2359
|
+
if (this.realtimeService) {
|
|
2360
|
+
try {
|
|
2361
|
+
const resultId = typeof result === "object" && result && "id" in result ? result.id : void 0;
|
|
2362
|
+
const recordId = String(hookContext.input.id || resultId || "");
|
|
2363
|
+
const event = {
|
|
2364
|
+
type: "data.record.deleted",
|
|
2365
|
+
object,
|
|
2366
|
+
payload: {
|
|
2367
|
+
recordId
|
|
2368
|
+
},
|
|
2369
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2370
|
+
};
|
|
2371
|
+
await this.realtimeService.publish(event);
|
|
2372
|
+
this.logger.debug("Published data.record.deleted event", { object, recordId });
|
|
2373
|
+
} catch (error) {
|
|
2374
|
+
this.logger.warn("Failed to publish data event", { object, error });
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2215
2377
|
return hookContext.result;
|
|
2216
2378
|
} catch (e) {
|
|
2217
2379
|
this.logger.error("Delete operation failed", e, { object });
|
|
@@ -2669,6 +2831,9 @@ var MetadataFacade = class {
|
|
|
2669
2831
|
};
|
|
2670
2832
|
|
|
2671
2833
|
// src/plugin.ts
|
|
2834
|
+
function hasLoadMetaFromDb(service) {
|
|
2835
|
+
return typeof service === "object" && service !== null && typeof service["loadMetaFromDb"] === "function";
|
|
2836
|
+
}
|
|
2672
2837
|
var ObjectQLPlugin = class {
|
|
2673
2838
|
constructor(ql, hostContext) {
|
|
2674
2839
|
this.name = "com.objectstack.engine.objectql";
|
|
@@ -2680,38 +2845,18 @@ var ObjectQLPlugin = class {
|
|
|
2680
2845
|
this.ql = new ObjectQL(hostCtx);
|
|
2681
2846
|
}
|
|
2682
2847
|
ctx.registerService("objectql", this.ql);
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
} catch (e) {
|
|
2691
|
-
}
|
|
2692
|
-
if (!hasMetadata) {
|
|
2693
|
-
try {
|
|
2694
|
-
const metadataFacade = new MetadataFacade();
|
|
2695
|
-
ctx.registerService("metadata", metadataFacade);
|
|
2696
|
-
ctx.logger.info("MetadataFacade registered as metadata service", {
|
|
2697
|
-
mode: "in-memory",
|
|
2698
|
-
features: ["registry", "fast-lookup"]
|
|
2848
|
+
ctx.registerService("data", this.ql);
|
|
2849
|
+
const ql = this.ql;
|
|
2850
|
+
ctx.registerService("manifest", {
|
|
2851
|
+
register: (manifest) => {
|
|
2852
|
+
ql.registerApp(manifest);
|
|
2853
|
+
ctx.logger.debug("Manifest registered via manifest service", {
|
|
2854
|
+
id: manifest.id || manifest.name
|
|
2699
2855
|
});
|
|
2700
|
-
} catch (e) {
|
|
2701
|
-
if (!e.message?.includes("already registered")) {
|
|
2702
|
-
throw e;
|
|
2703
|
-
}
|
|
2704
2856
|
}
|
|
2705
|
-
}
|
|
2706
|
-
ctx.logger.info("External metadata service detected", {
|
|
2707
|
-
provider: metadataProvider,
|
|
2708
|
-
mode: "will-sync-in-start-phase"
|
|
2709
|
-
});
|
|
2710
|
-
}
|
|
2711
|
-
ctx.registerService("data", this.ql);
|
|
2857
|
+
});
|
|
2712
2858
|
ctx.logger.info("ObjectQL engine registered", {
|
|
2713
|
-
services: ["objectql", "data"]
|
|
2714
|
-
metadataProvider
|
|
2859
|
+
services: ["objectql", "data", "manifest"]
|
|
2715
2860
|
});
|
|
2716
2861
|
const protocolShim = new ObjectStackProtocolImplementation(
|
|
2717
2862
|
this.ql,
|
|
@@ -2724,7 +2869,7 @@ var ObjectQLPlugin = class {
|
|
|
2724
2869
|
ctx.logger.info("ObjectQL engine starting...");
|
|
2725
2870
|
try {
|
|
2726
2871
|
const metadataService = ctx.getService("metadata");
|
|
2727
|
-
if (metadataService &&
|
|
2872
|
+
if (metadataService && typeof metadataService.loadMany === "function" && this.ql) {
|
|
2728
2873
|
await this.loadMetadataFromService(metadataService, ctx);
|
|
2729
2874
|
}
|
|
2730
2875
|
} catch (e) {
|
|
@@ -2738,13 +2883,29 @@ var ObjectQLPlugin = class {
|
|
|
2738
2883
|
ctx.logger.debug("Discovered and registered driver service", { serviceName: name });
|
|
2739
2884
|
}
|
|
2740
2885
|
if (name.startsWith("app.")) {
|
|
2886
|
+
ctx.logger.warn(
|
|
2887
|
+
`[DEPRECATED] Service "${name}" uses legacy app.* convention. Migrate to ctx.getService('manifest').register(data).`
|
|
2888
|
+
);
|
|
2741
2889
|
this.ql.registerApp(service);
|
|
2742
|
-
ctx.logger.debug("Discovered and registered app service", { serviceName: name });
|
|
2890
|
+
ctx.logger.debug("Discovered and registered app service (legacy)", { serviceName: name });
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
try {
|
|
2894
|
+
const realtimeService = ctx.getService("realtime");
|
|
2895
|
+
if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
|
|
2896
|
+
ctx.logger.info("[ObjectQLPlugin] Bridging realtime service to ObjectQL for event publishing");
|
|
2897
|
+
this.ql.setRealtimeService(realtimeService);
|
|
2743
2898
|
}
|
|
2899
|
+
} catch (e) {
|
|
2900
|
+
ctx.logger.debug("[ObjectQLPlugin] No realtime service found \u2014 data events will not be published", {
|
|
2901
|
+
error: e.message
|
|
2902
|
+
});
|
|
2744
2903
|
}
|
|
2745
2904
|
}
|
|
2746
2905
|
await this.ql?.init();
|
|
2906
|
+
await this.restoreMetadataFromDb(ctx);
|
|
2747
2907
|
await this.syncRegisteredSchemas(ctx);
|
|
2908
|
+
await this.bridgeObjectsToMetadataService(ctx);
|
|
2748
2909
|
this.registerAuditHooks(ctx);
|
|
2749
2910
|
this.registerTenantMiddleware(ctx);
|
|
2750
2911
|
ctx.logger.info("ObjectQL engine started", {
|
|
@@ -2937,6 +3098,97 @@ var ObjectQLPlugin = class {
|
|
|
2937
3098
|
ctx.logger.info("Schema sync complete", { synced, skipped, total: allObjects.length });
|
|
2938
3099
|
}
|
|
2939
3100
|
}
|
|
3101
|
+
/**
|
|
3102
|
+
* Restore persisted metadata from the database (sys_metadata) on startup.
|
|
3103
|
+
*
|
|
3104
|
+
* Calls `protocol.loadMetaFromDb()` to bulk-load all active metadata
|
|
3105
|
+
* records (objects, views, apps, etc.) into the in-memory SchemaRegistry.
|
|
3106
|
+
* This closes the persistence loop so that user-created schemas survive
|
|
3107
|
+
* kernel cold starts and redeployments.
|
|
3108
|
+
*
|
|
3109
|
+
* Gracefully degrades when:
|
|
3110
|
+
* - The protocol service is unavailable (e.g., in-memory-only mode).
|
|
3111
|
+
* - `loadMetaFromDb` is not implemented by the protocol shim.
|
|
3112
|
+
* - The underlying driver/table does not exist yet (first-run scenario).
|
|
3113
|
+
*/
|
|
3114
|
+
async restoreMetadataFromDb(ctx) {
|
|
3115
|
+
let protocol;
|
|
3116
|
+
try {
|
|
3117
|
+
const service = ctx.getService("protocol");
|
|
3118
|
+
if (!service || !hasLoadMetaFromDb(service)) {
|
|
3119
|
+
ctx.logger.debug("Protocol service does not support loadMetaFromDb, skipping DB restore");
|
|
3120
|
+
return;
|
|
3121
|
+
}
|
|
3122
|
+
protocol = service;
|
|
3123
|
+
} catch (e) {
|
|
3124
|
+
ctx.logger.debug("Protocol service unavailable, skipping DB restore", {
|
|
3125
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3126
|
+
});
|
|
3127
|
+
return;
|
|
3128
|
+
}
|
|
3129
|
+
try {
|
|
3130
|
+
const { loaded, errors } = await protocol.loadMetaFromDb();
|
|
3131
|
+
if (loaded > 0 || errors > 0) {
|
|
3132
|
+
ctx.logger.info("Metadata restored from database to SchemaRegistry", { loaded, errors });
|
|
3133
|
+
} else {
|
|
3134
|
+
ctx.logger.debug("No persisted metadata found in database");
|
|
3135
|
+
}
|
|
3136
|
+
} catch (e) {
|
|
3137
|
+
ctx.logger.debug("DB metadata restore failed (non-fatal)", {
|
|
3138
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3139
|
+
});
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
/**
|
|
3143
|
+
* Bridge all SchemaRegistry objects to the metadata service.
|
|
3144
|
+
*
|
|
3145
|
+
* This ensures objects registered by plugins and loaded from sys_metadata
|
|
3146
|
+
* are visible to AI tools and other consumers that query IMetadataService.
|
|
3147
|
+
*
|
|
3148
|
+
* Runs after both restoreMetadataFromDb() and syncRegisteredSchemas() to
|
|
3149
|
+
* catch all objects in the SchemaRegistry regardless of their source.
|
|
3150
|
+
*/
|
|
3151
|
+
async bridgeObjectsToMetadataService(ctx) {
|
|
3152
|
+
try {
|
|
3153
|
+
const metadataService = ctx.getService("metadata");
|
|
3154
|
+
if (!metadataService || typeof metadataService.register !== "function") {
|
|
3155
|
+
ctx.logger.debug("Metadata service unavailable for bridging, skipping");
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
if (!this.ql?.registry) {
|
|
3159
|
+
ctx.logger.debug("SchemaRegistry unavailable for bridging, skipping");
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
const objects = this.ql.registry.getAllObjects();
|
|
3163
|
+
let bridged = 0;
|
|
3164
|
+
for (const obj of objects) {
|
|
3165
|
+
try {
|
|
3166
|
+
const existing = await metadataService.getObject(obj.name);
|
|
3167
|
+
if (!existing) {
|
|
3168
|
+
await metadataService.register("object", obj.name, obj);
|
|
3169
|
+
bridged++;
|
|
3170
|
+
}
|
|
3171
|
+
} catch (e) {
|
|
3172
|
+
ctx.logger.debug("Failed to bridge object to metadata service", {
|
|
3173
|
+
object: obj.name,
|
|
3174
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3175
|
+
});
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
if (bridged > 0) {
|
|
3179
|
+
ctx.logger.info("Bridged objects from SchemaRegistry to metadata service", {
|
|
3180
|
+
count: bridged,
|
|
3181
|
+
total: objects.length
|
|
3182
|
+
});
|
|
3183
|
+
} else {
|
|
3184
|
+
ctx.logger.debug("No objects needed bridging (all already in metadata service)");
|
|
3185
|
+
}
|
|
3186
|
+
} catch (e) {
|
|
3187
|
+
ctx.logger.debug("Failed to bridge objects to metadata service", {
|
|
3188
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
2940
3192
|
/**
|
|
2941
3193
|
* Load metadata from external metadata service into ObjectQL registry
|
|
2942
3194
|
* This enables ObjectQL to use file-based or remote metadata
|