Skip to content

Commit edc8ecc

Browse files
Copilothuangyiirene
andcommitted
Add support for both legacy and @objectstack/spec plugin types
Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
1 parent 3889259 commit edc8ecc

3 files changed

Lines changed: 138 additions & 18 deletions

File tree

packages/foundation/core/src/app.ts

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
IObjectQL,
1616
ObjectQLConfig,
1717
ObjectQLPlugin,
18+
PluginDefinition,
1819
HookName,
1920
HookHandler,
2021
HookContext,
@@ -37,7 +38,7 @@ export class ObjectQL implements IObjectQL {
3738
private remotes: string[] = [];
3839
private hooks: Record<string, HookEntry[]> = {};
3940
private actions: Record<string, ActionEntry> = {};
40-
private pluginsList: ObjectQLPlugin[] = [];
41+
private pluginsList: Array<ObjectQLPlugin | PluginDefinition> = [];
4142

4243
// Store config for lazy loading in init()
4344
private config: ObjectQLConfig;
@@ -63,7 +64,7 @@ export class ObjectQL implements IObjectQL {
6364
}
6465
}
6566
}
66-
use(plugin: ObjectQLPlugin) {
67+
use(plugin: ObjectQLPlugin | PluginDefinition) {
6768
this.pluginsList.push(plugin);
6869
}
6970

@@ -214,10 +215,87 @@ export class ObjectQL implements IObjectQL {
214215
}
215216
}
216217

218+
/**
219+
* Create a PluginContext from the current IObjectQL instance.
220+
* This adapts the IObjectQL interface to the PluginContext expected by @objectstack/spec plugins.
221+
*
222+
* @private
223+
*/
224+
private createPluginContext(app: IObjectQL): any {
225+
// TODO: Implement full PluginContext conversion
226+
// For now, provide a minimal adapter that maps IObjectQL to PluginContext
227+
return {
228+
ql: {
229+
object: (name: string) => {
230+
// Return a repository-like interface
231+
return app.createContext().object(name);
232+
},
233+
query: async (soql: string) => {
234+
// TODO: Implement SOQL query execution
235+
throw new Error('SOQL queries not yet implemented in adapter');
236+
}
237+
},
238+
os: {
239+
getCurrentUser: async () => {
240+
// TODO: Get from context
241+
return null;
242+
},
243+
getConfig: async (key: string) => {
244+
// TODO: Implement config access
245+
return null;
246+
}
247+
},
248+
logger: {
249+
debug: (...args: any[]) => console.debug('[Plugin]', ...args),
250+
info: (...args: any[]) => console.info('[Plugin]', ...args),
251+
warn: (...args: any[]) => console.warn('[Plugin]', ...args),
252+
error: (...args: any[]) => console.error('[Plugin]', ...args),
253+
},
254+
storage: {
255+
get: async (key: string) => {
256+
// TODO: Implement plugin storage
257+
return null;
258+
},
259+
set: async (key: string, value: any) => {
260+
// TODO: Implement plugin storage
261+
},
262+
delete: async (key: string) => {
263+
// TODO: Implement plugin storage
264+
}
265+
},
266+
i18n: {
267+
t: (key: string, params?: any) => key, // Fallback: return key
268+
getLocale: () => 'en'
269+
},
270+
metadata: app.metadata,
271+
events: {
272+
// TODO: Implement event bus
273+
},
274+
app: {
275+
router: {
276+
get: (path: string, handler: Function) => {
277+
// TODO: Implement router registration
278+
},
279+
post: (path: string, handler: Function) => {
280+
// TODO: Implement router registration
281+
},
282+
use: (pathOrHandler: string | Function, handler?: Function) => {
283+
// TODO: Implement middleware registration
284+
}
285+
},
286+
scheduler: undefined // Optional in spec
287+
}
288+
};
289+
}
290+
217291
async init() {
218292
// 0. Init Plugins (This allows plugins to register custom loaders)
219293
for (const plugin of this.pluginsList) {
220-
console.log(`Initializing plugin '${plugin.name}'...`);
294+
// Type guard: check if it's a legacy plugin or new PluginDefinition
295+
const isLegacyPlugin = 'setup' in plugin && typeof plugin.setup === 'function';
296+
const pluginId = isLegacyPlugin ? (plugin as ObjectQLPlugin).name : (plugin as PluginDefinition).id || 'unknown';
297+
298+
console.log(`Initializing plugin '${pluginId}'...`);
221299

222300
let app: IObjectQL = this;
223301
const pkgName = (plugin as any)._packageName;
@@ -239,7 +317,21 @@ export class ObjectQL implements IObjectQL {
239317
});
240318
}
241319

242-
await plugin.setup(app);
320+
if (isLegacyPlugin) {
321+
// Legacy plugin with setup() method
322+
await (plugin as ObjectQLPlugin).setup(app);
323+
} else {
324+
// New plugin with lifecycle hooks
325+
const newPlugin = plugin as PluginDefinition;
326+
327+
// Call onEnable hook if it exists
328+
if (newPlugin.onEnable) {
329+
// TODO: Build proper PluginContext from IObjectQL
330+
// For now, we'll create a minimal context adapter
331+
const context = this.createPluginContext(app);
332+
await newPlugin.onEnable(context);
333+
}
334+
}
243335
}
244336

245337
// Packages, Presets, Source, Objects loading logic removed from Core.

packages/foundation/platform-node/src/plugin.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
* LICENSE file in the root directory of this source tree.
77
*/
88

9-
import { ObjectQLPlugin } from '@objectql/types';
9+
import { ObjectQLPlugin, PluginDefinition } from '@objectql/types';
1010

11-
export function loadPlugin(packageName: string): ObjectQLPlugin {
11+
export function loadPlugin(packageName: string): ObjectQLPlugin | PluginDefinition {
1212
let mod: any;
1313
try {
1414
const modulePath = require.resolve(packageName, { paths: [process.cwd()] });
@@ -17,26 +17,51 @@ export function loadPlugin(packageName: string): ObjectQLPlugin {
1717
throw new Error(`Failed to resolve plugin '${packageName}': ${e}`);
1818
}
1919

20+
// Helper to check if candidate is a legacy ObjectQLPlugin
21+
const isLegacyPlugin = (candidate: any): candidate is ObjectQLPlugin => {
22+
return candidate && typeof candidate.setup === 'function' && candidate.name;
23+
};
24+
25+
// Helper to check if candidate is a new PluginDefinition
26+
const isNewPlugin = (candidate: any): candidate is PluginDefinition => {
27+
return candidate && (
28+
typeof candidate.onEnable === 'function' ||
29+
typeof candidate.onDisable === 'function' ||
30+
typeof candidate.onInstall === 'function' ||
31+
typeof candidate.onUninstall === 'function' ||
32+
typeof candidate.onUpgrade === 'function'
33+
);
34+
};
35+
2036
// Helper to find plugin instance
21-
const findPlugin = (candidate: any): ObjectQLPlugin | undefined => {
37+
const findPlugin = (candidate: any): ObjectQLPlugin | PluginDefinition | undefined => {
2238
if (!candidate) return undefined;
2339

24-
// 1. Try treating as Class
40+
// 1. Check if it's a new PluginDefinition
41+
if (isNewPlugin(candidate)) {
42+
return candidate;
43+
}
44+
45+
// 2. Try treating as Class (legacy)
2546
if (typeof candidate === 'function') {
2647
try {
2748
const inst = new candidate();
28-
if (inst && typeof inst.setup === 'function') {
29-
return inst; // Found it!
49+
if (isLegacyPlugin(inst)) {
50+
return inst;
51+
}
52+
if (isNewPlugin(inst)) {
53+
return inst;
3054
}
3155
} catch (e) {
3256
// Not a constructor or instantiation failed
3357
}
3458
}
3559

36-
// 2. Try treating as Instance
37-
if (candidate && typeof candidate.setup === 'function') {
38-
if (candidate.name) return candidate;
60+
// 3. Try treating as Instance (legacy)
61+
if (isLegacyPlugin(candidate)) {
62+
return candidate;
3963
}
64+
4065
return undefined;
4166
};
4267

@@ -55,7 +80,7 @@ export function loadPlugin(packageName: string): ObjectQLPlugin {
5580
(instance as any)._packageName = packageName;
5681
return instance;
5782
} else {
58-
console.error(`[PluginLoader] Failed to find ObjectQLPlugin in '${packageName}'. Exports:`, Object.keys(mod));
59-
throw new Error(`Plugin '${packageName}' must export a class or object implementing ObjectQLPlugin.`);
83+
console.error(`[PluginLoader] Failed to find plugin in '${packageName}'. Exports:`, Object.keys(mod));
84+
throw new Error(`Plugin '${packageName}' must export a PluginDefinition (with lifecycle hooks) or legacy ObjectQLPlugin (with setup method).`);
6085
}
6186
}

packages/foundation/types/src/config.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { MetadataRegistry } from "./registry";
1010
import { Driver } from "./driver";
1111
import { ObjectConfig } from "./object";
12-
import { ObjectQLPlugin } from "./plugin";
12+
import { ObjectQLPlugin, PluginDefinition } from "./plugin";
1313

1414
export interface ObjectQLConfig {
1515
registry?: MetadataRegistry;
@@ -39,9 +39,12 @@ export interface ObjectQLConfig {
3939
modules?: string[];
4040
/**
4141
* List of plugins to load.
42-
* Can be an instance of ObjectQLPlugin or a package name string.
42+
* Can be:
43+
* - An instance of ObjectQLPlugin (legacy, deprecated)
44+
* - An instance of PluginDefinition (from @objectstack/spec, recommended)
45+
* - A package name string
4346
*/
44-
plugins?: (ObjectQLPlugin | string)[];
47+
plugins?: (ObjectQLPlugin | PluginDefinition | string)[];
4548
/**
4649
* List of remote ObjectQL instances to connect to.
4750
* e.g. ["http://user-service:3000", "http://order-service:3000"]

0 commit comments

Comments
 (0)