@@ -4,6 +4,11 @@ import { Hono } from 'hono';
44import { logger } from 'hono/logger' ;
55import { cors } from 'hono/cors' ;
66import { SchemaRegistry , DataEngine } from '@objectstack/objectql' ;
7+ import { ServerPlugin } from './plugin' ;
8+ import { CoreRestApiPlugin } from './plugins/rest-api' ;
9+
10+ export * from './plugin' ;
11+ export { CoreRestApiPlugin } ;
712
813export interface ServerConfig {
914 port ?: number ;
@@ -12,13 +17,18 @@ export interface ServerConfig {
1217 path ?: string ;
1318 } ;
1419 logger ?: boolean ;
20+ /**
21+ * List of plugins to load.
22+ * Can include ObjectStack Manifests (Apps) or ServerPlugins (Runtime Logic).
23+ */
1524 plugins ?: any [ ] ;
1625}
1726
1827export class ObjectStackServer {
1928 public app : Hono ;
2029 public engine : DataEngine ;
2130 private config : ServerConfig ;
31+ private runtimePlugins : ServerPlugin [ ] = [ ] ;
2232
2333 constructor ( config : ServerConfig = { } ) {
2434 this . config = {
@@ -29,20 +39,50 @@ export class ObjectStackServer {
2939 } ;
3040
3141 this . app = new Hono ( ) ;
32- this . engine = new DataEngine ( this . config . plugins ) ;
42+
43+ // Separate Manifests (DataEngine) from Runtime Plugins (Server)
44+ const manifests : any [ ] = [ ] ;
45+
46+ // Always load Core REST API first (it can be overridden if needed, but it's core)
47+ this . runtimePlugins . push ( CoreRestApiPlugin ) ;
48+
49+ if ( this . config . plugins ) {
50+ this . config . plugins . forEach ( p => {
51+ if ( this . isServerPlugin ( p ) ) {
52+ this . runtimePlugins . push ( p ) ;
53+ } else {
54+ manifests . push ( p ) ;
55+ }
56+ } ) ;
57+ }
58+
59+ // Initialize Engine with Manifests
60+ this . engine = new DataEngine ( manifests ) ;
3361
3462 this . initializeMiddleware ( ) ;
35- this . initializeRoutes ( ) ;
63+ this . initializePlugins ( ) ;
3664 this . initializeStatic ( ) ;
3765 }
3866
67+ private isServerPlugin ( p : any ) : p is ServerPlugin {
68+ return p && typeof p . install === 'function' ;
69+ }
70+
3971 private initializeMiddleware ( ) {
4072 if ( this . config . logger ) {
4173 this . app . use ( '*' , logger ( ) ) ;
4274 }
4375 this . app . use ( '*' , cors ( ) ) ;
4476 }
4577
78+ private initializePlugins ( ) {
79+ console . log ( `[Server] Loading ${ this . runtimePlugins . length } runtime plugins...` ) ;
80+ for ( const plugin of this . runtimePlugins ) {
81+ console . log ( `[Server] Installing plugin: ${ plugin . name } ` ) ;
82+ plugin . install ( this ) ;
83+ }
84+ }
85+
4686 private initializeStatic ( ) {
4787 if ( this . config . static ) {
4888 const root = this . config . static . root ;
@@ -51,198 +91,6 @@ export class ObjectStackServer {
5191 }
5292 }
5393
54- private initializeRoutes ( ) {
55- // 1. Discovery
56- this . app . get ( '/api/v1' , ( c ) => {
57- return c . json ( {
58- name : 'ObjectOS Server' ,
59- version : '1.0.0' ,
60- environment : process . env . NODE_ENV || 'development' ,
61- routes : {
62- discovery : '/api/v1' ,
63- metadata : '/api/v1/meta' ,
64- data : '/api/v1/data' ,
65- auth : '/api/v1/auth' ,
66- ui : '/api/v1/ui'
67- } ,
68- capabilities : {
69- search : true ,
70- files : true
71- }
72- } ) ;
73- } ) ;
74-
75- // 2. Metadata: List Types
76- this . app . get ( '/api/v1/meta' , ( c ) => {
77- const types = SchemaRegistry . getRegisteredTypes ( ) ;
78- return c . json ( {
79- data : types . map ( type => ( {
80- type,
81- href : `/api/v1/meta/${ type } s` , // Convention: pluralize
82- count : SchemaRegistry . listItems ( type ) . length
83- } ) )
84- } ) ;
85- } ) ;
86-
87- // 3. Metadata: List Items by Type
88- this . app . get ( '/api/v1/meta/:type' , ( c ) => {
89- const typePlural = c . req . param ( 'type' ) ;
90-
91- // Simple Singularization Mapping
92- const typeMap : Record < string , string > = {
93- 'objects' : 'object' ,
94- 'apps' : 'app' ,
95- 'flows' : 'flow' ,
96- 'reports' : 'report' ,
97- 'plugins' : 'plugin' ,
98- 'kinds' : 'kind'
99- } ;
100- const type = typeMap [ typePlural ] || typePlural ; // Fallback to direct pass
101-
102- const items = SchemaRegistry . listItems ( type ) ;
103-
104- const summaries = items . map ( ( item : any ) => ( {
105- id : item . id ,
106- name : item . name ,
107- label : item . label ,
108- type : item . type ,
109- icon : item . icon ,
110- description : item . description ,
111- ...( type === 'object' ? { path : `/api/v1/data/${ item . name } ` } : { } ) ,
112- self : `/api/v1/meta/${ typePlural } /${ item . name || item . id } `
113- } ) ) ;
114-
115- return c . json ( { data : summaries } ) ;
116- } ) ;
117-
118- // 4. Metadata: Get Single Item
119- this . app . get ( '/api/v1/meta/:type/:name' , ( c ) => {
120- const typePlural = c . req . param ( 'type' ) ;
121- const name = c . req . param ( 'name' ) ;
122-
123- const typeMap : Record < string , string > = {
124- 'objects' : 'object' ,
125- 'apps' : 'app' ,
126- 'flows' : 'flow' ,
127- 'reports' : 'report' ,
128- 'plugins' : 'plugin' ,
129- 'kinds' : 'kind'
130- } ;
131- const type = typeMap [ typePlural ] || typePlural ;
132-
133- const item = SchemaRegistry . getItem ( type , name ) ;
134- if ( ! item ) return c . json ( { error : `Metadata not found: ${ type } /${ name } ` } , 404 ) ;
135-
136- return c . json ( item ) ;
137- } ) ;
138-
139- // 5. UI: View Definition
140- this . app . get ( '/api/v1/ui/view/:object' , ( c ) => {
141- const objectName = c . req . param ( 'object' ) ;
142- const type = ( c . req . query ( 'type' ) as 'list' | 'form' ) || 'list' ;
143- try {
144- const view = this . engine . getView ( objectName , type ) ;
145- if ( ! view ) return c . json ( { error : 'View not generated' } , 404 ) ;
146- return c . json ( view ) ;
147- } catch ( e : any ) {
148- return c . json ( { error : e . message } , 400 ) ;
149- }
150- } ) ;
151-
152- // 6. Data: Find
153- this . app . get ( '/api/v1/data/:object' , async ( c ) => {
154- const objectName = c . req . param ( 'object' ) ;
155- const query = c . req . query ( ) ;
156-
157- try {
158- // TODO: Map query params to cleaner AST if needed, or Engine does simple mapping
159- // e.g. ?sort=-name -> { sort: ['-name'] }
160- const result = await this . engine . find ( objectName , query ) ;
161- return c . json ( result ) ;
162- } catch ( e : any ) {
163- return c . json ( { error : e . message } , 400 ) ;
164- }
165- } ) ;
166-
167- // 7. Data: Query (Advanced AST)
168- this . app . post ( '/api/v1/data/:object/query' , async ( c ) => {
169- const objectName = c . req . param ( 'object' ) ;
170- const body = await c . req . json ( ) ;
171-
172- try {
173- // Body is Partial<QueryAST>
174- // Engine find expects (object, filters/options)
175- // If engine.find signature supports AST passing, we pass it.
176- // Currently engine.find(object, filters: any).
177- // Let's assume engine handles it or we adapt it.
178- // For now, pass body as the filter/options object.
179- const result = await this . engine . find ( objectName , body ) ;
180- return c . json ( result ) ;
181- } catch ( e : any ) {
182- return c . json ( { error : e . message } , 400 ) ;
183- }
184- } ) ;
185-
186- // 8. Data: Get
187- this . app . get ( '/api/v1/data/:object/:id' , async ( c ) => {
188- const objectName = c . req . param ( 'object' ) ;
189- const id = c . req . param ( 'id' ) ;
190- try {
191- const result = await this . engine . get ( objectName , id ) ;
192- return c . json ( result ) ;
193- } catch ( e : any ) {
194- return c . json ( { error : e . message } , 404 ) ;
195- }
196- } ) ;
197-
198- // 9. Data: Create
199- this . app . post ( '/api/v1/data/:object' , async ( c ) => {
200- const objectName = c . req . param ( 'object' ) ;
201- const body = await c . req . json ( ) ;
202- try {
203- const result = await this . engine . create ( objectName , body ) ;
204- return c . json ( result , 201 ) ;
205- } catch ( e : any ) {
206- return c . json ( { error : e . message } , 400 ) ;
207- }
208- } ) ;
209-
210- // 10. Data: Update
211- this . app . patch ( '/api/v1/data/:object/:id' , async ( c ) => {
212- const objectName = c . req . param ( 'object' ) ;
213- const id = c . req . param ( 'id' ) ;
214- const body = await c . req . json ( ) ;
215- try {
216- const result = await this . engine . update ( objectName , id , body ) ;
217- return c . json ( result ) ;
218- } catch ( e : any ) {
219- return c . json ( { error : e . message } , 400 ) ;
220- }
221- } ) ;
222-
223- // 11. Data: Delete
224- this . app . delete ( '/api/v1/data/:object/:id' , async ( c ) => {
225- const objectName = c . req . param ( 'object' ) ;
226- const id = c . req . param ( 'id' ) ;
227- try {
228- const result = await this . engine . delete ( objectName , id ) ;
229- return c . json ( result ) ;
230- } catch ( e : any ) {
231- return c . json ( { error : e . message } , 400 ) ;
232- }
233- } ) ;
234-
235- // 12. Data: Batch Operations
236- this . app . post ( '/api/v1/data/:object/batch' , async ( c ) => {
237- // TODO: Implement batch in Engine
238- return c . json ( { error : 'Not implemented' } , 501 ) ;
239- } ) ;
240- this . app . delete ( '/api/v1/data/:object/batch' , async ( c ) => {
241- // TODO: Implement batch in Engine
242- return c . json ( { error : 'Not implemented' } , 501 ) ;
243- } ) ;
244- }
245-
24694 public async start ( ) {
24795 console . log ( '--- Starting ObjectStack Server ---' ) ;
24896 await this . engine . start ( ) ;
@@ -255,3 +103,4 @@ export class ObjectStackServer {
255103 } ) ;
256104 }
257105}
106+
0 commit comments