Skip to content
Merged
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions examples/showcase/enterprise-erp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@objectql/cli": "workspace:*",
"@objectql/driver-sql": "workspace:*",
"@objectql/platform-node": "workspace:*",
"@objectstack/spec": "^0.2.0",
"@types/jest": "^30.0.0",
"jest": "^30.2.0",
"ts-jest": "^29.4.6",
Expand Down
4 changes: 2 additions & 2 deletions examples/showcase/enterprise-erp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ObjectQL } from '@objectql/core';
import { SqlDriver } from '@objectql/driver-sql';
import { ObjectLoader } from '@objectql/platform-node';
import * as path from 'path';
import { AuditLogPlugin } from './plugins/audit/audit.plugin';
import AuditLogPlugin from './plugins/audit/audit.plugin';

async function main() {
console.log("🚀 Starting Enterprise ERP Showcase...");
Expand All @@ -26,7 +26,7 @@ async function main() {
});

// Register Plugin
app.use(new AuditLogPlugin());
app.use(AuditLogPlugin);

const loader = new ObjectLoader(app.metadata);
await loader.load(path.join(__dirname));
Expand Down
40 changes: 20 additions & 20 deletions examples/showcase/enterprise-erp/src/plugins/audit/audit.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,26 @@
* LICENSE file in the root directory of this source tree.
*/

import { ObjectQLPlugin, IObjectQL, MutationHookContext } from '@objectql/types';
import type { PluginDefinition, PluginContextData } from '@objectstack/spec';

export class AuditLogPlugin implements ObjectQLPlugin {
name = 'audit-log';
const AuditLogPlugin: PluginDefinition = {
id: 'audit-log',

onEnable: async (context: PluginContextData) => {
console.log('[AuditLogPlugin] Enabling...');

setup(app: IObjectQL) {
console.log('[AuditLogPlugin] Setting up...');

// 1. Listen to all 'afterCreate' events
app.on('afterCreate', '*', async (ctx) => {
// Narrow down context type or use assertion since 'afterCreate' is Mutation
const mutationCtx = ctx as MutationHookContext;
const userId = mutationCtx.user?.id || 'Guest';
console.log(`[Audit] Created ${mutationCtx.objectName} (ID: ${mutationCtx.id}) by User ${userId}`);
});

// 2. Listen to all 'afterDelete' events
app.on('afterDelete', '*', async (ctx) => {
const mutationCtx = ctx as MutationHookContext;
console.log(`[Audit] Deleted ${mutationCtx.objectName} (ID: ${mutationCtx.id})`);
});
// TODO: Register event handlers using the new context API
// The PluginContextData provides:
// - context.events for event handling
// - context.ql for data access
// - context.logger for logging

// For now, we'll just log that the plugin is enabled
context.logger.info('[AuditLogPlugin] Plugin enabled');

// Note: The new plugin system uses context.events instead of app.on()
// This will need to be implemented when the events API is available
}
}
};

export default AuditLogPlugin;
5 changes: 1 addition & 4 deletions examples/showcase/enterprise-erp/src/plugins/audit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,5 @@
*/

// Export the plugin for external usage
export * from './audit.plugin';

// Make it the default export as well for easier consumption
import { AuditLogPlugin } from './audit.plugin';
import AuditLogPlugin from './audit.plugin';
export default AuditLogPlugin;
2 changes: 1 addition & 1 deletion packages/foundation/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
},
"dependencies": {
"@objectql/types": "workspace:*",
"@objectstack/spec": "^0.1.2",
"@objectstack/spec": "^0.2.0",
"@objectstack/runtime": "^0.1.1",
"@objectstack/objectql": "^0.1.1",
"js-yaml": "^4.1.0",
Expand Down
127 changes: 103 additions & 24 deletions packages/foundation/core/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import {
ObjectQLContextOptions,
IObjectQL,
ObjectQLConfig,
ObjectQLPlugin,
HookName,
HookHandler,
HookContext,
ActionHandler,
ActionContext,
LoaderPlugin
} from '@objectql/types';
import type { PluginDefinition } from '@objectstack/spec';
import { ObjectRepository } from './repository';
// import { createDriverFromConnection } from './driver'; // REMOVE THIS

Expand All @@ -37,7 +37,7 @@ export class ObjectQL implements IObjectQL {
private remotes: string[] = [];
private hooks: Record<string, HookEntry[]> = {};
private actions: Record<string, ActionEntry> = {};
private pluginsList: ObjectQLPlugin[] = [];
private pluginsList: PluginDefinition[] = [];

// Store config for lazy loading in init()
private config: ObjectQLConfig;
Expand All @@ -63,7 +63,7 @@ export class ObjectQL implements IObjectQL {
}
}
}
use(plugin: ObjectQLPlugin) {
use(plugin: PluginDefinition) {
this.pluginsList.push(plugin);
}

Expand Down Expand Up @@ -214,32 +214,111 @@ export class ObjectQL implements IObjectQL {
}
}

/**
* Create a PluginContext from the current IObjectQL instance.
* This adapts the IObjectQL interface to the PluginContext expected by @objectstack/spec plugins.
*
* **Current Implementation Status:**
* - ✅ ql.object() - Fully functional, provides repository interface for data access
* - ❌ ql.query() - Not implemented, throws error with guidance
* - ❌ os.getCurrentUser() - Stub, returns null
* - ❌ os.getConfig() - Stub, returns null
* - ✅ logger - Functional, logs to console with [Plugin] prefix
* - ❌ storage - Stub, no persistence implemented
* - ✅ i18n - Basic fallback implementation
* - ✅ metadata - Direct access to MetadataRegistry
* - ❌ events - Empty object, event bus not implemented
* - ❌ app.router - Stub methods, no actual routing
* - ❌ app.scheduler - Not implemented (optional in spec)
*
* @private
* @returns Minimal PluginContext adapter for current plugin system capabilities
*/
private createPluginContext(): import('@objectstack/spec').PluginContextData {
// TODO: Implement full PluginContext conversion
// For now, provide a minimal adapter that maps IObjectQL to PluginContext
return {
ql: {
object: (name: string) => {
// Return a repository-like interface
// Cast to ObjectQL to access createContext
return (this as ObjectQL).createContext({}).object(name);
},
query: async (soql: string) => {
// TODO: Implement SOQL query execution
// This requires implementing a SOQL parser and converter
// For now, throw a descriptive error to guide users
throw new Error(
'SOQL queries are not yet supported in plugin context adapter. ' +
'Please use context.ql.object(name).find() instead for data access.'
);
}
},
os: {
getCurrentUser: async () => {
// TODO: Get from context
return null;
},
getConfig: async (key: string) => {
// TODO: Implement config access
return null;
}
},
logger: {
debug: (...args: any[]) => console.debug('[Plugin]', ...args),
info: (...args: any[]) => console.info('[Plugin]', ...args),
warn: (...args: any[]) => console.warn('[Plugin]', ...args),
error: (...args: any[]) => console.error('[Plugin]', ...args),
},
storage: {
get: async (key: string) => {
// TODO: Implement plugin storage
return null;
},
set: async (key: string, value: any) => {
// TODO: Implement plugin storage
},
delete: async (key: string) => {
// TODO: Implement plugin storage
}
},
i18n: {
t: (key: string, params?: any) => key, // Fallback: return key
getLocale: () => 'en'
},
metadata: this.metadata,
events: {
// TODO: Implement event bus
},
app: {
router: {
get: (path: string, handler: (...args: unknown[]) => unknown, ...args: unknown[]) => {
// TODO: Implement router registration
},
post: (path: string, handler: (...args: unknown[]) => unknown, ...args: unknown[]) => {
// TODO: Implement router registration
},
use: (path: string | undefined, handler: (...args: unknown[]) => unknown, ...args: unknown[]) => {
// TODO: Implement middleware registration
}
},
scheduler: undefined // Optional in spec
}
};
}

async init() {
// 0. Init Plugins (This allows plugins to register custom loaders)
for (const plugin of this.pluginsList) {
console.log(`Initializing plugin '${plugin.name}'...`);
const pluginId = plugin.id || 'unknown';

let app: IObjectQL = this;
const pkgName = (plugin as any)._packageName;

if (pkgName) {
app = new Proxy(this, {
get(target, prop) {
if (prop === 'on') {
return (event: HookName, obj: string, handler: HookHandler) =>
target.on(event, obj, handler, pkgName);
}
if (prop === 'registerAction') {
return (obj: string, act: string, handler: ActionHandler) =>
target.registerAction(obj, act, handler, pkgName);
}
const value = (target as any)[prop];
return typeof value === 'function' ? value.bind(target) : value;
}
});
console.log(`Initializing plugin '${pluginId}'...`);

// Call onEnable hook if it exists
if (plugin.onEnable) {
const context = this.createPluginContext();
await plugin.onEnable(context);
}

await plugin.setup(app);
}

// Packages, Presets, Source, Objects loading logic removed from Core.
Expand Down
49 changes: 13 additions & 36 deletions packages/foundation/core/test/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

import { ObjectQL } from '../src/app';
import { MockDriver } from './mock-driver';
import { ObjectConfig, ObjectQLPlugin, HookContext, ActionContext, Metadata } from '@objectql/types';
import { ObjectConfig, HookContext, ActionContext, Metadata } from '@objectql/types';
import type { PluginDefinition } from '@objectstack/spec';

const todoObject: ObjectConfig = {
name: 'todo',
Expand Down Expand Up @@ -65,9 +66,9 @@ describe('ObjectQL App', () => {
});

it('should accept plugin instances', () => {
const mockPlugin: ObjectQLPlugin = {
name: 'test-plugin',
setup: jest.fn()
const mockPlugin: PluginDefinition = {
id: 'test-plugin',
onEnable: jest.fn()
};
const app = new ObjectQL({
datasources: {},
Expand Down Expand Up @@ -291,10 +292,10 @@ describe('ObjectQL App', () => {

describe('Plugin System', () => {
it('should initialize plugins on init', async () => {
const setupFn = jest.fn();
const mockPlugin: ObjectQLPlugin = {
name: 'test-plugin',
setup: setupFn
const onEnableFn = jest.fn();
const mockPlugin: PluginDefinition = {
id: 'test-plugin',
onEnable: onEnableFn
};

const app = new ObjectQL({
Expand All @@ -303,43 +304,19 @@ describe('ObjectQL App', () => {
});

await app.init();
expect(setupFn).toHaveBeenCalledWith(app);
expect(onEnableFn).toHaveBeenCalled();
});

it('should use plugin method', () => {
const mockPlugin: ObjectQLPlugin = {
name: 'test-plugin',
setup: jest.fn()
const mockPlugin: PluginDefinition = {
id: 'test-plugin',
onEnable: jest.fn()
};

const app = new ObjectQL({ datasources: {} });
app.use(mockPlugin);
expect(app).toBeDefined();
});

it('should provide package-scoped proxy for plugins', async () => {
let capturedApp: any;
const mockPlugin: ObjectQLPlugin = {
name: 'test-plugin',
setup: async (app) => {
capturedApp = app;
}
};
(mockPlugin as any)._packageName = 'test-package';

const app = new ObjectQL({
datasources: {},
plugins: [mockPlugin]
});
app.registerObject(todoObject);

await app.init();

// Test proxied methods
const handler = jest.fn();
capturedApp.on('beforeCreate', 'todo', handler);
capturedApp.registerAction('todo', 'test', handler);
});
});

describe('Initialization', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/foundation/platform-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"dependencies": {
"@objectql/types": "workspace:*",
"@objectql/core": "workspace:*",
"@objectstack/spec": "^0.2.0",
"fast-glob": "^3.3.2",
"js-yaml": "^4.1.1"
},
Expand Down
Loading
Loading