@@ -15,15 +15,20 @@ import {
1515} from '@objectql/types' ;
1616import { ObjectLoader } from './loader' ;
1717import { ObjectRepository } from './repository' ;
18- import { RemoteDriver } from '@objectql/driver-remote' ;
18+ import { loadPlugin } from './plugin' ;
19+ import { createDriverFromConnection } from './driver' ;
20+ import { loadRemoteFromUrl } from './remote' ;
21+ import { executeActionHelper , registerActionHelper , ActionEntry } from './action' ;
22+ import { registerHookHelper , triggerHookHelper , HookEntry } from './hook' ;
23+ import { registerObjectHelper , getConfigsHelper } from './object' ;
1924
2025export class ObjectQL implements IObjectQL {
2126 public metadata : ObjectRegistry ;
2227 private loader : ObjectLoader ;
2328 private datasources : Record < string , Driver > = { } ;
2429 private remotes : string [ ] = [ ] ;
25- private hooks : Record < string , Array < { objectName : string , handler : HookHandler , packageName ?: string } > > = { } ;
26- private actions : Record < string , { handler : ActionHandler , packageName ?: string } > = { } ;
30+ private hooks : Record < string , HookEntry [ ] > = { } ;
31+ private actions : Record < string , ActionEntry > = { } ;
2732 private pluginsList : ObjectQLPlugin [ ] = [ ] ;
2833
2934 constructor ( config : ObjectQLConfig ) {
@@ -33,7 +38,7 @@ export class ObjectQL implements IObjectQL {
3338 this . remotes = config . remotes || [ ] ;
3439
3540 if ( config . connection ) {
36- this . loadDriverFromConnection ( config . connection ) ;
41+ this . datasources [ 'default' ] = createDriverFromConnection ( config . connection ) ;
3742 }
3843
3944 // 1. Load Presets/Packages first (Base Layer)
@@ -51,7 +56,7 @@ export class ObjectQL implements IObjectQL {
5156 if ( config . plugins ) {
5257 for ( const plugin of config . plugins ) {
5358 if ( typeof plugin === 'string' ) {
54- this . loadPluginFromPackage ( plugin ) ;
59+ this . use ( loadPlugin ( plugin ) ) ;
5560 } else {
5661 this . use ( plugin ) ;
5762 }
@@ -74,58 +79,6 @@ export class ObjectQL implements IObjectQL {
7479 }
7580 }
7681
77- private loadPluginFromPackage ( packageName : string ) {
78- let mod : any ;
79- try {
80- const modulePath = require . resolve ( packageName , { paths : [ process . cwd ( ) ] } ) ;
81- mod = require ( modulePath ) ;
82- } catch ( e ) {
83- throw new Error ( `Failed to resolve plugin '${ packageName } ': ${ e } ` ) ;
84- }
85-
86- // Helper to find plugin instance
87- const findPlugin = ( candidate : any ) : ObjectQLPlugin | undefined => {
88- if ( ! candidate ) return undefined ;
89-
90- // 1. Try treating as Class
91- if ( typeof candidate === 'function' ) {
92- try {
93- const inst = new candidate ( ) ;
94- if ( inst && typeof inst . setup === 'function' ) {
95- return inst ; // Found it!
96- }
97- } catch ( e ) {
98- // Not a constructor or instantiation failed
99- }
100- }
101-
102- // 2. Try treating as Instance
103- if ( candidate && typeof candidate . setup === 'function' ) {
104- if ( candidate . name ) return candidate ;
105- }
106- return undefined ;
107- } ;
108-
109- // Search in default, module root, and all named exports
110- let instance = findPlugin ( mod . default ) || findPlugin ( mod ) ;
111-
112- if ( ! instance && mod && typeof mod === 'object' ) {
113- for ( const key of Object . keys ( mod ) ) {
114- if ( key === 'default' ) continue ;
115- instance = findPlugin ( mod [ key ] ) ;
116- if ( instance ) break ;
117- }
118- }
119-
120- if ( instance ) {
121- ( instance as any ) . _packageName = packageName ;
122- this . use ( instance ) ;
123- } else {
124- console . error ( `[PluginLoader] Failed to find ObjectQLPlugin in '${ packageName } '. Exports:` , Object . keys ( mod ) ) ;
125- throw new Error ( `Plugin '${ packageName } ' must export a class or object implementing ObjectQLPlugin.` ) ;
126- }
127- }
128-
12982 addPackage ( name : string ) {
13083 this . loader . loadPackage ( name ) ;
13184 }
@@ -151,48 +104,19 @@ export class ObjectQL implements IObjectQL {
151104 }
152105
153106 on ( event : HookName , objectName : string , handler : HookHandler , packageName ?: string ) {
154- if ( ! this . hooks [ event ] ) {
155- this . hooks [ event ] = [ ] ;
156- }
157- this . hooks [ event ] . push ( { objectName, handler, packageName } ) ;
107+ registerHookHelper ( this . hooks , event , objectName , handler , packageName ) ;
158108 }
159109
160110 async triggerHook ( event : HookName , objectName : string , ctx : HookContext ) {
161- // 1. Registry Hooks (File-based)
162- const fileHooks = this . metadata . get < any > ( 'hook' , objectName ) ;
163- if ( fileHooks && typeof fileHooks [ event ] === 'function' ) {
164- await fileHooks [ event ] ( ctx ) ;
165- }
166-
167- // 2. Programmatic Hooks
168- const hooks = this . hooks [ event ] || [ ] ;
169- for ( const hook of hooks ) {
170- if ( hook . objectName === '*' || hook . objectName === objectName ) {
171- await hook . handler ( ctx ) ;
172- }
173- }
111+ await triggerHookHelper ( this . metadata , this . hooks , event , objectName , ctx ) ;
174112 }
175113
176114 registerAction ( objectName : string , actionName : string , handler : ActionHandler , packageName ?: string ) {
177- const key = `${ objectName } :${ actionName } ` ;
178- this . actions [ key ] = { handler, packageName } ;
115+ registerActionHelper ( this . actions , objectName , actionName , handler , packageName ) ;
179116 }
180117
181118 async executeAction ( objectName : string , actionName : string , ctx : ActionContext ) {
182- // 1. Programmatic
183- const key = `${ objectName } :${ actionName } ` ;
184- const actionEntry = this . actions [ key ] ;
185- if ( actionEntry ) {
186- return await actionEntry . handler ( ctx ) ;
187- }
188-
189- // 2. Registry (File-based)
190- const fileActions = this . metadata . get < any > ( 'action' , objectName ) ;
191- if ( fileActions && typeof fileActions [ actionName ] === 'function' ) {
192- return await fileActions [ actionName ] ( ctx ) ;
193- }
194-
195- throw new Error ( `Action '${ actionName } ' not found for object '${ objectName } '` ) ;
119+ return await executeActionHelper ( this . metadata , this . actions , objectName , actionName , ctx ) ;
196120 }
197121
198122 loadFromDirectory ( dir : string , packageName ?: string ) {
@@ -244,19 +168,7 @@ export class ObjectQL implements IObjectQL {
244168 }
245169
246170 registerObject ( object : ObjectConfig ) {
247- // Normalize fields
248- if ( object . fields ) {
249- for ( const [ key , field ] of Object . entries ( object . fields ) ) {
250- if ( ! field . name ) {
251- field . name = key ;
252- }
253- }
254- }
255- this . metadata . register ( 'object' , {
256- type : 'object' ,
257- id : object . name ,
258- content : object
259- } ) ;
171+ registerObjectHelper ( this . metadata , object ) ;
260172 }
261173
262174 unregisterObject ( name : string ) {
@@ -268,12 +180,7 @@ export class ObjectQL implements IObjectQL {
268180 }
269181
270182 getConfigs ( ) : Record < string , ObjectConfig > {
271- const result : Record < string , ObjectConfig > = { } ;
272- const objects = this . metadata . list < ObjectConfig > ( 'object' ) ;
273- for ( const obj of objects ) {
274- result [ obj . name ] = obj ;
275- }
276- return result ;
183+ return getConfigsHelper ( this . metadata ) ;
277184 }
278185
279186 datasource ( name : string ) : Driver {
@@ -288,7 +195,15 @@ export class ObjectQL implements IObjectQL {
288195 // -1. Load Remotes
289196 if ( this . remotes . length > 0 ) {
290197 console . log ( `Loading ${ this . remotes . length } remotes...` ) ;
291- await Promise . all ( this . remotes . map ( url => this . loadRemote ( url ) ) ) ;
198+ const results = await Promise . all ( this . remotes . map ( url => loadRemoteFromUrl ( url ) ) ) ;
199+ for ( const res of results ) {
200+ if ( res ) {
201+ this . datasources [ res . driverName ] = res . driver ;
202+ for ( const obj of res . objects ) {
203+ this . registerObject ( obj ) ;
204+ }
205+ }
206+ }
292207 }
293208
294209 // 0. Init Plugins
@@ -329,95 +244,4 @@ export class ObjectQL implements IObjectQL {
329244 }
330245 }
331246 }
332-
333- private loadDriverFromConnection ( connection : string ) {
334- let driverPackage = '' ;
335- let driverClass = '' ;
336- let driverConfig : any = { } ;
337-
338- if ( connection . startsWith ( 'mongodb://' ) ) {
339- driverPackage = '@objectql/driver-mongo' ;
340- driverClass = 'MongoDriver' ;
341- driverConfig = { url : connection } ;
342- }
343- else if ( connection . startsWith ( 'sqlite://' ) ) {
344- driverPackage = '@objectql/driver-knex' ;
345- driverClass = 'KnexDriver' ;
346- const filename = connection . replace ( 'sqlite://' , '' ) ;
347- driverConfig = {
348- client : 'sqlite3' ,
349- connection : { filename } ,
350- useNullAsDefault : true
351- } ;
352- }
353- else if ( connection . startsWith ( 'postgres://' ) || connection . startsWith ( 'postgresql://' ) ) {
354- driverPackage = '@objectql/driver-knex' ;
355- driverClass = 'KnexDriver' ;
356- driverConfig = {
357- client : 'pg' ,
358- connection : connection
359- } ;
360- }
361- else if ( connection . startsWith ( 'mysql://' ) ) {
362- driverPackage = '@objectql/driver-knex' ;
363- driverClass = 'KnexDriver' ;
364- driverConfig = {
365- client : 'mysql2' ,
366- connection : connection
367- } ;
368- }
369- else {
370- throw new Error ( `Unsupported connection protocol: ${ connection } ` ) ;
371- }
372-
373- try {
374- // eslint-disable-next-line @typescript-eslint/no-var-requires
375- const pkg = require ( driverPackage ) ;
376- const DriverClass = pkg [ driverClass ] ;
377- if ( ! DriverClass ) {
378- throw new Error ( `${ driverClass } not found in ${ driverPackage } ` ) ;
379- }
380- this . datasources [ 'default' ] = new DriverClass ( driverConfig ) ;
381- } catch ( e : any ) {
382- throw new Error ( `Failed to load driver ${ driverPackage } . Please install it: npm install ${ driverPackage } . Error: ${ e . message } ` ) ;
383- }
384- }
385-
386- private async loadRemote ( url : string ) {
387- try {
388- const baseUrl = url . replace ( / \/ $ / , '' ) ;
389- const metadataUrl = `${ baseUrl } /api/metadata/objects` ;
390-
391- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
392- // @ts -ignore - Fetch is available in Node 18+
393- const res = await fetch ( metadataUrl ) ;
394- if ( ! res . ok ) {
395- console . warn ( `[ObjectQL] Remote ${ url } returned ${ res . status } ` ) ;
396- return ;
397- }
398-
399- const data = await res . json ( ) as any ;
400- if ( ! data || ! data . objects ) return ;
401-
402- const driverName = `remote:${ baseUrl } ` ;
403- this . datasources [ driverName ] = new RemoteDriver ( baseUrl ) ;
404-
405- await Promise . all ( data . objects . map ( async ( summary : any ) => {
406- try {
407- // @ts -ignore
408- const detailRes = await fetch ( `${ metadataUrl } /${ summary . name } ` ) ;
409- if ( detailRes . ok ) {
410- const config = await detailRes . json ( ) as ObjectConfig ;
411- config . datasource = driverName ;
412- this . registerObject ( config ) ;
413- }
414- } catch ( e ) {
415- console . warn ( `[ObjectQL] Failed to load object ${ summary . name } from ${ url } ` ) ;
416- }
417- } ) ) ;
418-
419- } catch ( e : any ) {
420- console . warn ( `[ObjectQL] Remote connection error ${ url } : ${ e . message } ` ) ;
421- }
422- }
423247}
0 commit comments