22
33import { ObjectStackProtocol } from '@objectstack/spec/api' ;
44import { IDataEngine } from '@objectstack/core' ;
5- import type {
6- BatchUpdateRequest ,
7- BatchUpdateResponse ,
5+ import type {
6+ BatchUpdateRequest ,
7+ BatchUpdateResponse ,
88 UpdateManyDataRequest ,
99 DeleteManyDataRequest
1010} from '@objectstack/spec/api' ;
1111import type { MetadataCacheRequest , MetadataCacheResponse , ServiceInfo , ApiRoutes , WellKnownCapabilities } from '@objectstack/spec/api' ;
1212import type { IFeedService } from '@objectstack/spec/contracts' ;
1313import { parseFilterAST , isFilterAST } from '@objectstack/spec/data' ;
14+ import { PLURAL_TO_SINGULAR , SINGULAR_TO_PLURAL } from '@objectstack/spec/shared' ;
1415
1516// We import SchemaRegistry directly since this class lives in the same package
1617import { SchemaRegistry } from './registry.js' ;
@@ -201,10 +202,10 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
201202 async getMetaItems ( request : { type : string ; packageId ?: string } ) {
202203 const { packageId } = request ;
203204 let items = SchemaRegistry . listItems ( request . type , packageId ) ;
204- // Normalize singular/plural: REST uses singular ('app') but registry may store as plural ('apps')
205+ // Normalize singular/plural using explicit mapping
205206 if ( items . length === 0 ) {
206- const alt = request . type . endsWith ( 's' ) ? request . type . slice ( 0 , - 1 ) : request . type + 's' ;
207- items = SchemaRegistry . listItems ( alt , packageId ) ;
207+ const alt = PLURAL_TO_SINGULAR [ request . type ] ?? SINGULAR_TO_PLURAL [ request . type ] ;
208+ if ( alt ) items = SchemaRegistry . listItems ( alt , packageId ) ;
208209 }
209210
210211 // Fallback to database if registry is empty for this type
@@ -225,8 +226,9 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
225226 return data ;
226227 } ) ;
227228 } else {
228- // Try alternate type name in DB
229- const alt = request . type . endsWith ( 's' ) ? request . type . slice ( 0 , - 1 ) : request . type + 's' ;
229+ // Try alternate type name in DB using explicit mapping
230+ const alt = PLURAL_TO_SINGULAR [ request . type ] ?? SINGULAR_TO_PLURAL [ request . type ] ;
231+ if ( alt ) {
230232 const altRecords = await this . engine . find ( 'sys_metadata' , {
231233 where : { type : alt , state : 'active' }
232234 } ) ;
@@ -239,6 +241,7 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
239241 return data ;
240242 } ) ;
241243 }
244+ }
242245 }
243246 } catch {
244247 // DB not available, return registry results (empty)
@@ -281,10 +284,10 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
281284
282285 async getMetaItem ( request : { type : string , name : string , packageId ?: string } ) {
283286 let item = SchemaRegistry . getItem ( request . type , request . name ) ;
284- // Normalize singular/plural
287+ // Normalize singular/plural using explicit mapping
285288 if ( item === undefined ) {
286- const alt = request . type . endsWith ( 's' ) ? request . type . slice ( 0 , - 1 ) : request . type + 's' ;
287- item = SchemaRegistry . getItem ( alt , request . name ) ;
289+ const alt = PLURAL_TO_SINGULAR [ request . type ] ?? SINGULAR_TO_PLURAL [ request . type ] ;
290+ if ( alt ) item = SchemaRegistry . getItem ( alt , request . name ) ;
288291 }
289292
290293 // Fallback to database if not in registry
@@ -300,8 +303,9 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
300303 // Hydrate back into registry for next time
301304 SchemaRegistry . registerItem ( request . type , item , 'name' as any ) ;
302305 } else {
303- // Try alternate type name
304- const alt = request . type . endsWith ( 's' ) ? request . type . slice ( 0 , - 1 ) : request . type + 's' ;
306+ // Try alternate type name using explicit mapping
307+ const alt = PLURAL_TO_SINGULAR [ request . type ] ?? SINGULAR_TO_PLURAL [ request . type ] ;
308+ if ( alt ) {
305309 const altRecord = await this . engine . findOne ( 'sys_metadata' , {
306310 where : { type : alt , name : request . name , state : 'active' }
307311 } ) ;
@@ -312,6 +316,7 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
312316 // Hydrate back into registry for next time
313317 SchemaRegistry . registerItem ( request . type , item , 'name' as any ) ;
314318 }
319+ }
315320 }
316321 } catch {
317322 // DB not available, return undefined
@@ -617,7 +622,27 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
617622
618623 async getMetaItemCached ( request : { type : string , name : string , cacheRequest ?: MetadataCacheRequest } ) : Promise < MetadataCacheResponse > {
619624 try {
620- const item = SchemaRegistry . getItem ( request . type , request . name ) ;
625+ let item = SchemaRegistry . getItem ( request . type , request . name ) ;
626+
627+ // Normalize singular/plural using explicit mapping
628+ if ( ! item ) {
629+ const alt = PLURAL_TO_SINGULAR [ request . type ] ?? SINGULAR_TO_PLURAL [ request . type ] ;
630+ if ( alt ) item = SchemaRegistry . getItem ( alt , request . name ) ;
631+ }
632+
633+ // Fallback to MetadataService (e.g. agents, tools registered in MetadataManager)
634+ if ( ! item ) {
635+ try {
636+ const services = this . getServicesRegistry ?.( ) ;
637+ const metadataService = services ?. get ( 'metadata' ) ;
638+ if ( metadataService && typeof metadataService . get === 'function' ) {
639+ item = await metadataService . get ( request . type , request . name ) ;
640+ }
641+ } catch {
642+ // MetadataService not available
643+ }
644+ }
645+
621646 if ( ! item ) {
622647 throw new Error ( `Metadata item ${ request . type } /${ request . name } not found` ) ;
623648 }
@@ -1038,10 +1063,12 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
10381063 const data = typeof record . metadata === 'string'
10391064 ? JSON . parse ( record . metadata )
10401065 : record . metadata ;
1041- if ( record . type === 'object' ) {
1066+ // Normalize DB type to singular (DB may store legacy plural forms)
1067+ const normalizedType = PLURAL_TO_SINGULAR [ record . type ] ?? record . type ;
1068+ if ( normalizedType === 'object' ) {
10421069 SchemaRegistry . registerObject ( data as any , record . packageId || 'sys_metadata' ) ;
10431070 } else {
1044- SchemaRegistry . registerItem ( record . type , data , 'name' as any ) ;
1071+ SchemaRegistry . registerItem ( normalizedType , data , 'name' as any ) ;
10451072 }
10461073 loaded ++ ;
10471074 } catch ( e ) {
0 commit comments