Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 64 additions & 3 deletions packages/objectql/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,16 @@ export class ObjectQLPlugin implements Plugin {
// object registered by plugins (e.g., sys_user from plugin-auth).
await this.syncRegisteredSchemas(ctx);

// Bridge all SchemaRegistry objects to metadata service
// This ensures AI tools and other IMetadataService consumers can see all objects
await this.bridgeObjectsToMetadataService(ctx);

Comment on lines +132 to +135
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New behavior (bridging SchemaRegistry objects into IMetadataService on startup) isn’t covered by tests. Since packages/objectql has integration tests for plugin startup ordering and metadata restoration, please add coverage asserting that: (1) plugin-registered and DB-restored objects become visible via metadataService.listObjects(), and (2) bridging is a no-op when metadata service is unavailable.

Copilot uses AI. Check for mistakes.
// Register built-in audit hooks
this.registerAuditHooks(ctx);

// Register tenant isolation middleware
this.registerTenantMiddleware(ctx);

ctx.logger.info('ObjectQL engine started', {
driversRegistered: this.ql?.['drivers']?.size || 0,
objectsRegistered: this.ql?.registry?.getAllObjects?.()?.length || 0
Expand Down Expand Up @@ -387,12 +391,12 @@ export class ObjectQLPlugin implements Plugin {
return;
}

// Phase 2: DB hydration
// Phase 2: DB hydration (loads into SchemaRegistry)
try {
const { loaded, errors } = await protocol.loadMetaFromDb();

if (loaded > 0 || errors > 0) {
ctx.logger.info('Metadata restored from database', { loaded, errors });
ctx.logger.info('Metadata restored from database to SchemaRegistry', { loaded, errors });
} else {
ctx.logger.debug('No persisted metadata found in database');
}
Expand All @@ -404,6 +408,63 @@ export class ObjectQLPlugin implements Plugin {
}
}

/**
* Bridge all SchemaRegistry objects to the metadata service.
*
* This ensures objects registered by plugins and loaded from sys_metadata
* are visible to AI tools and other consumers that query IMetadataService.
*
* Runs after both restoreMetadataFromDb() and syncRegisteredSchemas() to
* catch all objects in the SchemaRegistry regardless of their source.
*/
private async bridgeObjectsToMetadataService(ctx: PluginContext): Promise<void> {
try {
const metadataService = ctx.getService<any>('metadata');
if (!metadataService || typeof metadataService.register !== 'function') {
ctx.logger.debug('Metadata service unavailable for bridging, skipping');
return;
}
Comment on lines +421 to +426
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type safety is lost by using ctx.getService('metadata') and then duck-typing method presence. Since IMetadataService is already defined in @objectstack/spec/contracts (and used elsewhere via ctx.getService('metadata')), consider typing this as IMetadataService and validating both getObject and register up front to avoid per-object failures/log spam when a partial implementation is provided.

Copilot uses AI. Check for mistakes.

if (!this.ql?.registry) {
ctx.logger.debug('SchemaRegistry unavailable for bridging, skipping');
return;
}

const objects = this.ql.registry.getAllObjects();
let bridged = 0;

for (const obj of objects) {
try {
// Check if object is already in metadata service to avoid duplicates
const existing = await metadataService.getObject(obj.name);
if (!existing) {
// Register object that exists in SchemaRegistry but not in metadata service
await metadataService.register('object', obj.name, obj);
bridged++;
Comment on lines +439 to +443
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bridgeObjectsToMetadataService() uses metadataService.register('object', ...) which (per MetadataManager.register) persists to any write-capable 'datasource:' loader (DatabaseLoader/sys_metadata). This means bridging can inadvertently persist plugin/system objects into sys_metadata, potentially freezing their schemas and causing upgrade drift when the plugin later changes the object definition. Consider adding/using a non-persisting registration path (e.g., a registerRuntime/registerInMemory API or a persist:false option) so the bridge only affects runtime visibility in IMetadataService without writing to database loaders.

Copilot uses AI. Check for mistakes.
}
} catch (e: unknown) {
ctx.logger.debug('Failed to bridge object to metadata service', {
object: obj.name,
error: e instanceof Error ? e.message : String(e),
});
}
}

Comment on lines +433 to +452
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bridge currently does an N+1 sequence of async calls (await getObject for every SchemaRegistry object, then possibly await register). On instances with many objects (and especially if register writes to a DB loader), this can significantly slow startup. Prefer a bulk prefetch (e.g., listNames('object')/listObjects()) to build a Set of existing names, then only register the missing ones; also consider bounded concurrency for registrations.

Copilot uses AI. Check for mistakes.
if (bridged > 0) {
ctx.logger.info('Bridged objects from SchemaRegistry to metadata service', {
count: bridged,
total: objects.length
});
} else {
ctx.logger.debug('No objects needed bridging (all already in metadata service)');
}
} catch (e: unknown) {
ctx.logger.debug('Failed to bridge objects to metadata service', {
error: e instanceof Error ? e.message : String(e),
});
}
}

/**
* Load metadata from external metadata service into ObjectQL registry
* This enables ObjectQL to use file-based or remote metadata
Expand Down
Loading