@@ -5,6 +5,18 @@ import { RouteManager } from './route-manager.js';
55import { RestServerConfig , RestApiConfig , CrudEndpointsConfig , MetadataEndpointsConfig , BatchEndpointsConfig , RouteGenerationConfig } from '@objectstack/spec/api' ;
66import { ObjectStackProtocol } from '@objectstack/spec/api' ;
77
8+ /**
9+ * Structural subset of `KernelManager` that RestServer needs in order to
10+ * resolve a per-project protocol at request time. Typed locally to avoid
11+ * an @objectstack/runtime → @objectstack/rest → @objectstack/runtime
12+ * package cycle.
13+ */
14+ export interface RestKernelManager {
15+ getOrCreate ( projectId : string ) : Promise < {
16+ getServiceAsync < T = unknown > ( name : string ) : Promise < T > ;
17+ } > ;
18+ }
19+
820/**
921 * Normalized REST Server Configuration
1022 * All nested properties are required after normalization
@@ -97,15 +109,31 @@ export class RestServer {
97109 private protocol : ObjectStackProtocol ;
98110 private config : NormalizedRestServerConfig ;
99111 private routeManager : RouteManager ;
100-
112+ private kernelManager ?: RestKernelManager ;
113+
101114 constructor (
102- server : IHttpServer ,
103- protocol : ObjectStackProtocol ,
104- config : RestServerConfig = { }
115+ server : IHttpServer ,
116+ protocol : ObjectStackProtocol ,
117+ config : RestServerConfig = { } ,
118+ kernelManager ?: RestKernelManager ,
105119 ) {
106120 this . protocol = protocol ;
107121 this . config = this . normalizeConfig ( config ) ;
108122 this . routeManager = new RouteManager ( server ) ;
123+ this . kernelManager = kernelManager ;
124+ }
125+
126+ /**
127+ * Resolve the protocol for a given request. When `projectId` is present
128+ * and a KernelManager is wired, fetch the per-project kernel's
129+ * `protocol` service so metadata / data / UI reads hit the project's
130+ * own registry and datastore. Otherwise fall back to the control-kernel
131+ * protocol captured at boot.
132+ */
133+ private async resolveProtocol ( projectId ?: string ) : Promise < ObjectStackProtocol > {
134+ if ( ! projectId || ! this . kernelManager ) return this . protocol ;
135+ const kernel = await this . kernelManager . getOrCreate ( projectId ) ;
136+ return kernel . getServiceAsync < ObjectStackProtocol > ( 'protocol' ) ;
109137 }
110138
111139 /**
@@ -333,9 +361,8 @@ export class RestServer {
333361 handler : async ( req : any , res : any ) => {
334362 try {
335363 const projectId = isScoped ? req . params ?. projectId : undefined ;
336- const types = await this . protocol . getMetaTypes (
337- projectId ? ( { projectId } as any ) : undefined ,
338- ) ;
364+ const p = await this . resolveProtocol ( projectId ) ;
365+ const types = await p . getMetaTypes ( ) ;
339366 res . json ( types ) ;
340367 } catch ( error : any ) {
341368 res . status ( 500 ) . json ( { error : error . message } ) ;
@@ -357,10 +384,10 @@ export class RestServer {
357384 try {
358385 const packageId = req . query ?. package || undefined ;
359386 const projectId = isScoped ? req . params ?. projectId : undefined ;
360- const items = await this . protocol . getMetaItems ( {
387+ const p = await this . resolveProtocol ( projectId ) ;
388+ const items = await p . getMetaItems ( {
361389 type : req . params . type ,
362390 packageId,
363- ...( projectId ? { projectId } : { } ) ,
364391 } as any ) ;
365392 res . json ( items ) ;
366393 } catch ( error : any ) {
@@ -382,18 +409,18 @@ export class RestServer {
382409 handler : async ( req : any , res : any ) => {
383410 try {
384411 const projectId = isScoped ? req . params ?. projectId : undefined ;
412+ const p = await this . resolveProtocol ( projectId ) ;
385413 // Check if cached version is available
386- if ( metadata . enableCache && this . protocol . getMetaItemCached ) {
414+ if ( metadata . enableCache && p . getMetaItemCached ) {
387415 const cacheRequest = {
388416 ifNoneMatch : req . headers [ 'if-none-match' ] as string ,
389417 ifModifiedSince : req . headers [ 'if-modified-since' ] as string ,
390418 } ;
391419
392- const result = await this . protocol . getMetaItemCached ( {
420+ const result = await p . getMetaItemCached ( {
393421 type : req . params . type ,
394422 name : req . params . name ,
395423 cacheRequest,
396- ...( projectId ? { projectId } : { } ) ,
397424 } as any ) ;
398425
399426 if ( result . notModified ) {
@@ -423,11 +450,10 @@ export class RestServer {
423450 } else {
424451 // Non-cached version
425452 const packageId = req . query ?. package || undefined ;
426- const item = await this . protocol . getMetaItem ( {
453+ const item = await p . getMetaItem ( {
427454 type : req . params . type ,
428455 name : req . params . name ,
429456 packageId,
430- ...( projectId ? { projectId } : { } ) ,
431457 } as any ) ;
432458 res . json ( item ) ;
433459 }
@@ -450,17 +476,17 @@ export class RestServer {
450476 path : `${ metaPath } /:type/:name` ,
451477 handler : async ( req : any , res : any ) => {
452478 try {
453- if ( ! this . protocol . saveMetaItem ) {
479+ const projectId = isScoped ? req . params ?. projectId : undefined ;
480+ const p = await this . resolveProtocol ( projectId ) ;
481+ if ( ! p . saveMetaItem ) {
454482 res . status ( 501 ) . json ( { error : 'Save operation not supported by protocol implementation' } ) ;
455483 return ;
456484 }
457485
458- const projectId = isScoped ? req . params ?. projectId : undefined ;
459- const result = await this . protocol . saveMetaItem ( {
486+ const result = await p . saveMetaItem ( {
460487 type : req . params . type ,
461488 name : req . params . name ,
462489 item : req . body ,
463- ...( projectId ? { projectId } : { } ) ,
464490 } as any ) ;
465491 res . json ( result ) ;
466492 } catch ( error : any ) {
@@ -487,12 +513,12 @@ export class RestServer {
487513 path : `${ uiPath } /view/:object/:type` ,
488514 handler : async ( req : any , res : any ) => {
489515 try {
490- if ( this . protocol . getUiView ) {
491- const projectId = isScoped ? req . params ?. projectId : undefined ;
492- const view = await this . protocol . getUiView ( {
516+ const projectId = isScoped ? req . params ?. projectId : undefined ;
517+ const p = await this . resolveProtocol ( projectId ) ;
518+ if ( p . getUiView ) {
519+ const view = await p . getUiView ( {
493520 object : req . params . object ,
494521 type : req . params . type as any ,
495- ...( projectId ? { projectId } : { } ) ,
496522 } as any ) ;
497523 res . json ( view ) ;
498524 } else {
@@ -527,10 +553,10 @@ export class RestServer {
527553 handler : async ( req : any , res : any ) => {
528554 try {
529555 const projectId = isScoped ? req . params ?. projectId : undefined ;
530- const result = await this . protocol . findData ( {
556+ const p = await this . resolveProtocol ( projectId ) ;
557+ const result = await p . findData ( {
531558 object : req . params . object ,
532559 query : req . query ,
533- ...( projectId ? { projectId } : { } ) ,
534560 } as any ) ;
535561 res . json ( result ) ;
536562 } catch ( error : any ) {
@@ -552,13 +578,13 @@ export class RestServer {
552578 handler : async ( req : any , res : any ) => {
553579 try {
554580 const projectId = isScoped ? req . params ?. projectId : undefined ;
581+ const p = await this . resolveProtocol ( projectId ) ;
555582 const { select, expand } = req . query || { } ;
556- const result = await this . protocol . getData ( {
583+ const result = await p . getData ( {
557584 object : req . params . object ,
558585 id : req . params . id ,
559586 ...( select != null ? { select } : { } ) ,
560587 ...( expand != null ? { expand } : { } ) ,
561- ...( projectId ? { projectId } : { } ) ,
562588 } as any ) ;
563589 res . json ( result ) ;
564590 } catch ( error : any ) {
@@ -580,10 +606,10 @@ export class RestServer {
580606 handler : async ( req : any , res : any ) => {
581607 try {
582608 const projectId = isScoped ? req . params ?. projectId : undefined ;
583- const result = await this . protocol . createData ( {
609+ const p = await this . resolveProtocol ( projectId ) ;
610+ const result = await p . createData ( {
584611 object : req . params . object ,
585612 data : req . body ,
586- ...( projectId ? { projectId } : { } ) ,
587613 } as any ) ;
588614 res . status ( 201 ) . json ( result ) ;
589615 } catch ( error : any ) {
@@ -605,11 +631,11 @@ export class RestServer {
605631 handler : async ( req : any , res : any ) => {
606632 try {
607633 const projectId = isScoped ? req . params ?. projectId : undefined ;
608- const result = await this . protocol . updateData ( {
634+ const p = await this . resolveProtocol ( projectId ) ;
635+ const result = await p . updateData ( {
609636 object : req . params . object ,
610637 id : req . params . id ,
611638 data : req . body ,
612- ...( projectId ? { projectId } : { } ) ,
613639 } as any ) ;
614640 res . json ( result ) ;
615641 } catch ( error : any ) {
@@ -631,10 +657,10 @@ export class RestServer {
631657 handler : async ( req : any , res : any ) => {
632658 try {
633659 const projectId = isScoped ? req . params ?. projectId : undefined ;
634- const result = await this . protocol . deleteData ( {
660+ const p = await this . resolveProtocol ( projectId ) ;
661+ const result = await p . deleteData ( {
635662 object : req . params . object ,
636663 id : req . params . id ,
637- ...( projectId ? { projectId } : { } ) ,
638664 } as any ) ;
639665 res . json ( result ) ;
640666 } catch ( error : any ) {
@@ -667,10 +693,10 @@ export class RestServer {
667693 handler : async ( req : any , res : any ) => {
668694 try {
669695 const projectId = isScoped ? req . params ?. projectId : undefined ;
670- const result = await this . protocol . batchData ! ( {
696+ const p = await this . resolveProtocol ( projectId ) ;
697+ const result = await p . batchData ! ( {
671698 object : req . params . object ,
672699 request : req . body ,
673- ...( projectId ? { projectId } : { } ) ,
674700 } as any ) ;
675701 res . json ( result ) ;
676702 } catch ( error : any ) {
@@ -692,10 +718,10 @@ export class RestServer {
692718 handler : async ( req : any , res : any ) => {
693719 try {
694720 const projectId = isScoped ? req . params ?. projectId : undefined ;
695- const result = await this . protocol . createManyData ! ( {
721+ const p = await this . resolveProtocol ( projectId ) ;
722+ const result = await p . createManyData ! ( {
696723 object : req . params . object ,
697724 records : req . body || [ ] ,
698- ...( projectId ? { projectId } : { } ) ,
699725 } as any ) ;
700726 res . status ( 201 ) . json ( result ) ;
701727 } catch ( error : any ) {
@@ -717,10 +743,10 @@ export class RestServer {
717743 handler : async ( req : any , res : any ) => {
718744 try {
719745 const projectId = isScoped ? req . params ?. projectId : undefined ;
720- const result = await this . protocol . updateManyData ! ( {
746+ const p = await this . resolveProtocol ( projectId ) ;
747+ const result = await p . updateManyData ! ( {
721748 object : req . params . object ,
722749 ...req . body ,
723- ...( projectId ? { projectId } : { } ) ,
724750 } as any ) ;
725751 res . json ( result ) ;
726752 } catch ( error : any ) {
@@ -742,10 +768,10 @@ export class RestServer {
742768 handler : async ( req : any , res : any ) => {
743769 try {
744770 const projectId = isScoped ? req . params ?. projectId : undefined ;
745- const result = await this . protocol . deleteManyData ! ( {
771+ const p = await this . resolveProtocol ( projectId ) ;
772+ const result = await p . deleteManyData ! ( {
746773 object : req . params . object ,
747774 ...req . body ,
748- ...( projectId ? { projectId } : { } ) ,
749775 } as any ) ;
750776 res . json ( result ) ;
751777 } catch ( error : any ) {
0 commit comments