@objectstack/objectql 4.0.2 → 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 +10 -10
- package/CHANGELOG.md +14 -0
- package/dist/index.d.mts +38 -6
- package/dist/index.d.ts +38 -6
- package/dist/index.js +292 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +292 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/engine.ts +116 -10
- package/src/plugin.integration.test.ts +170 -0
- package/src/plugin.ts +149 -1
- package/src/protocol.ts +100 -19
- package/src/registry.test.ts +11 -11
- package/src/registry.ts +4 -4
package/dist/index.mjs
CHANGED
|
@@ -301,7 +301,7 @@ var SchemaRegistry = class {
|
|
|
301
301
|
if (type === "object") {
|
|
302
302
|
return ObjectSchema.parse(item);
|
|
303
303
|
}
|
|
304
|
-
if (type === "
|
|
304
|
+
if (type === "app") {
|
|
305
305
|
return AppSchema.parse(item);
|
|
306
306
|
}
|
|
307
307
|
if (type === "package") {
|
|
@@ -451,13 +451,13 @@ var SchemaRegistry = class {
|
|
|
451
451
|
// App Helpers
|
|
452
452
|
// ==========================================
|
|
453
453
|
static registerApp(app, packageId) {
|
|
454
|
-
this.registerItem("
|
|
454
|
+
this.registerItem("app", app, "name", packageId);
|
|
455
455
|
}
|
|
456
456
|
static getApp(name) {
|
|
457
|
-
return this.getItem("
|
|
457
|
+
return this.getItem("app", name);
|
|
458
458
|
}
|
|
459
459
|
static getAllApps() {
|
|
460
|
-
return this.listItems("
|
|
460
|
+
return this.listItems("app");
|
|
461
461
|
}
|
|
462
462
|
// ==========================================
|
|
463
463
|
// Plugin Helpers
|
|
@@ -513,6 +513,7 @@ SchemaRegistry.metadata = /* @__PURE__ */ new Map();
|
|
|
513
513
|
|
|
514
514
|
// src/protocol.ts
|
|
515
515
|
import { parseFilterAST, isFilterAST } from "@objectstack/spec/data";
|
|
516
|
+
import { PLURAL_TO_SINGULAR, SINGULAR_TO_PLURAL } from "@objectstack/spec/shared";
|
|
516
517
|
function simpleHash(str) {
|
|
517
518
|
let hash = 0;
|
|
518
519
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -640,16 +641,25 @@ var ObjectStackProtocolImplementation = class {
|
|
|
640
641
|
};
|
|
641
642
|
}
|
|
642
643
|
async getMetaTypes() {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
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 };
|
|
646
656
|
}
|
|
647
657
|
async getMetaItems(request) {
|
|
648
658
|
const { packageId } = request;
|
|
649
659
|
let items = SchemaRegistry.listItems(request.type, packageId);
|
|
650
660
|
if (items.length === 0) {
|
|
651
|
-
const alt = request.type
|
|
652
|
-
items = SchemaRegistry.listItems(alt, packageId);
|
|
661
|
+
const alt = PLURAL_TO_SINGULAR[request.type] ?? SINGULAR_TO_PLURAL[request.type];
|
|
662
|
+
if (alt) items = SchemaRegistry.listItems(alt, packageId);
|
|
653
663
|
}
|
|
654
664
|
if (items.length === 0) {
|
|
655
665
|
try {
|
|
@@ -665,21 +675,47 @@ var ObjectStackProtocolImplementation = class {
|
|
|
665
675
|
return data;
|
|
666
676
|
});
|
|
667
677
|
} else {
|
|
668
|
-
const alt = request.type
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
if (altRecords && altRecords.length > 0) {
|
|
673
|
-
items = altRecords.map((record) => {
|
|
674
|
-
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
675
|
-
SchemaRegistry.registerItem(request.type, data, "name");
|
|
676
|
-
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" }
|
|
677
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
|
+
}
|
|
678
690
|
}
|
|
679
691
|
}
|
|
680
692
|
} catch {
|
|
681
693
|
}
|
|
682
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
|
+
}
|
|
683
719
|
return {
|
|
684
720
|
type: request.type,
|
|
685
721
|
items
|
|
@@ -688,8 +724,8 @@ var ObjectStackProtocolImplementation = class {
|
|
|
688
724
|
async getMetaItem(request) {
|
|
689
725
|
let item = SchemaRegistry.getItem(request.type, request.name);
|
|
690
726
|
if (item === void 0) {
|
|
691
|
-
const alt = request.type
|
|
692
|
-
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);
|
|
693
729
|
}
|
|
694
730
|
if (item === void 0) {
|
|
695
731
|
try {
|
|
@@ -700,18 +736,30 @@ var ObjectStackProtocolImplementation = class {
|
|
|
700
736
|
item = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
701
737
|
SchemaRegistry.registerItem(request.type, item, "name");
|
|
702
738
|
} else {
|
|
703
|
-
const alt = request.type
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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
|
+
}
|
|
710
748
|
}
|
|
711
749
|
}
|
|
712
750
|
} catch {
|
|
713
751
|
}
|
|
714
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
|
+
}
|
|
715
763
|
return {
|
|
716
764
|
type: request.type,
|
|
717
765
|
name: request.name,
|
|
@@ -942,7 +990,21 @@ var ObjectStackProtocolImplementation = class {
|
|
|
942
990
|
// ==========================================
|
|
943
991
|
async getMetaItemCached(request) {
|
|
944
992
|
try {
|
|
945
|
-
|
|
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
|
+
}
|
|
946
1008
|
if (!item) {
|
|
947
1009
|
throw new Error(`Metadata item ${request.type}/${request.name} not found`);
|
|
948
1010
|
}
|
|
@@ -1294,10 +1356,11 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1294
1356
|
for (const record of records) {
|
|
1295
1357
|
try {
|
|
1296
1358
|
const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
|
|
1297
|
-
|
|
1359
|
+
const normalizedType = PLURAL_TO_SINGULAR[record.type] ?? record.type;
|
|
1360
|
+
if (normalizedType === "object") {
|
|
1298
1361
|
SchemaRegistry.registerObject(data, record.packageId || "sys_metadata");
|
|
1299
1362
|
} else {
|
|
1300
|
-
SchemaRegistry.registerItem(
|
|
1363
|
+
SchemaRegistry.registerItem(normalizedType, data, "name");
|
|
1301
1364
|
}
|
|
1302
1365
|
loaded++;
|
|
1303
1366
|
} catch (e) {
|
|
@@ -1447,6 +1510,7 @@ var ObjectStackProtocolImplementation = class {
|
|
|
1447
1510
|
import { ExecutionContextSchema } from "@objectstack/spec/kernel";
|
|
1448
1511
|
import { createLogger } from "@objectstack/core";
|
|
1449
1512
|
import { CoreServiceName } from "@objectstack/spec/system";
|
|
1513
|
+
import { pluralToSingular } from "@objectstack/spec/shared";
|
|
1450
1514
|
var _ObjectQL = class _ObjectQL {
|
|
1451
1515
|
constructor(hostContext = {}) {
|
|
1452
1516
|
this.drivers = /* @__PURE__ */ new Map();
|
|
@@ -1737,7 +1801,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1737
1801
|
for (const item of items) {
|
|
1738
1802
|
const itemName = item.name || item.id;
|
|
1739
1803
|
if (itemName) {
|
|
1740
|
-
SchemaRegistry.registerItem(key, item, "name", id);
|
|
1804
|
+
SchemaRegistry.registerItem(pluralToSingular(key), item, "name", id);
|
|
1741
1805
|
}
|
|
1742
1806
|
}
|
|
1743
1807
|
}
|
|
@@ -1843,7 +1907,7 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1843
1907
|
for (const item of items) {
|
|
1844
1908
|
const itemName = item.name || item.id;
|
|
1845
1909
|
if (itemName) {
|
|
1846
|
-
SchemaRegistry.registerItem(key, item, "name", ownerId);
|
|
1910
|
+
SchemaRegistry.registerItem(pluralToSingular(key), item, "name", ownerId);
|
|
1847
1911
|
}
|
|
1848
1912
|
}
|
|
1849
1913
|
}
|
|
@@ -1867,6 +1931,16 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1867
1931
|
this.logger.info("Set default driver", { driverName: driver.name });
|
|
1868
1932
|
}
|
|
1869
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
|
+
}
|
|
1870
1944
|
/**
|
|
1871
1945
|
* Helper to get object definition
|
|
1872
1946
|
*/
|
|
@@ -1921,14 +1995,22 @@ var _ObjectQL = class _ObjectQL {
|
|
|
1921
1995
|
driverCount: this.drivers.size,
|
|
1922
1996
|
drivers: Array.from(this.drivers.keys())
|
|
1923
1997
|
});
|
|
1998
|
+
const failedDrivers = [];
|
|
1924
1999
|
for (const [name, driver] of this.drivers) {
|
|
1925
2000
|
try {
|
|
1926
2001
|
await driver.connect();
|
|
1927
2002
|
this.logger.info("Driver connected successfully", { driverName: name });
|
|
1928
2003
|
} catch (e) {
|
|
2004
|
+
failedDrivers.push(name);
|
|
1929
2005
|
this.logger.error("Failed to connect driver", e, { driverName: name });
|
|
1930
2006
|
}
|
|
1931
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
|
+
}
|
|
1932
2014
|
this.logger.info("ObjectQL engine initialization complete");
|
|
1933
2015
|
}
|
|
1934
2016
|
async destroy() {
|
|
@@ -2130,6 +2212,39 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2130
2212
|
hookContext.event = "afterInsert";
|
|
2131
2213
|
hookContext.result = result;
|
|
2132
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
|
+
}
|
|
2133
2248
|
return hookContext.result;
|
|
2134
2249
|
} catch (e) {
|
|
2135
2250
|
this.logger.error("Insert operation failed", e, { object });
|
|
@@ -2176,6 +2291,26 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2176
2291
|
hookContext.event = "afterUpdate";
|
|
2177
2292
|
hookContext.result = result;
|
|
2178
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
|
+
}
|
|
2179
2314
|
return hookContext.result;
|
|
2180
2315
|
} catch (e) {
|
|
2181
2316
|
this.logger.error("Update operation failed", e, { object });
|
|
@@ -2221,6 +2356,24 @@ var _ObjectQL = class _ObjectQL {
|
|
|
2221
2356
|
hookContext.event = "afterDelete";
|
|
2222
2357
|
hookContext.result = result;
|
|
2223
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
|
+
}
|
|
2224
2377
|
return hookContext.result;
|
|
2225
2378
|
} catch (e) {
|
|
2226
2379
|
this.logger.error("Delete operation failed", e, { object });
|
|
@@ -2678,6 +2831,9 @@ var MetadataFacade = class {
|
|
|
2678
2831
|
};
|
|
2679
2832
|
|
|
2680
2833
|
// src/plugin.ts
|
|
2834
|
+
function hasLoadMetaFromDb(service) {
|
|
2835
|
+
return typeof service === "object" && service !== null && typeof service["loadMetaFromDb"] === "function";
|
|
2836
|
+
}
|
|
2681
2837
|
var ObjectQLPlugin = class {
|
|
2682
2838
|
constructor(ql, hostContext) {
|
|
2683
2839
|
this.name = "com.objectstack.engine.objectql";
|
|
@@ -2734,9 +2890,22 @@ var ObjectQLPlugin = class {
|
|
|
2734
2890
|
ctx.logger.debug("Discovered and registered app service (legacy)", { serviceName: name });
|
|
2735
2891
|
}
|
|
2736
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);
|
|
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
|
+
});
|
|
2903
|
+
}
|
|
2737
2904
|
}
|
|
2738
2905
|
await this.ql?.init();
|
|
2906
|
+
await this.restoreMetadataFromDb(ctx);
|
|
2739
2907
|
await this.syncRegisteredSchemas(ctx);
|
|
2908
|
+
await this.bridgeObjectsToMetadataService(ctx);
|
|
2740
2909
|
this.registerAuditHooks(ctx);
|
|
2741
2910
|
this.registerTenantMiddleware(ctx);
|
|
2742
2911
|
ctx.logger.info("ObjectQL engine started", {
|
|
@@ -2929,6 +3098,97 @@ var ObjectQLPlugin = class {
|
|
|
2929
3098
|
ctx.logger.info("Schema sync complete", { synced, skipped, total: allObjects.length });
|
|
2930
3099
|
}
|
|
2931
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
|
+
}
|
|
2932
3192
|
/**
|
|
2933
3193
|
* Load metadata from external metadata service into ObjectQL registry
|
|
2934
3194
|
* This enables ObjectQL to use file-based or remote metadata
|