@objectstack/objectql 4.0.2 → 4.0.4
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 +10 -10
- package/CHANGELOG.md +23 -0
- package/dist/index.d.mts +72 -6
- package/dist/index.d.ts +72 -6
- package/dist/index.js +385 -43
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +385 -43
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/datasource-mapping.test.ts +181 -0
- package/src/engine.test.ts +15 -1
- package/src/engine.ts +253 -27
- package/src/plugin.integration.test.ts +170 -0
- package/src/plugin.ts +149 -1
- package/src/protocol.ts +107 -19
- package/src/registry.test.ts +11 -11
- package/src/registry.ts +4 -4
package/dist/index.js
CHANGED
|
@@ -341,7 +341,7 @@ var SchemaRegistry = class {
|
|
|
341
341
|
if (type === "object") {
|
|
342
342
|
return import_data.ObjectSchema.parse(item);
|
|
343
343
|
}
|
|
344
|
-
if (type === "
|
|
344
|
+
if (type === "app") {
|
|
345
345
|
return import_ui.AppSchema.parse(item);
|
|
346
346
|
}
|
|
347
347
|
if (type === "package") {
|
|
@@ -491,13 +491,13 @@ var SchemaRegistry = class {
|
|
|
491
491
|
// App Helpers
|
|
492
492
|
// ==========================================
|
|
493
493
|
static registerApp(app, packageId) {
|
|
494
|
-
this.registerItem("
|
|
494
|
+
this.registerItem("app", app, "name", packageId);
|
|
495
495
|
}
|
|
496
496
|
static getApp(name) {
|
|
497
|
-
return this.getItem("
|
|
497
|
+
return this.getItem("app", name);
|
|
498
498
|
}
|
|
499
499
|
static getAllApps() {
|
|
500
|
-
return this.listItems("
|
|
500
|
+
return this.listItems("app");
|
|
501
501
|
}
|
|
502
502
|
// ==========================================
|
|
503
503
|
// Plugin Helpers
|
|
@@ -553,6 +553,7 @@ SchemaRegistry.metadata = /* @__PURE__ */ new Map();
|
|
|
553
553
|
|
|
554
554
|
// src/protocol.ts
|
|
555
555
|
var import_data2 = require("@objectstack/spec/data");
|
|
556
|
+
var import_shared = require("@objectstack/spec/shared");
|
|
556
557
|
function simpleHash(str) {
|
|
557
558
|
let hash = 0;
|
|
558
559
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -680,16 +681,25 @@ var ObjectStackProtocolImplementation = class {
|
|
|
680
681
|
};
|
|
681
682
|
}
|
|
682
683
|
async getMetaTypes() {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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 };
|
|
686
696
|
}
|
|
687
697
|
async getMetaItems(request) {
|
|
688
698
|
const { packageId } = request;
|
|
689
699
|
let items = SchemaRegistry.listItems(request.type, packageId);
|
|
690
700
|
if (items.length === 0) {
|
|
691
|
-
const alt = request.type
|
|
692
|
-
items = SchemaRegistry.listItems(alt, packageId);
|
|
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);
|
|
693
703
|
}
|
|
694
704
|
if (items.length === 0) {
|
|
695
705
|
try {
|
|
@@ -705,21 +715,50 @@ var ObjectStackProtocolImplementation = class {
|
|
|
705
715
|
return data;
|
|
706
716
|
});
|
|
707
717
|
} else {
|
|
708
|
-
const alt = request.type
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
if (altRecords && altRecords.length > 0) {
|
|
713
|
-
items = altRecords.map((record) => {
|
|
714
|
-
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
715
|
-
SchemaRegistry.registerItem(request.type, data, "name");
|
|
716
|
-
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" }
|
|
717
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
|
+
}
|
|
718
730
|
}
|
|
719
731
|
}
|
|
720
732
|
} catch {
|
|
721
733
|
}
|
|
722
734
|
}
|
|
735
|
+
try {
|
|
736
|
+
const services = this.getServicesRegistry?.();
|
|
737
|
+
const metadataService = services?.get("metadata");
|
|
738
|
+
if (metadataService && typeof metadataService.list === "function") {
|
|
739
|
+
let runtimeItems = await metadataService.list(request.type);
|
|
740
|
+
if (packageId && runtimeItems && runtimeItems.length > 0) {
|
|
741
|
+
runtimeItems = runtimeItems.filter((item) => item?._packageId === packageId);
|
|
742
|
+
}
|
|
743
|
+
if (runtimeItems && runtimeItems.length > 0) {
|
|
744
|
+
const itemMap = /* @__PURE__ */ new Map();
|
|
745
|
+
for (const item of items) {
|
|
746
|
+
const entry = item;
|
|
747
|
+
if (entry && typeof entry === "object" && "name" in entry) {
|
|
748
|
+
itemMap.set(entry.name, entry);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
for (const item of runtimeItems) {
|
|
752
|
+
const entry = item;
|
|
753
|
+
if (entry && typeof entry === "object" && "name" in entry) {
|
|
754
|
+
itemMap.set(entry.name, entry);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
items = Array.from(itemMap.values());
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
} catch {
|
|
761
|
+
}
|
|
723
762
|
return {
|
|
724
763
|
type: request.type,
|
|
725
764
|
items
|
|
@@ -728,8 +767,8 @@ var ObjectStackProtocolImplementation = class {
|
|
|
728
767
|
async getMetaItem(request) {
|
|
729
768
|
let item = SchemaRegistry.getItem(request.type, request.name);
|
|
730
769
|
if (item === void 0) {
|
|
731
|
-
const alt = request.type
|
|
732
|
-
item = SchemaRegistry.getItem(alt, request.name);
|
|
770
|
+
const alt = import_shared.PLURAL_TO_SINGULAR[request.type] ?? import_shared.SINGULAR_TO_PLURAL[request.type];
|
|
771
|
+
if (alt) item = SchemaRegistry.getItem(alt, request.name);
|
|
733
772
|
}
|
|
734
773
|
if (item === void 0) {
|
|
735
774
|
try {
|
|
@@ -740,18 +779,30 @@ var ObjectStackProtocolImplementation = class {
|
|
|
740
779
|
item = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
741
780
|
SchemaRegistry.registerItem(request.type, item, "name");
|
|
742
781
|
} else {
|
|
743
|
-
const alt = request.type
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
782
|
+
const alt = import_shared.PLURAL_TO_SINGULAR[request.type] ?? import_shared.SINGULAR_TO_PLURAL[request.type];
|
|
783
|
+
if (alt) {
|
|
784
|
+
const altRecord = await this.engine.findOne("sys_metadata", {
|
|
785
|
+
where: { type: alt, name: request.name, state: "active" }
|
|
786
|
+
});
|
|
787
|
+
if (altRecord) {
|
|
788
|
+
item = typeof altRecord.metadata === "string" ? JSON.parse(altRecord.metadata) : altRecord.metadata;
|
|
789
|
+
SchemaRegistry.registerItem(request.type, item, "name");
|
|
790
|
+
}
|
|
750
791
|
}
|
|
751
792
|
}
|
|
752
793
|
} catch {
|
|
753
794
|
}
|
|
754
795
|
}
|
|
796
|
+
if (item === void 0) {
|
|
797
|
+
try {
|
|
798
|
+
const services = this.getServicesRegistry?.();
|
|
799
|
+
const metadataService = services?.get("metadata");
|
|
800
|
+
if (metadataService && typeof metadataService.get === "function") {
|
|
801
|
+
item = await metadataService.get(request.type, request.name);
|
|
802
|
+
}
|
|
803
|
+
} catch {
|
|
804
|
+
}
|
|
805
|
+
}
|
|
755
806
|
return {
|
|
756
807
|
type: request.type,
|
|
757
808
|
name: request.name,
|
|
@@ -982,7 +1033,21 @@ var ObjectStackProtocolImplementation = class {
|
|
|
982
1033
|
// ==========================================
|
|
983
1034
|
async getMetaItemCached(request) {
|
|
984
1035
|
try {
|
|
985
|
-
|
|
1036
|
+
let item = SchemaRegistry.getItem(request.type, request.name);
|
|
1037
|
+
if (!item) {
|
|
1038
|
+
const alt = import_shared.PLURAL_TO_SINGULAR[request.type] ?? import_shared.SINGULAR_TO_PLURAL[request.type];
|
|
1039
|
+
if (alt) item = SchemaRegistry.getItem(alt, request.name);
|
|
1040
|
+
}
|
|
1041
|
+
if (!item) {
|
|
1042
|
+
try {
|
|
1043
|
+
const services = this.getServicesRegistry?.();
|
|
1044
|
+
const metadataService = services?.get("metadata");
|
|
1045
|
+
if (metadataService && typeof metadataService.get === "function") {
|
|
1046
|
+
item = await metadataService.get(request.type, request.name);
|
|
1047
|
+
}
|
|
1048
|
+
} catch {
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
986
1051
|
if (!item) {
|
|
987
1052
|
throw new Error(`Metadata item ${request.type}/${request.name} not found`);
|
|
988
1053
|
}
|
|
@@ -1334,10 +1399,11 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1334
1399
|
for (const record of records) {
|
|
1335
1400
|
try {
|
|
1336
1401
|
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
1337
|
-
|
|
1402
|
+
const normalizedType = import_shared.PLURAL_TO_SINGULAR[record.type] ?? record.type;
|
|
1403
|
+
if (normalizedType === "object") {
|
|
1338
1404
|
SchemaRegistry.registerObject(data, record.packageId || "sys_metadata");
|
|
1339
1405
|
} else {
|
|
1340
|
-
SchemaRegistry.registerItem(
|
|
1406
|
+
SchemaRegistry.registerItem(normalizedType, data, "name");
|
|
1341
1407
|
}
|
|
1342
1408
|
loaded++;
|
|
1343
1409
|
} catch (e) {
|
|
@@ -1487,10 +1553,15 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1487
1553
|
var import_kernel2 = require("@objectstack/spec/kernel");
|
|
1488
1554
|
var import_core = require("@objectstack/core");
|
|
1489
1555
|
var import_system = require("@objectstack/spec/system");
|
|
1556
|
+
var import_shared2 = require("@objectstack/spec/shared");
|
|
1490
1557
|
var _ObjectQL = class _ObjectQL {
|
|
1491
1558
|
constructor(hostContext = {}) {
|
|
1492
1559
|
this.drivers = /* @__PURE__ */ new Map();
|
|
1493
1560
|
this.defaultDriver = null;
|
|
1561
|
+
// Datasource mapping rules (imported from defineStack)
|
|
1562
|
+
this.datasourceMapping = [];
|
|
1563
|
+
// Package manifests registry (for defaultDatasource lookup)
|
|
1564
|
+
this.manifests = /* @__PURE__ */ new Map();
|
|
1494
1565
|
// Per-object hooks with priority support
|
|
1495
1566
|
this.hooks = /* @__PURE__ */ new Map([
|
|
1496
1567
|
["beforeFind", []],
|
|
@@ -1688,6 +1759,9 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1688
1759
|
const id = manifest.id || manifest.name;
|
|
1689
1760
|
const namespace = manifest.namespace;
|
|
1690
1761
|
this.logger.debug("Registering package manifest", { id, namespace });
|
|
1762
|
+
if (id) {
|
|
1763
|
+
this.manifests.set(id, manifest);
|
|
1764
|
+
}
|
|
1691
1765
|
SchemaRegistry.installPackage(manifest);
|
|
1692
1766
|
this.logger.debug("Installed Package", { id: manifest.id, name: manifest.name, namespace });
|
|
1693
1767
|
if (manifest.objects) {
|
|
@@ -1777,7 +1851,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1777
1851
|
for (const item of items) {
|
|
1778
1852
|
const itemName = item.name || item.id;
|
|
1779
1853
|
if (itemName) {
|
|
1780
|
-
SchemaRegistry.registerItem(key, item, "name", id);
|
|
1854
|
+
SchemaRegistry.registerItem((0, import_shared2.pluralToSingular)(key), item, "name", id);
|
|
1781
1855
|
}
|
|
1782
1856
|
}
|
|
1783
1857
|
}
|
|
@@ -1883,7 +1957,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1883
1957
|
for (const item of items) {
|
|
1884
1958
|
const itemName = item.name || item.id;
|
|
1885
1959
|
if (itemName) {
|
|
1886
|
-
SchemaRegistry.registerItem(key, item, "name", ownerId);
|
|
1960
|
+
SchemaRegistry.registerItem((0, import_shared2.pluralToSingular)(key), item, "name", ownerId);
|
|
1887
1961
|
}
|
|
1888
1962
|
}
|
|
1889
1963
|
}
|
|
@@ -1907,6 +1981,16 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1907
1981
|
this.logger.info("Set default driver", { driverName: driver.name });
|
|
1908
1982
|
}
|
|
1909
1983
|
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Set the realtime service for publishing data change events.
|
|
1986
|
+
* Should be called after kernel resolves the realtime service.
|
|
1987
|
+
*
|
|
1988
|
+
* @param service - An IRealtimeService instance for event publishing
|
|
1989
|
+
*/
|
|
1990
|
+
setRealtimeService(service) {
|
|
1991
|
+
this.realtimeService = service;
|
|
1992
|
+
this.logger.info("RealtimeService configured for data events");
|
|
1993
|
+
}
|
|
1910
1994
|
/**
|
|
1911
1995
|
* Helper to get object definition
|
|
1912
1996
|
*/
|
|
@@ -1932,27 +2016,99 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1932
2016
|
}
|
|
1933
2017
|
/**
|
|
1934
2018
|
* Helper to get the target driver
|
|
2019
|
+
*
|
|
2020
|
+
* Resolution priority (first match wins):
|
|
2021
|
+
* 1. Object's explicit `datasource` field (if not 'default')
|
|
2022
|
+
* 2. DatasourceMapping rules (namespace/package/pattern matching)
|
|
2023
|
+
* 3. Package's `defaultDatasource` from manifest
|
|
2024
|
+
* 4. Global default driver
|
|
1935
2025
|
*/
|
|
1936
2026
|
getDriver(objectName) {
|
|
1937
2027
|
const object = SchemaRegistry.getObject(objectName);
|
|
1938
|
-
if (object) {
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
2028
|
+
if (object?.datasource && object.datasource !== "default") {
|
|
2029
|
+
if (this.drivers.has(object.datasource)) {
|
|
2030
|
+
return this.drivers.get(object.datasource);
|
|
2031
|
+
}
|
|
2032
|
+
throw new Error(`[ObjectQL] Datasource '${object.datasource}' configured for object '${objectName}' is not registered.`);
|
|
2033
|
+
}
|
|
2034
|
+
const mappedDatasource = this.resolveDatasourceFromMapping(objectName, object);
|
|
2035
|
+
if (mappedDatasource && this.drivers.has(mappedDatasource)) {
|
|
2036
|
+
this.logger.debug("Resolved datasource from mapping", {
|
|
2037
|
+
object: objectName,
|
|
2038
|
+
datasource: mappedDatasource
|
|
2039
|
+
});
|
|
2040
|
+
return this.drivers.get(mappedDatasource);
|
|
2041
|
+
}
|
|
2042
|
+
const fqn = object?.name || objectName;
|
|
2043
|
+
const owner = SchemaRegistry.getObjectOwner(fqn);
|
|
2044
|
+
if (owner?.packageId) {
|
|
2045
|
+
const manifest = this.manifests.get(owner.packageId);
|
|
2046
|
+
if (manifest?.defaultDatasource && manifest.defaultDatasource !== "default") {
|
|
2047
|
+
if (this.drivers.has(manifest.defaultDatasource)) {
|
|
2048
|
+
this.logger.debug("Resolved datasource from package manifest", {
|
|
2049
|
+
object: objectName,
|
|
2050
|
+
package: owner.packageId,
|
|
2051
|
+
datasource: manifest.defaultDatasource
|
|
2052
|
+
});
|
|
2053
|
+
return this.drivers.get(manifest.defaultDatasource);
|
|
1947
2054
|
}
|
|
1948
|
-
throw new Error(`[ObjectQL] Datasource '${datasourceName}' configured for object '${objectName}' is not registered.`);
|
|
1949
2055
|
}
|
|
1950
2056
|
}
|
|
1951
|
-
if (this.defaultDriver) {
|
|
2057
|
+
if (this.defaultDriver && this.drivers.has(this.defaultDriver)) {
|
|
1952
2058
|
return this.drivers.get(this.defaultDriver);
|
|
1953
2059
|
}
|
|
1954
2060
|
throw new Error(`[ObjectQL] No driver available for object '${objectName}'`);
|
|
1955
2061
|
}
|
|
2062
|
+
/**
|
|
2063
|
+
* Resolve datasource from mapping rules
|
|
2064
|
+
*
|
|
2065
|
+
* Rules are evaluated in order (or by priority if specified).
|
|
2066
|
+
* First matching rule wins.
|
|
2067
|
+
*/
|
|
2068
|
+
resolveDatasourceFromMapping(objectName, object) {
|
|
2069
|
+
if (!this.datasourceMapping || this.datasourceMapping.length === 0) {
|
|
2070
|
+
return null;
|
|
2071
|
+
}
|
|
2072
|
+
const sortedRules = [...this.datasourceMapping].sort((a, b) => {
|
|
2073
|
+
const aPriority = a.priority ?? 1e3;
|
|
2074
|
+
const bPriority = b.priority ?? 1e3;
|
|
2075
|
+
return aPriority - bPriority;
|
|
2076
|
+
});
|
|
2077
|
+
for (const rule of sortedRules) {
|
|
2078
|
+
if (rule.namespace && object?.namespace === rule.namespace) {
|
|
2079
|
+
return rule.datasource;
|
|
2080
|
+
}
|
|
2081
|
+
if (rule.package && object?.packageId === rule.package) {
|
|
2082
|
+
return rule.datasource;
|
|
2083
|
+
}
|
|
2084
|
+
if (rule.objectPattern && this.matchPattern(objectName, rule.objectPattern)) {
|
|
2085
|
+
return rule.datasource;
|
|
2086
|
+
}
|
|
2087
|
+
if (rule.default) {
|
|
2088
|
+
return rule.datasource;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
return null;
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* Simple glob pattern matching
|
|
2095
|
+
* Supports * (any chars) and ? (single char)
|
|
2096
|
+
*/
|
|
2097
|
+
matchPattern(objectName, pattern) {
|
|
2098
|
+
const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
2099
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
2100
|
+
return regex.test(objectName);
|
|
2101
|
+
}
|
|
2102
|
+
/**
|
|
2103
|
+
* Set datasource mapping rules
|
|
2104
|
+
* Called by ObjectQLPlugin during bootstrap
|
|
2105
|
+
*/
|
|
2106
|
+
setDatasourceMapping(rules) {
|
|
2107
|
+
this.datasourceMapping = rules;
|
|
2108
|
+
this.logger.info("Datasource mapping rules configured", {
|
|
2109
|
+
ruleCount: rules.length
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
1956
2112
|
/**
|
|
1957
2113
|
* Initialize the engine and all registered drivers
|
|
1958
2114
|
*/
|
|
@@ -1961,14 +2117,22 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1961
2117
|
driverCount: this.drivers.size,
|
|
1962
2118
|
drivers: Array.from(this.drivers.keys())
|
|
1963
2119
|
});
|
|
2120
|
+
const failedDrivers = [];
|
|
1964
2121
|
for (const [name, driver] of this.drivers) {
|
|
1965
2122
|
try {
|
|
1966
2123
|
await driver.connect();
|
|
1967
2124
|
this.logger.info("Driver connected successfully", { driverName: name });
|
|
1968
2125
|
} catch (e) {
|
|
2126
|
+
failedDrivers.push(name);
|
|
1969
2127
|
this.logger.error("Failed to connect driver", e, { driverName: name });
|
|
1970
2128
|
}
|
|
1971
2129
|
}
|
|
2130
|
+
if (failedDrivers.length > 0) {
|
|
2131
|
+
this.logger.warn(
|
|
2132
|
+
`${failedDrivers.length} of ${this.drivers.size} driver(s) failed initial connect. Operations may recover via lazy reconnection or fail at query time.`,
|
|
2133
|
+
{ failedDrivers }
|
|
2134
|
+
);
|
|
2135
|
+
}
|
|
1972
2136
|
this.logger.info("ObjectQL engine initialization complete");
|
|
1973
2137
|
}
|
|
1974
2138
|
async destroy() {
|
|
@@ -2170,6 +2334,39 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2170
2334
|
hookContext.event = "afterInsert";
|
|
2171
2335
|
hookContext.result = result;
|
|
2172
2336
|
await this.triggerHooks("afterInsert", hookContext);
|
|
2337
|
+
if (this.realtimeService) {
|
|
2338
|
+
try {
|
|
2339
|
+
if (Array.isArray(result)) {
|
|
2340
|
+
for (const record of result) {
|
|
2341
|
+
const event = {
|
|
2342
|
+
type: "data.record.created",
|
|
2343
|
+
object,
|
|
2344
|
+
payload: {
|
|
2345
|
+
recordId: record.id,
|
|
2346
|
+
after: record
|
|
2347
|
+
},
|
|
2348
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2349
|
+
};
|
|
2350
|
+
await this.realtimeService.publish(event);
|
|
2351
|
+
}
|
|
2352
|
+
this.logger.debug(`Published ${result.length} data.record.created events`, { object });
|
|
2353
|
+
} else {
|
|
2354
|
+
const event = {
|
|
2355
|
+
type: "data.record.created",
|
|
2356
|
+
object,
|
|
2357
|
+
payload: {
|
|
2358
|
+
recordId: result.id,
|
|
2359
|
+
after: result
|
|
2360
|
+
},
|
|
2361
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2362
|
+
};
|
|
2363
|
+
await this.realtimeService.publish(event);
|
|
2364
|
+
this.logger.debug("Published data.record.created event", { object, recordId: result.id });
|
|
2365
|
+
}
|
|
2366
|
+
} catch (error) {
|
|
2367
|
+
this.logger.warn("Failed to publish data event", { object, error });
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2173
2370
|
return hookContext.result;
|
|
2174
2371
|
} catch (e) {
|
|
2175
2372
|
this.logger.error("Insert operation failed", e, { object });
|
|
@@ -2216,6 +2413,26 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2216
2413
|
hookContext.event = "afterUpdate";
|
|
2217
2414
|
hookContext.result = result;
|
|
2218
2415
|
await this.triggerHooks("afterUpdate", hookContext);
|
|
2416
|
+
if (this.realtimeService) {
|
|
2417
|
+
try {
|
|
2418
|
+
const resultId = typeof result === "object" && result && "id" in result ? result.id : void 0;
|
|
2419
|
+
const recordId = String(hookContext.input.id || resultId || "");
|
|
2420
|
+
const event = {
|
|
2421
|
+
type: "data.record.updated",
|
|
2422
|
+
object,
|
|
2423
|
+
payload: {
|
|
2424
|
+
recordId,
|
|
2425
|
+
changes: hookContext.input.data,
|
|
2426
|
+
after: result
|
|
2427
|
+
},
|
|
2428
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2429
|
+
};
|
|
2430
|
+
await this.realtimeService.publish(event);
|
|
2431
|
+
this.logger.debug("Published data.record.updated event", { object, recordId });
|
|
2432
|
+
} catch (error) {
|
|
2433
|
+
this.logger.warn("Failed to publish data event", { object, error });
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2219
2436
|
return hookContext.result;
|
|
2220
2437
|
} catch (e) {
|
|
2221
2438
|
this.logger.error("Update operation failed", e, { object });
|
|
@@ -2261,6 +2478,24 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2261
2478
|
hookContext.event = "afterDelete";
|
|
2262
2479
|
hookContext.result = result;
|
|
2263
2480
|
await this.triggerHooks("afterDelete", hookContext);
|
|
2481
|
+
if (this.realtimeService) {
|
|
2482
|
+
try {
|
|
2483
|
+
const resultId = typeof result === "object" && result && "id" in result ? result.id : void 0;
|
|
2484
|
+
const recordId = String(hookContext.input.id || resultId || "");
|
|
2485
|
+
const event = {
|
|
2486
|
+
type: "data.record.deleted",
|
|
2487
|
+
object,
|
|
2488
|
+
payload: {
|
|
2489
|
+
recordId
|
|
2490
|
+
},
|
|
2491
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2492
|
+
};
|
|
2493
|
+
await this.realtimeService.publish(event);
|
|
2494
|
+
this.logger.debug("Published data.record.deleted event", { object, recordId });
|
|
2495
|
+
} catch (error) {
|
|
2496
|
+
this.logger.warn("Failed to publish data event", { object, error });
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2264
2499
|
return hookContext.result;
|
|
2265
2500
|
} catch (e) {
|
|
2266
2501
|
this.logger.error("Delete operation failed", e, { object });
|
|
@@ -2718,6 +2953,9 @@ var MetadataFacade = class {
|
|
|
2718
2953
|
};
|
|
2719
2954
|
|
|
2720
2955
|
// src/plugin.ts
|
|
2956
|
+
function hasLoadMetaFromDb(service) {
|
|
2957
|
+
return typeof service === "object" && service !== null && typeof service["loadMetaFromDb"] === "function";
|
|
2958
|
+
}
|
|
2721
2959
|
var ObjectQLPlugin = class {
|
|
2722
2960
|
constructor(ql, hostContext) {
|
|
2723
2961
|
this.name = "com.objectstack.engine.objectql";
|
|
@@ -2774,9 +3012,22 @@ var ObjectQLPlugin = class {
|
|
|
2774
3012
|
ctx.logger.debug("Discovered and registered app service (legacy)", { serviceName: name });
|
|
2775
3013
|
}
|
|
2776
3014
|
}
|
|
3015
|
+
try {
|
|
3016
|
+
const realtimeService = ctx.getService("realtime");
|
|
3017
|
+
if (realtimeService && typeof realtimeService === "object" && "publish" in realtimeService) {
|
|
3018
|
+
ctx.logger.info("[ObjectQLPlugin] Bridging realtime service to ObjectQL for event publishing");
|
|
3019
|
+
this.ql.setRealtimeService(realtimeService);
|
|
3020
|
+
}
|
|
3021
|
+
} catch (e) {
|
|
3022
|
+
ctx.logger.debug("[ObjectQLPlugin] No realtime service found \u2014 data events will not be published", {
|
|
3023
|
+
error: e.message
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
2777
3026
|
}
|
|
2778
3027
|
await this.ql?.init();
|
|
3028
|
+
await this.restoreMetadataFromDb(ctx);
|
|
2779
3029
|
await this.syncRegisteredSchemas(ctx);
|
|
3030
|
+
await this.bridgeObjectsToMetadataService(ctx);
|
|
2780
3031
|
this.registerAuditHooks(ctx);
|
|
2781
3032
|
this.registerTenantMiddleware(ctx);
|
|
2782
3033
|
ctx.logger.info("ObjectQL engine started", {
|
|
@@ -2969,6 +3220,97 @@ var ObjectQLPlugin = class {
|
|
|
2969
3220
|
ctx.logger.info("Schema sync complete", { synced, skipped, total: allObjects.length });
|
|
2970
3221
|
}
|
|
2971
3222
|
}
|
|
3223
|
+
/**
|
|
3224
|
+
* Restore persisted metadata from the database (sys_metadata) on startup.
|
|
3225
|
+
*
|
|
3226
|
+
* Calls `protocol.loadMetaFromDb()` to bulk-load all active metadata
|
|
3227
|
+
* records (objects, views, apps, etc.) into the in-memory SchemaRegistry.
|
|
3228
|
+
* This closes the persistence loop so that user-created schemas survive
|
|
3229
|
+
* kernel cold starts and redeployments.
|
|
3230
|
+
*
|
|
3231
|
+
* Gracefully degrades when:
|
|
3232
|
+
* - The protocol service is unavailable (e.g., in-memory-only mode).
|
|
3233
|
+
* - `loadMetaFromDb` is not implemented by the protocol shim.
|
|
3234
|
+
* - The underlying driver/table does not exist yet (first-run scenario).
|
|
3235
|
+
*/
|
|
3236
|
+
async restoreMetadataFromDb(ctx) {
|
|
3237
|
+
let protocol;
|
|
3238
|
+
try {
|
|
3239
|
+
const service = ctx.getService("protocol");
|
|
3240
|
+
if (!service || !hasLoadMetaFromDb(service)) {
|
|
3241
|
+
ctx.logger.debug("Protocol service does not support loadMetaFromDb, skipping DB restore");
|
|
3242
|
+
return;
|
|
3243
|
+
}
|
|
3244
|
+
protocol = service;
|
|
3245
|
+
} catch (e) {
|
|
3246
|
+
ctx.logger.debug("Protocol service unavailable, skipping DB restore", {
|
|
3247
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3248
|
+
});
|
|
3249
|
+
return;
|
|
3250
|
+
}
|
|
3251
|
+
try {
|
|
3252
|
+
const { loaded, errors } = await protocol.loadMetaFromDb();
|
|
3253
|
+
if (loaded > 0 || errors > 0) {
|
|
3254
|
+
ctx.logger.info("Metadata restored from database to SchemaRegistry", { loaded, errors });
|
|
3255
|
+
} else {
|
|
3256
|
+
ctx.logger.debug("No persisted metadata found in database");
|
|
3257
|
+
}
|
|
3258
|
+
} catch (e) {
|
|
3259
|
+
ctx.logger.debug("DB metadata restore failed (non-fatal)", {
|
|
3260
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3261
|
+
});
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
/**
|
|
3265
|
+
* Bridge all SchemaRegistry objects to the metadata service.
|
|
3266
|
+
*
|
|
3267
|
+
* This ensures objects registered by plugins and loaded from sys_metadata
|
|
3268
|
+
* are visible to AI tools and other consumers that query IMetadataService.
|
|
3269
|
+
*
|
|
3270
|
+
* Runs after both restoreMetadataFromDb() and syncRegisteredSchemas() to
|
|
3271
|
+
* catch all objects in the SchemaRegistry regardless of their source.
|
|
3272
|
+
*/
|
|
3273
|
+
async bridgeObjectsToMetadataService(ctx) {
|
|
3274
|
+
try {
|
|
3275
|
+
const metadataService = ctx.getService("metadata");
|
|
3276
|
+
if (!metadataService || typeof metadataService.register !== "function") {
|
|
3277
|
+
ctx.logger.debug("Metadata service unavailable for bridging, skipping");
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
if (!this.ql?.registry) {
|
|
3281
|
+
ctx.logger.debug("SchemaRegistry unavailable for bridging, skipping");
|
|
3282
|
+
return;
|
|
3283
|
+
}
|
|
3284
|
+
const objects = this.ql.registry.getAllObjects();
|
|
3285
|
+
let bridged = 0;
|
|
3286
|
+
for (const obj of objects) {
|
|
3287
|
+
try {
|
|
3288
|
+
const existing = await metadataService.getObject(obj.name);
|
|
3289
|
+
if (!existing) {
|
|
3290
|
+
await metadataService.register("object", obj.name, obj);
|
|
3291
|
+
bridged++;
|
|
3292
|
+
}
|
|
3293
|
+
} catch (e) {
|
|
3294
|
+
ctx.logger.debug("Failed to bridge object to metadata service", {
|
|
3295
|
+
object: obj.name,
|
|
3296
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
if (bridged > 0) {
|
|
3301
|
+
ctx.logger.info("Bridged objects from SchemaRegistry to metadata service", {
|
|
3302
|
+
count: bridged,
|
|
3303
|
+
total: objects.length
|
|
3304
|
+
});
|
|
3305
|
+
} else {
|
|
3306
|
+
ctx.logger.debug("No objects needed bridging (all already in metadata service)");
|
|
3307
|
+
}
|
|
3308
|
+
} catch (e) {
|
|
3309
|
+
ctx.logger.debug("Failed to bridge objects to metadata service", {
|
|
3310
|
+
error: e instanceof Error ? e.message : String(e)
|
|
3311
|
+
});
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
2972
3314
|
/**
|
|
2973
3315
|
* Load metadata from external metadata service into ObjectQL registry
|
|
2974
3316
|
* This enables ObjectQL to use file-based or remote metadata
|