Skip to content

Commit 716cd96

Browse files
committed
feat(objectql): enhance plugin registration to support nested plugins and metadata
1 parent 5f09c53 commit 716cd96

2 files changed

Lines changed: 96 additions & 0 deletions

File tree

packages/cli/src/commands/serve.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,17 @@ export const serveCommand = new Command('serve')
207207
}
208208
}
209209

210+
// Wrap raw config objects (no init/start) into AppPlugin
211+
// This handles plugins defined as plain { name, objects, ... } bundles
212+
if (pluginToLoad && typeof pluginToLoad === 'object' && !pluginToLoad.init) {
213+
try {
214+
const { AppPlugin } = await import('@objectstack/runtime');
215+
pluginToLoad = new AppPlugin(pluginToLoad);
216+
} catch (e: any) {
217+
// Fall through to kernel.use which will report the error
218+
}
219+
}
220+
210221
await kernel.use(pluginToLoad);
211222
const pluginName = plugin.name || plugin.constructor?.name || 'unnamed';
212223
trackPlugin(pluginName);

packages/objectql/src/engine.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,91 @@ export class ObjectQL implements IDataEngine {
268268
this.logger.debug('Registered Kind', { kind: kind.name || kind.type, from: id });
269269
}
270270
}
271+
272+
// 7. Recursively register nested plugins
273+
if (Array.isArray(manifest.plugins) && manifest.plugins.length > 0) {
274+
console.log(`[ObjectQL] Processing ${manifest.plugins.length} nested plugins from ${id}`);
275+
this.logger.debug('Processing nested plugins', { id, count: manifest.plugins.length });
276+
for (const plugin of manifest.plugins) {
277+
if (plugin && typeof plugin === 'object') {
278+
const pluginName = plugin.name || plugin.id || 'unnamed-plugin';
279+
console.log(`[ObjectQL] Registering nested plugin: ${pluginName}, has objects: ${!!plugin.objects}, objects type: ${typeof plugin.objects}`);
280+
this.logger.debug('Registering nested plugin', { pluginName, parentId: id });
281+
this.registerPlugin(plugin, id, namespace);
282+
}
283+
}
284+
}
285+
}
286+
287+
/**
288+
* Register a nested plugin's metadata (objects, actions, views, etc.)
289+
*
290+
* Unlike registerApp(), this does NOT call SchemaRegistry.installPackage()
291+
* because plugins are not formal manifests — they are lightweight config
292+
* bundles with objects, actions, triggers, and navigation.
293+
*
294+
* @param plugin - The plugin config object
295+
* @param parentId - The parent package ID (for ownership tracking)
296+
* @param parentNamespace - The parent package's namespace (for FQN resolution)
297+
*/
298+
private registerPlugin(plugin: any, parentId: string, parentNamespace?: string) {
299+
const pluginId = plugin.id || plugin.name || parentId;
300+
const pluginNamespace = plugin.namespace || parentNamespace;
301+
302+
// Register objects (supports both Array and Map formats)
303+
if (plugin.objects) {
304+
try {
305+
if (Array.isArray(plugin.objects)) {
306+
console.log(`[ObjectQL] Registering ${plugin.objects.length} objects (Array) for plugin ${pluginId}`);
307+
for (const objDef of plugin.objects) {
308+
const fqn = SchemaRegistry.registerObject(objDef, pluginId, pluginNamespace, 'own');
309+
console.log(`[ObjectQL] Registered Object: ${fqn}`);
310+
}
311+
} else {
312+
const entries = Object.entries(plugin.objects);
313+
console.log(`[ObjectQL] Registering ${entries.length} objects (Map) for plugin ${pluginId}, namespace: ${pluginNamespace}`);
314+
for (const [name, objDef] of entries) {
315+
(objDef as any).name = name;
316+
const fqn = SchemaRegistry.registerObject(objDef as any, pluginId, pluginNamespace, 'own');
317+
console.log(`[ObjectQL] Registered Object: ${fqn}`);
318+
}
319+
}
320+
} catch (err: any) {
321+
console.error(`[ObjectQL] Failed to register plugin objects for ${pluginId}:`, err.message);
322+
}
323+
} else {
324+
console.log(`[ObjectQL] Plugin ${pluginId} has no objects`);
325+
}
326+
327+
// Register plugin as app if it has navigation (for sidebar display)
328+
if (plugin.name && plugin.navigation) {
329+
try {
330+
SchemaRegistry.registerApp(plugin, pluginId);
331+
this.logger.debug('Registered plugin-as-app', { app: plugin.name, from: pluginId });
332+
} catch (err: any) {
333+
this.logger.warn('Failed to register plugin as app', { pluginId, error: err.message });
334+
}
335+
}
336+
337+
// Register metadata arrays (actions, views, triggers, etc.)
338+
const metadataArrayKeys = [
339+
'actions', 'views', 'pages', 'dashboards', 'reports', 'themes',
340+
'flows', 'workflows', 'approvals', 'webhooks',
341+
'roles', 'permissions', 'profiles', 'sharingRules', 'policies',
342+
'agents', 'ragPipelines', 'apis',
343+
'hooks', 'mappings', 'analyticsCubes', 'connectors',
344+
];
345+
for (const key of metadataArrayKeys) {
346+
const items = (plugin as any)[key];
347+
if (Array.isArray(items) && items.length > 0) {
348+
for (const item of items) {
349+
const itemName = item.name || item.id;
350+
if (itemName) {
351+
SchemaRegistry.registerItem(key, item, 'name' as any, pluginId);
352+
}
353+
}
354+
}
355+
}
271356
}
272357

273358
/**

0 commit comments

Comments
 (0)