@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.js
CHANGED
|
@@ -93,36 +93,45 @@ var SchemaRegistry = class {
|
|
|
93
93
|
// ==========================================
|
|
94
94
|
/**
|
|
95
95
|
* Register a namespace for a package.
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* @throws Error if namespace is already registered to a different package
|
|
96
|
+
* Multiple packages can share the same namespace (e.g. 'sys').
|
|
99
97
|
*/
|
|
100
98
|
static registerNamespace(namespace, packageId) {
|
|
101
99
|
if (!namespace) return;
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
);
|
|
100
|
+
let owners = this.namespaceRegistry.get(namespace);
|
|
101
|
+
if (!owners) {
|
|
102
|
+
owners = /* @__PURE__ */ new Set();
|
|
103
|
+
this.namespaceRegistry.set(namespace, owners);
|
|
107
104
|
}
|
|
108
|
-
|
|
105
|
+
owners.add(packageId);
|
|
109
106
|
this.log(`[Registry] Registered namespace: ${namespace} \u2192 ${packageId}`);
|
|
110
107
|
}
|
|
111
108
|
/**
|
|
112
109
|
* Unregister a namespace when a package is uninstalled.
|
|
113
110
|
*/
|
|
114
111
|
static unregisterNamespace(namespace, packageId) {
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
const owners = this.namespaceRegistry.get(namespace);
|
|
113
|
+
if (owners) {
|
|
114
|
+
owners.delete(packageId);
|
|
115
|
+
if (owners.size === 0) {
|
|
116
|
+
this.namespaceRegistry.delete(namespace);
|
|
117
|
+
}
|
|
118
|
+
this.log(`[Registry] Unregistered namespace: ${namespace} \u2190 ${packageId}`);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
/**
|
|
122
|
-
* Get the
|
|
122
|
+
* Get the packages that use a namespace.
|
|
123
123
|
*/
|
|
124
124
|
static getNamespaceOwner(namespace) {
|
|
125
|
-
|
|
125
|
+
const owners = this.namespaceRegistry.get(namespace);
|
|
126
|
+
if (!owners || owners.size === 0) return void 0;
|
|
127
|
+
return owners.values().next().value;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get all packages that share a namespace.
|
|
131
|
+
*/
|
|
132
|
+
static getNamespaceOwners(namespace) {
|
|
133
|
+
const owners = this.namespaceRegistry.get(namespace);
|
|
134
|
+
return owners ? Array.from(owners) : [];
|
|
126
135
|
}
|
|
127
136
|
// ==========================================
|
|
128
137
|
// Object Registration (Ownership Model)
|
|
@@ -332,7 +341,7 @@ var SchemaRegistry = class {
|
|
|
332
341
|
if (type === "object") {
|
|
333
342
|
return import_data.ObjectSchema.parse(item);
|
|
334
343
|
}
|
|
335
|
-
if (type === "
|
|
344
|
+
if (type === "app") {
|
|
336
345
|
return import_ui.AppSchema.parse(item);
|
|
337
346
|
}
|
|
338
347
|
if (type === "package") {
|
|
@@ -482,13 +491,13 @@ var SchemaRegistry = class {
|
|
|
482
491
|
// App Helpers
|
|
483
492
|
// ==========================================
|
|
484
493
|
static registerApp(app, packageId) {
|
|
485
|
-
this.registerItem("
|
|
494
|
+
this.registerItem("app", app, "name", packageId);
|
|
486
495
|
}
|
|
487
496
|
static getApp(name) {
|
|
488
|
-
return this.getItem("
|
|
497
|
+
return this.getItem("app", name);
|
|
489
498
|
}
|
|
490
499
|
static getAllApps() {
|
|
491
|
-
return this.listItems("
|
|
500
|
+
return this.listItems("app");
|
|
492
501
|
}
|
|
493
502
|
// ==========================================
|
|
494
503
|
// Plugin Helpers
|
|
@@ -534,7 +543,7 @@ SchemaRegistry._logLevel = "info";
|
|
|
534
543
|
SchemaRegistry.objectContributors = /* @__PURE__ */ new Map();
|
|
535
544
|
/** FQN → Merged ServiceObject (cached, invalidated on changes) */
|
|
536
545
|
SchemaRegistry.mergedObjectCache = /* @__PURE__ */ new Map();
|
|
537
|
-
/** Namespace → PackageId (
|
|
546
|
+
/** Namespace → Set<PackageId> (multiple packages can share a namespace) */
|
|
538
547
|
SchemaRegistry.namespaceRegistry = /* @__PURE__ */ new Map();
|
|
539
548
|
// ==========================================
|
|
540
549
|
// Generic metadata storage (non-object types)
|
|
@@ -544,6 +553,7 @@ SchemaRegistry.metadata = /* @__PURE__ */ new Map();
|
|
|
544
553
|
|
|
545
554
|
// src/protocol.ts
|
|
546
555
|
var import_data2 = require("@objectstack/spec/data");
|
|
556
|
+
var import_shared = require("@objectstack/spec/shared");
|
|
547
557
|
function simpleHash(str) {
|
|
548
558
|
let hash = 0;
|
|
549
559
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -671,20 +681,32 @@ var ObjectStackProtocolImplementation = class {
|
|
|
671
681
|
};
|
|
672
682
|
}
|
|
673
683
|
async getMetaTypes() {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
684
|
+
const schemaTypes = SchemaRegistry.getRegisteredTypes();
|
|
685
|
+
let runtimeTypes = [];
|
|
686
|
+
try {
|
|
687
|
+
const services = this.getServicesRegistry?.();
|
|
688
|
+
const metadataService = services?.get("metadata");
|
|
689
|
+
if (metadataService && typeof metadataService.getRegisteredTypes === "function") {
|
|
690
|
+
runtimeTypes = await metadataService.getRegisteredTypes();
|
|
691
|
+
}
|
|
692
|
+
} catch {
|
|
693
|
+
}
|
|
694
|
+
const allTypes = Array.from(/* @__PURE__ */ new Set([...schemaTypes, ...runtimeTypes]));
|
|
695
|
+
return { types: allTypes };
|
|
677
696
|
}
|
|
678
697
|
async getMetaItems(request) {
|
|
679
|
-
|
|
698
|
+
const { packageId } = request;
|
|
699
|
+
let items = SchemaRegistry.listItems(request.type, packageId);
|
|
680
700
|
if (items.length === 0) {
|
|
681
|
-
const alt = request.type
|
|
682
|
-
items = SchemaRegistry.listItems(alt);
|
|
701
|
+
const alt = import_shared.PLURAL_TO_SINGULAR[request.type] ?? import_shared.SINGULAR_TO_PLURAL[request.type];
|
|
702
|
+
if (alt) items = SchemaRegistry.listItems(alt, packageId);
|
|
683
703
|
}
|
|
684
704
|
if (items.length === 0) {
|
|
685
705
|
try {
|
|
706
|
+
const whereClause = { type: request.type, state: "active" };
|
|
707
|
+
if (packageId) whereClause._packageId = packageId;
|
|
686
708
|
const allRecords = await this.engine.find("sys_metadata", {
|
|
687
|
-
where:
|
|
709
|
+
where: whereClause
|
|
688
710
|
});
|
|
689
711
|
if (allRecords && allRecords.length > 0) {
|
|
690
712
|
items = allRecords.map((record) => {
|
|
@@ -693,21 +715,47 @@ var ObjectStackProtocolImplementation = class {
|
|
|
693
715
|
return data;
|
|
694
716
|
});
|
|
695
717
|
} else {
|
|
696
|
-
const alt = request.type
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
if (altRecords && altRecords.length > 0) {
|
|
701
|
-
items = altRecords.map((record) => {
|
|
702
|
-
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
703
|
-
SchemaRegistry.registerItem(request.type, data, "name");
|
|
704
|
-
return data;
|
|
718
|
+
const alt = import_shared.PLURAL_TO_SINGULAR[request.type] ?? import_shared.SINGULAR_TO_PLURAL[request.type];
|
|
719
|
+
if (alt) {
|
|
720
|
+
const altRecords = await this.engine.find("sys_metadata", {
|
|
721
|
+
where: { type: alt, state: "active" }
|
|
705
722
|
});
|
|
723
|
+
if (altRecords && altRecords.length > 0) {
|
|
724
|
+
items = altRecords.map((record) => {
|
|
725
|
+
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
726
|
+
SchemaRegistry.registerItem(request.type, data, "name");
|
|
727
|
+
return data;
|
|
728
|
+
});
|
|
729
|
+
}
|
|
706
730
|
}
|
|
707
731
|
}
|
|
708
732
|
} catch {
|
|
709
733
|
}
|
|
710
734
|
}
|
|
735
|
+
try {
|
|
736
|
+
const services = this.getServicesRegistry?.();
|
|
737
|
+
const metadataService = services?.get("metadata");
|
|
738
|
+
if (metadataService && typeof metadataService.list === "function") {
|
|
739
|
+
const runtimeItems = await metadataService.list(request.type);
|
|
740
|
+
if (runtimeItems && runtimeItems.length > 0) {
|
|
741
|
+
const itemMap = /* @__PURE__ */ new Map();
|
|
742
|
+
for (const item of items) {
|
|
743
|
+
const entry = item;
|
|
744
|
+
if (entry && typeof entry === "object" && "name" in entry) {
|
|
745
|
+
itemMap.set(entry.name, entry);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
for (const item of runtimeItems) {
|
|
749
|
+
const entry = item;
|
|
750
|
+
if (entry && typeof entry === "object" && "name" in entry) {
|
|
751
|
+
itemMap.set(entry.name, entry);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
items = Array.from(itemMap.values());
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
} catch {
|
|
758
|
+
}
|
|
711
759
|
return {
|
|
712
760
|
type: request.type,
|
|
713
761
|
items
|
|
@@ -716,8 +764,8 @@ var ObjectStackProtocolImplementation = class {
|
|
|
716
764
|
async getMetaItem(request) {
|
|
717
765
|
let item = SchemaRegistry.getItem(request.type, request.name);
|
|
718
766
|
if (item === void 0) {
|
|
719
|
-
const alt = request.type
|
|
720
|
-
item = SchemaRegistry.getItem(alt, request.name);
|
|
767
|
+
const alt = import_shared.PLURAL_TO_SINGULAR[request.type] ?? import_shared.SINGULAR_TO_PLURAL[request.type];
|
|
768
|
+
if (alt) item = SchemaRegistry.getItem(alt, request.name);
|
|
721
769
|
}
|
|
722
770
|
if (item === void 0) {
|
|
723
771
|
try {
|
|
@@ -728,18 +776,30 @@ var ObjectStackProtocolImplementation = class {
|
|
|
728
776
|
item = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
729
777
|
SchemaRegistry.registerItem(request.type, item, "name");
|
|
730
778
|
} else {
|
|
731
|
-
const alt = request.type
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
779
|
+
const alt = import_shared.PLURAL_TO_SINGULAR[request.type] ?? import_shared.SINGULAR_TO_PLURAL[request.type];
|
|
780
|
+
if (alt) {
|
|
781
|
+
const altRecord = await this.engine.findOne("sys_metadata", {
|
|
782
|
+
where: { type: alt, name: request.name, state: "active" }
|
|
783
|
+
});
|
|
784
|
+
if (altRecord) {
|
|
785
|
+
item = typeof altRecord.metadata === "string" ? JSON.parse(altRecord.metadata) : altRecord.metadata;
|
|
786
|
+
SchemaRegistry.registerItem(request.type, item, "name");
|
|
787
|
+
}
|
|
738
788
|
}
|
|
739
789
|
}
|
|
740
790
|
} catch {
|
|
741
791
|
}
|
|
742
792
|
}
|
|
793
|
+
if (item === void 0) {
|
|
794
|
+
try {
|
|
795
|
+
const services = this.getServicesRegistry?.();
|
|
796
|
+
const metadataService = services?.get("metadata");
|
|
797
|
+
if (metadataService && typeof metadataService.get === "function") {
|
|
798
|
+
item = await metadataService.get(request.type, request.name);
|
|
799
|
+
}
|
|
800
|
+
} catch {
|
|
801
|
+
}
|
|
802
|
+
}
|
|
743
803
|
return {
|
|
744
804
|
type: request.type,
|
|
745
805
|
name: request.name,
|
|
@@ -912,10 +972,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
912
972
|
const records = await this.engine.find(request.object, options);
|
|
913
973
|
return {
|
|
914
974
|
object: request.object,
|
|
915
|
-
value: records,
|
|
916
|
-
// OData compatibility
|
|
917
975
|
records,
|
|
918
|
-
// Legacy
|
|
919
976
|
total: records.length,
|
|
920
977
|
hasMore: false
|
|
921
978
|
};
|
|
@@ -973,7 +1030,21 @@ var ObjectStackProtocolImplementation = class {
|
|
|
973
1030
|
// ==========================================
|
|
974
1031
|
async getMetaItemCached(request) {
|
|
975
1032
|
try {
|
|
976
|
-
|
|
1033
|
+
let item = SchemaRegistry.getItem(request.type, request.name);
|
|
1034
|
+
if (!item) {
|
|
1035
|
+
const alt = import_shared.PLURAL_TO_SINGULAR[request.type] ?? import_shared.SINGULAR_TO_PLURAL[request.type];
|
|
1036
|
+
if (alt) item = SchemaRegistry.getItem(alt, request.name);
|
|
1037
|
+
}
|
|
1038
|
+
if (!item) {
|
|
1039
|
+
try {
|
|
1040
|
+
const services = this.getServicesRegistry?.();
|
|
1041
|
+
const metadataService = services?.get("metadata");
|
|
1042
|
+
if (metadataService && typeof metadataService.get === "function") {
|
|
1043
|
+
item = await metadataService.get(request.type, request.name);
|
|
1044
|
+
}
|
|
1045
|
+
} catch {
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
977
1048
|
if (!item) {
|
|
978
1049
|
throw new Error(`Metadata item ${request.type}/${request.name} not found`);
|
|
979
1050
|
}
|
|
@@ -1325,10 +1396,11 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1325
1396
|
for (const record of records) {
|
|
1326
1397
|
try {
|
|
1327
1398
|
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
1328
|
-
|
|
1399
|
+
const normalizedType = import_shared.PLURAL_TO_SINGULAR[record.type] ?? record.type;
|
|
1400
|
+
if (normalizedType === "object") {
|
|
1329
1401
|
SchemaRegistry.registerObject(data, record.packageId || "sys_metadata");
|
|
1330
1402
|
} else {
|
|
1331
|
-
SchemaRegistry.registerItem(
|
|
1403
|
+
SchemaRegistry.registerItem(normalizedType, data, "name");
|
|
1332
1404
|
}
|
|
1333
1405
|
loaded++;
|
|
1334
1406
|
} catch (e) {
|
|
@@ -1478,6 +1550,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1478
1550
|
var import_kernel2 = require("@objectstack/spec/kernel");
|
|
1479
1551
|
var import_core = require("@objectstack/core");
|
|
1480
1552
|
var import_system = require("@objectstack/spec/system");
|
|
1553
|
+
var import_shared2 = require("@objectstack/spec/shared");
|
|
1481
1554
|
var _ObjectQL = class _ObjectQL {
|
|
1482
1555
|
constructor(hostContext = {}) {
|
|
1483
1556
|
this.drivers = /* @__PURE__ */ new Map();
|
|
@@ -1768,7 +1841,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1768
1841
|
for (const item of items) {
|
|
1769
1842
|
const itemName = item.name || item.id;
|
|
1770
1843
|
if (itemName) {
|
|
1771
|
-
SchemaRegistry.registerItem(key, item, "name", id);
|
|
1844
|
+
SchemaRegistry.registerItem((0, import_shared2.pluralToSingular)(key), item, "name", id);
|
|
1772
1845
|
}
|
|
1773
1846
|
}
|
|
1774
1847
|
}
|
|
@@ -1874,7 +1947,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1874
1947
|
for (const item of items) {
|
|
1875
1948
|
const itemName = item.name || item.id;
|
|
1876
1949
|
if (itemName) {
|
|
1877
|
-
SchemaRegistry.registerItem(key, item, "name", ownerId);
|
|
1950
|
+
SchemaRegistry.registerItem((0, import_shared2.pluralToSingular)(key), item, "name", ownerId);
|
|
1878
1951
|
}
|
|
1879
1952
|
}
|
|
1880
1953
|
}
|
|
@@ -1898,6 +1971,16 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1898
1971
|
this.logger.info("Set default driver", { driverName: driver.name });
|
|
1899
1972
|
}
|
|
1900
1973
|
}
|
|
1974
|
+
/**
|
|
1975
|
+
* Set the realtime service for publishing data change events.
|
|
1976
|
+
* Should be called after kernel resolves the realtime service.
|
|
1977
|
+
*
|
|
1978
|
+
* @param service - An IRealtimeService instance for event publishing
|
|
1979
|
+
*/
|
|
1980
|
+
setRealtimeService(service) {
|
|
1981
|
+
this.realtimeService = service;
|
|
1982
|
+
this.logger.info("RealtimeService configured for data events");
|
|
1983
|
+
}
|
|
1901
1984
|
/**
|
|
1902
1985
|
* Helper to get object definition
|
|
1903
1986
|
*/
|
|
@@ -1952,14 +2035,22 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1952
2035
|
driverCount: this.drivers.size,
|
|
1953
2036
|
drivers: Array.from(this.drivers.keys())
|
|
1954
2037
|
});
|
|
2038
|
+
const failedDrivers = [];
|
|
1955
2039
|
for (const [name, driver] of this.drivers) {
|
|
1956
2040
|
try {
|
|
1957
2041
|
await driver.connect();
|
|
1958
2042
|
this.logger.info("Driver connected successfully", { driverName: name });
|
|
1959
2043
|
} catch (e) {
|
|
2044
|
+
failedDrivers.push(name);
|
|
1960
2045
|
this.logger.error("Failed to connect driver", e, { driverName: name });
|
|
1961
2046
|
}
|
|
1962
2047
|
}
|
|
2048
|
+
if (failedDrivers.length > 0) {
|
|
2049
|
+
this.logger.warn(
|
|
2050
|
+
`${failedDrivers.length} of ${this.drivers.size} driver(s) failed initial connect. Operations may recover via lazy reconnection or fail at query time.`,
|
|
2051
|
+
{ failedDrivers }
|
|
2052
|
+
);
|
|
2053
|
+
}
|
|
1963
2054
|
this.logger.info("ObjectQL engine initialization complete");
|
|
1964
2055
|
}
|
|
1965
2056
|
async destroy() {
|
|
@@ -2161,6 +2252,39 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2161
2252
|
hookContext.event = "afterInsert";
|
|
2162
2253
|
hookContext.result = result;
|
|
2163
2254
|
await this.triggerHooks("afterInsert", hookContext);
|
|
2255
|
+
if (this.realtimeService) {
|
|
2256
|
+
try {
|
|
2257
|
+
if (Array.isArray(result)) {
|
|
2258
|
+
for (const record of result) {
|
|
2259
|
+
const event = {
|
|
2260
|
+
type: "data.record.created",
|
|
2261
|
+
object,
|
|
2262
|
+
payload: {
|
|
2263
|
+
recordId: record.id,
|
|
2264
|
+
after: record
|
|
2265
|
+
},
|
|
2266
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2267
|
+
};
|
|
2268
|
+
await this.realtimeService.publish(event);
|
|
2269
|
+
}
|
|
2270
|
+
this.logger.debug(`Published ${result.length} data.record.created events`, { object });
|
|
2271
|
+
} else {
|
|
2272
|
+
const event = {
|
|
2273
|
+
type: "data.record.created",
|
|
2274
|
+
object,
|
|
2275
|
+
payload: {
|
|
2276
|
+
recordId: result.id,
|
|
2277
|
+
after: result
|
|
2278
|
+
},
|
|
2279
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2280
|
+
};
|
|
2281
|
+
await this.realtimeService.publish(event);
|
|
2282
|
+
this.logger.debug("Published data.record.created event", { object, recordId: result.id });
|
|
2283
|
+
}
|
|
2284
|
+
} catch (error) {
|
|
2285
|
+
this.logger.warn("Failed to publish data event", { object, error });
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2164
2288
|
return hookContext.result;
|
|
2165
2289
|
} catch (e) {
|
|
2166
2290
|
this.logger.error("Insert operation failed", e, { object });
|
|
@@ -2207,6 +2331,26 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2207
2331
|
hookContext.event = "afterUpdate";
|
|
2208
2332
|
hookContext.result = result;
|
|
2209
2333
|
await this.triggerHooks("afterUpdate", hookContext);
|
|
2334
|
+
if (this.realtimeService) {
|
|
2335
|
+
try {
|
|
2336
|
+
const resultId = typeof result === "object" && result && "id" in result ? result.id : void 0;
|
|
2337
|
+
const recordId = String(hookContext.input.id || resultId || "");
|
|
2338
|
+
const event = {
|
|
2339
|
+
type: "data.record.updated",
|
|
2340
|
+
object,
|
|
2341
|
+
payload: {
|
|
2342
|
+
recordId,
|
|
2343
|
+
changes: hookContext.input.data,
|
|
2344
|
+
after: result
|
|
2345
|
+
},
|
|
2346
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2347
|
+
};
|
|
2348
|
+
await this.realtimeService.publish(event);
|
|
2349
|
+
this.logger.debug("Published data.record.updated event", { object, recordId });
|
|
2350
|
+
} catch (error) {
|
|
2351
|
+
this.logger.warn("Failed to publish data event", { object, error });
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2210
2354
|
return hookContext.result;
|
|
2211
2355
|
} catch (e) {
|
|
2212
2356
|
this.logger.error("Update operation failed", e, { object });
|
|
@@ -2252,6 +2396,24 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2252
2396
|
hookContext.event = "afterDelete";
|
|
2253
2397
|
hookContext.result = result;
|
|
2254
2398
|
await this.triggerHooks("afterDelete", hookContext);
|
|
2399
|
+
if (this.realtimeService) {
|
|
2400
|
+
try {
|
|
2401
|
+
const resultId = typeof result === "object" && result && "id" in result ? result.id : void 0;
|
|
2402
|
+
const recordId = String(hookContext.input.id || resultId || "");
|
|
2403
|
+
const event = {
|
|
2404
|
+
type: "data.record.deleted",
|
|
2405
|
+
object,
|
|
2406
|
+
payload: {
|
|
2407
|
+
recordId
|
|
2408
|
+
},
|
|
2409
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2410
|
+
};
|
|
2411
|
+
await this.realtimeService.publish(event);
|
|
2412
|
+
this.logger.debug("Published data.record.deleted event", { object, recordId });
|
|
2413
|
+
} catch (error) {
|
|
2414
|
+
this.logger.warn("Failed to publish data event", { object, error });
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2255
2417
|
return hookContext.result;
|
|
2256
2418
|
} catch (e) {
|
|
2257
2419
|
this.logger.error("Delete operation failed", e, { object });
|
|
@@ -2709,6 +2871,9 @@ var MetadataFacade = class {
|
|
|
2709
2871
|
};
|
|
2710
2872
|
|
|
2711
2873
|
// src/plugin.ts
|
|
2874
|
+
function hasLoadMetaFromDb(service) {
|
|
2875
|
+
return typeof service === "object" && service !== null && typeof service["loadMetaFromDb"] === "function";
|
|
2876
|
+
}
|
|
2712
2877
|
var ObjectQLPlugin = class {
|
|
2713
2878
|
constructor(ql, hostContext) {
|
|
2714
2879
|
this.name = "com.objectstack.engine.objectql";
|
|
@@ -2720,38 +2885,18 @@ var ObjectQLPlugin = class {
|
|
|
2720
2885
|
this.ql = new ObjectQL(hostCtx);
|
|
2721
2886
|
}
|
|
2722
2887
|
ctx.registerService("objectql", this.ql);
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
} catch (e) {
|
|
2731
|
-
}
|
|
2732
|
-
if (!hasMetadata) {
|
|
2733
|
-
try {
|
|
2734
|
-
const metadataFacade = new MetadataFacade();
|
|
2735
|
-
ctx.registerService("metadata", metadataFacade);
|
|
2736
|
-
ctx.logger.info("MetadataFacade registered as metadata service", {
|
|
2737
|
-
mode: "in-memory",
|
|
2738
|
-
features: ["registry", "fast-lookup"]
|
|
2888
|
+
ctx.registerService("data", this.ql);
|
|
2889
|
+
const ql = this.ql;
|
|
2890
|
+
ctx.registerService("manifest", {
|
|
2891
|
+
register: (manifest) => {
|
|
2892
|
+
ql.registerApp(manifest);
|
|
2893
|
+
ctx.logger.debug("Manifest registered via manifest service", {
|
|
2894
|
+
id: manifest.id || manifest.name
|
|
2739
2895
|
});
|
|
2740
|
-
} catch (e) {
|
|
2741
|
-
if (!e.message?.includes("already registered")) {
|
|
2742
|
-
throw e;
|
|
2743
|
-
}
|
|
2744
2896
|
}
|
|
2745
|
-
}
|
|
2746
|
-
ctx.logger.info("External metadata service detected", {
|
|
2747
|
-
provider: metadataProvider,
|
|
2748
|
-
mode: "will-sync-in-start-phase"
|
|
2749
|
-
});
|
|
2750
|
-
}
|
|
2751
|
-
ctx.registerService("data", this.ql);
|
|
2897
|
+
});
|
|
2752
2898
|
ctx.logger.info("ObjectQL engine registered", {
|
|
2753
|
-
services: ["objectql", "data"]
|
|
2754
|
-
metadataProvider
|
|
2899
|
+
services: ["objectql", "data", "manifest"]
|
|
2755
2900
|
});
|
|
2756
2901
|
const protocolShim = new ObjectStackProtocolImplementation(
|
|
2757
2902
|
this.ql,
|
|
@@ -2764,7 +2909,7 @@ var ObjectQLPlugin = class {
|
|
|
2764
2909
|
ctx.logger.info("ObjectQL engine starting...");
|
|
2765
2910
|
try {
|
|
2766
2911
|
const metadataService = ctx.getService("metadata");
|
|
2767
|
-
if (metadataService &&
|
|
2912
|
+
if (metadataService && typeof metadataService.loadMany === "function" && this.ql) {
|
|
2768
2913
|
await this.loadMetadataFromService(metadataService, ctx);
|
|
2769
2914
|
}
|
|
2770
2915
|
} catch (e) {
|
|
@@ -2778,13 +2923,29 @@ var ObjectQLPlugin = class {
|
|
|
2778
2923
|
ctx.logger.debug("Discovered and registered driver service", { serviceName: name });
|
|
2779
2924
|
}
|
|
2780
2925
|
if (name.startsWith("app.")) {
|
|
2926
|
+
ctx.logger.warn(
|
|
2927
|
+
`[DEPRECATED] Service "${name}" uses legacy app.* convention. Migrate to ctx.getService('manifest').register(data).`
|
|
2928
|
+
);
|
|
2781
2929
|
this.ql.registerApp(service);
|
|
2782
|
-
ctx.logger.debug("Discovered and registered app service", { serviceName: name });
|
|
2930
|
+
ctx.logger.debug("Discovered and registered app service (legacy)", { serviceName: name });
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
try {
|
|
2934
|
+
const realtimeService = ctx.getService("realtime");
|
|
2935
|
+
if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
|
|
2936
|
+
ctx.logger.info("[ObjectQLPlugin] Bridging realtime service to ObjectQL for event publishing");
|
|
2937
|
+
this.ql.setRealtimeService(realtimeService);
|
|
2783
2938
|
}
|
|
2939
|
+
} catch (e) {
|
|
2940
|
+
ctx.logger.debug("[ObjectQLPlugin] No realtime service found \u2014 data events will not be published", {
|
|
2941
|
+
error: e.message
|
|
2942
|
+
});
|
|
2784
2943
|
}
|
|
2785
2944
|
}
|
|
2786
2945
|
await this.ql?.init();
|
|
2946
|
+
await this.restoreMetadataFromDb(ctx);
|
|
2787
2947
|
await this.syncRegisteredSchemas(ctx);
|
|
2948
|
+
await this.bridgeObjectsToMetadataService(ctx);
|
|
2788
2949
|
this.registerAuditHooks(ctx);
|
|
2789
2950
|
this.registerTenantMiddleware(ctx);
|
|
2790
2951
|
ctx.logger.info("ObjectQL engine started", {
|
|
@@ -2977,6 +3138,97 @@ var ObjectQLPlugin = class {
|
|
|
2977
3138
|
ctx.logger.info("Schema sync complete", { synced, skipped, total: allObjects.length });
|
|
2978
3139
|
}
|
|
2979
3140
|
}
|
|
3141
|
+
/**
|
|
3142
|
+
* Restore persisted metadata from the database (sys_metadata) on startup.
|
|
3143
|
+
*
|
|
3144
|
+
* Calls `protocol.loadMetaFromDb()` to bulk-load all active metadata
|
|
3145
|
+
* records (objects, views, apps, etc.) into the in-memory SchemaRegistry.
|
|
3146
|
+
* This closes the persistence loop so that user-created schemas survive
|
|
3147
|
+
* kernel cold starts and redeployments.
|
|
3148
|
+
*
|
|
3149
|
+
* Gracefully degrades when:
|
|
3150
|
+
* - The protocol service is unavailable (e.g., in-memory-only mode).
|
|
3151
|
+
* - `loadMetaFromDb` is not implemented by the protocol shim.
|
|
3152
|
+
* - The underlying driver/table does not exist yet (first-run scenario).
|
|
3153
|
+
*/
|
|
3154
|
+
async restoreMetadataFromDb(ctx) {
|
|
3155
|
+
let protocol;
|
|
3156
|
+
try {
|
|
3157
|
+
const service = ctx.getService("protocol");
|
|
3158
|
+
if (!service || !hasLoadMetaFromDb(service)) {
|
|
3159
|
+
ctx.logger.debug("Protocol service does not support loadMetaFromDb, skipping DB restore");
|
|
3160
|
+
return;
|
|
3161
|
+
}
|
|
3162
|
+
protocol = service;
|
|
3163
|
+
} catch (e) {
|
|
3164
|
+
ctx.logger.debug("Protocol service unavailable, skipping DB restore", {
|
|
3165
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3166
|
+
});
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
try {
|
|
3170
|
+
const { loaded, errors } = await protocol.loadMetaFromDb();
|
|
3171
|
+
if (loaded > 0 || errors > 0) {
|
|
3172
|
+
ctx.logger.info("Metadata restored from database to SchemaRegistry", { loaded, errors });
|
|
3173
|
+
} else {
|
|
3174
|
+
ctx.logger.debug("No persisted metadata found in database");
|
|
3175
|
+
}
|
|
3176
|
+
} catch (e) {
|
|
3177
|
+
ctx.logger.debug("DB metadata restore failed (non-fatal)", {
|
|
3178
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
/**
|
|
3183
|
+
* Bridge all SchemaRegistry objects to the metadata service.
|
|
3184
|
+
*
|
|
3185
|
+
* This ensures objects registered by plugins and loaded from sys_metadata
|
|
3186
|
+
* are visible to AI tools and other consumers that query IMetadataService.
|
|
3187
|
+
*
|
|
3188
|
+
* Runs after both restoreMetadataFromDb() and syncRegisteredSchemas() to
|
|
3189
|
+
* catch all objects in the SchemaRegistry regardless of their source.
|
|
3190
|
+
*/
|
|
3191
|
+
async bridgeObjectsToMetadataService(ctx) {
|
|
3192
|
+
try {
|
|
3193
|
+
const metadataService = ctx.getService("metadata");
|
|
3194
|
+
if (!metadataService || typeof metadataService.register !== "function") {
|
|
3195
|
+
ctx.logger.debug("Metadata service unavailable for bridging, skipping");
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
3198
|
+
if (!this.ql?.registry) {
|
|
3199
|
+
ctx.logger.debug("SchemaRegistry unavailable for bridging, skipping");
|
|
3200
|
+
return;
|
|
3201
|
+
}
|
|
3202
|
+
const objects = this.ql.registry.getAllObjects();
|
|
3203
|
+
let bridged = 0;
|
|
3204
|
+
for (const obj of objects) {
|
|
3205
|
+
try {
|
|
3206
|
+
const existing = await metadataService.getObject(obj.name);
|
|
3207
|
+
if (!existing) {
|
|
3208
|
+
await metadataService.register("object", obj.name, obj);
|
|
3209
|
+
bridged++;
|
|
3210
|
+
}
|
|
3211
|
+
} catch (e) {
|
|
3212
|
+
ctx.logger.debug("Failed to bridge object to metadata service", {
|
|
3213
|
+
object: obj.name,
|
|
3214
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3215
|
+
});
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
if (bridged > 0) {
|
|
3219
|
+
ctx.logger.info("Bridged objects from SchemaRegistry to metadata service", {
|
|
3220
|
+
count: bridged,
|
|
3221
|
+
total: objects.length
|
|
3222
|
+
});
|
|
3223
|
+
} else {
|
|
3224
|
+
ctx.logger.debug("No objects needed bridging (all already in metadata service)");
|
|
3225
|
+
}
|
|
3226
|
+
} catch (e) {
|
|
3227
|
+
ctx.logger.debug("Failed to bridge objects to metadata service", {
|
|
3228
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3229
|
+
});
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
2980
3232
|
/**
|
|
2981
3233
|
* Load metadata from external metadata service into ObjectQL registry
|
|
2982
3234
|
* This enables ObjectQL to use file-based or remote metadata
|