Skip to content

Commit a3caf55

Browse files
committed
Support loading plugins by package name string
Enhanced ObjectQL to allow plugins to be specified as package name strings in the config, in addition to direct plugin instances. Updated types and example app to demonstrate plugin loading by package name. Refactored plugin-audit exports and moved its test logic to a separate main.ts.
1 parent a81f591 commit a3caf55

7 files changed

Lines changed: 90 additions & 23 deletions

File tree

examples/app-using-preset/objectql.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import * as path from 'path';
44
const db = new ObjectQL({
55
connection: `sqlite://${path.join(__dirname, 'preset.sqlite3')}`,
66
// Load the project-management capabilities as a preset
7-
presets: ['@example/project-management']
7+
presets: ['@example/project-management'],
8+
plugins: ['@example/plugin-audit']
89
});
910

1011
export default db;

examples/app-using-preset/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"repl": "objectql repl"
99
},
1010
"dependencies": {
11-
"@example/project-management": "workspace:*"
11+
"@example/project-management": "workspace:*",
12+
"@example/plugin-audit": "workspace:*"
1213
},
1314
"devDependencies": {
1415
"@objectql/core": "workspace:*",

examples/plugin-audit/src/index.ts

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,6 @@
1-
import db from '../objectql.config';
1+
// Export the plugin for external usage
2+
export * from './audit.plugin';
23

3-
async function main() {
4-
await db.init();
5-
6-
const noteRepo = db.createContext({ userId: 'u001' }).object('note');
7-
8-
console.log('--- Creating Note ---');
9-
// Should trigger [Audit] log
10-
const note = await noteRepo.create({
11-
content: 'Hello Project Plugin!'
12-
});
13-
14-
console.log('--- Deleting Note ---');
15-
// Should trigger [Audit] log
16-
await noteRepo.delete(note.id);
17-
}
18-
19-
main().catch(console.error);
4+
// Make it the default export as well for easier consumption
5+
import { AuditLogPlugin } from './audit.plugin';
6+
export default AuditLogPlugin;

examples/plugin-audit/src/main.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import db from '../objectql.config';
2+
3+
async function main() {
4+
await db.init();
5+
6+
const noteRepo = db.createContext({ userId: 'u001' }).object('note');
7+
8+
console.log('--- Creating Note ---');
9+
// Should trigger [Audit] log
10+
const note = await noteRepo.create({
11+
content: 'Hello Project Plugin!'
12+
});
13+
14+
console.log('--- Deleting Note ---');
15+
// Should trigger [Audit] log
16+
await noteRepo.delete(note.id);
17+
}
18+
19+
main().catch(console.error);

packages/core/src/index.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ export class ObjectQL implements IObjectQL {
4848

4949
if (config.plugins) {
5050
for (const plugin of config.plugins) {
51-
this.use(plugin);
51+
if (typeof plugin === 'string') {
52+
this.loadPluginFromPackage(plugin);
53+
} else {
54+
this.use(plugin);
55+
}
5256
}
5357
}
5458

@@ -68,6 +72,57 @@ export class ObjectQL implements IObjectQL {
6872
}
6973
}
7074

75+
private loadPluginFromPackage(packageName: string) {
76+
let mod: any;
77+
try {
78+
const modulePath = require.resolve(packageName, { paths: [process.cwd()] });
79+
mod = require(modulePath);
80+
} catch (e) {
81+
throw new Error(`Failed to resolve plugin '${packageName}': ${e}`);
82+
}
83+
84+
// Helper to find plugin instance
85+
const findPlugin = (candidate: any): ObjectQLPlugin | undefined => {
86+
if (!candidate) return undefined;
87+
88+
// 1. Try treating as Class
89+
if (typeof candidate === 'function') {
90+
try {
91+
const inst = new candidate();
92+
if (inst && typeof inst.setup === 'function') {
93+
return inst; // Found it!
94+
}
95+
} catch (e) {
96+
// Not a constructor or instantiation failed
97+
}
98+
}
99+
100+
// 2. Try treating as Instance
101+
if (candidate && typeof candidate.setup === 'function') {
102+
if (candidate.name) return candidate;
103+
}
104+
return undefined;
105+
};
106+
107+
// Search in default, module root, and all named exports
108+
let instance = findPlugin(mod.default) || findPlugin(mod);
109+
110+
if (!instance && mod && typeof mod === 'object') {
111+
for (const key of Object.keys(mod)) {
112+
if (key === 'default') continue;
113+
instance = findPlugin(mod[key]);
114+
if (instance) break;
115+
}
116+
}
117+
118+
if (instance) {
119+
this.use(instance);
120+
} else {
121+
console.error(`[PluginLoader] Failed to find ObjectQLPlugin in '${packageName}'. Exports:`, Object.keys(mod));
122+
throw new Error(`Plugin '${packageName}' must export a class or object implementing ObjectQLPlugin.`);
123+
}
124+
}
125+
71126
addPackage(name: string) {
72127
this.loader.loadPackage(name);
73128
}

packages/types/src/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ export interface ObjectQLConfig {
5353
*/
5454
presets?: string[];
5555
/**
56-
* List of plugins to load.
56+
* List of plugins to load.
57+
* Can be an instance of ObjectQLPlugin or a package name string.
5758
*/
58-
plugins?: ObjectQLPlugin[];
59+
plugins?: (ObjectQLPlugin | string)[];
5960
}
6061

6162
export interface IObjectQL {

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)