33import { IHttpServer } from '@objectstack/core' ;
44import type { PackageService } from '@objectstack/service-package' ;
55
6+ /**
7+ * Options for package route registration.
8+ */
9+ export interface PackageRoutesOptions {
10+ /**
11+ * Protocol service (ObjectStackProtocol) — provides access to in-memory
12+ * SchemaRegistry packages loaded via defineStack()/AppPlugin at boot time.
13+ */
14+ protocol ?: { getMetaItems ?( req : { type : string } ) : Promise < { items : any [ ] } > } ;
15+ }
16+
617/**
718 * Register package management API routes
819 *
920 * Provides endpoints for publishing, retrieving, and managing packages.
1021 * Routes:
1122 * - POST /api/v1/packages - Publish a package
12- * - GET /api/v1/packages - List all packages
23+ * - GET /api/v1/packages - List all packages (merges registry + database)
1324 * - GET /api/v1/packages/:id - Get a specific package
1425 * - DELETE /api/v1/packages/:id - Delete a package
1526 */
16- export function registerPackageRoutes ( server : IHttpServer , packageService : PackageService , basePath : string = '/api/v1' ) {
27+ export function registerPackageRoutes (
28+ server : IHttpServer ,
29+ packageService : PackageService ,
30+ basePath : string = '/api/v1' ,
31+ options : PackageRoutesOptions = { } ,
32+ ) {
1733 const packagesPath = `${ basePath } /packages` ;
1834
1935 // POST /api/v1/packages - Publish a package
@@ -51,11 +67,54 @@ export function registerPackageRoutes(server: IHttpServer, packageService: Packa
5167 }
5268 } ) ;
5369
54- // GET /api/v1/packages - List all packages (latest versions )
70+ // GET /api/v1/packages - List all packages (merges registry + database )
5571 server . get ( packagesPath , async ( _req , res ) => {
5672 try {
57- const packages = await packageService . list ( ) ;
58- res . json ( { packages } ) ;
73+ // Merge two sources:
74+ // 1. Registry packages (in-memory, loaded at boot via defineStack/AppPlugin)
75+ // 2. Database packages (published via POST /packages)
76+ const packagesMap = new Map < string , any > ( ) ;
77+
78+ // Registry packages (via protocol service → SchemaRegistry)
79+ if ( options . protocol && typeof options . protocol . getMetaItems === 'function' ) {
80+ try {
81+ const result = await options . protocol . getMetaItems ( { type : 'package' } ) ;
82+ if ( result ?. items ) {
83+ for ( const item of result . items ) {
84+ const id = item . manifest ?. id || item . id ;
85+ if ( id ) {
86+ packagesMap . set ( id , {
87+ ...item ,
88+ source : 'registry' ,
89+ } ) ;
90+ }
91+ }
92+ }
93+ } catch {
94+ // Protocol unavailable — continue with database only
95+ }
96+ }
97+
98+ // Database packages (published artifacts)
99+ try {
100+ const dbPackages = await packageService . list ( ) ;
101+ for ( const pkg of dbPackages ) {
102+ const id = pkg . manifest ?. id || pkg . id ;
103+ if ( id ) {
104+ // Database entry takes precedence (has richer metadata from publish)
105+ packagesMap . set ( id , {
106+ ...packagesMap . get ( id ) ,
107+ ...pkg ,
108+ source : packagesMap . has ( id ) ? 'both' : 'database' ,
109+ } ) ;
110+ }
111+ }
112+ } catch {
113+ // Database query failed — continue with registry-only packages
114+ }
115+
116+ const packages = Array . from ( packagesMap . values ( ) ) ;
117+ res . json ( { packages, total : packages . length } ) ;
59118 } catch ( error ) {
60119 res . status ( 500 ) . json ( { error : ( error as Error ) . message } ) ;
61120 }
@@ -67,14 +126,30 @@ export function registerPackageRoutes(server: IHttpServer, packageService: Packa
67126 const packageId = req . params . id ;
68127 const version = req . query ?. version || 'latest' ;
69128
129+ // Try database first (richer data from publish)
70130 const pkg = await packageService . get ( packageId , version ) ;
71-
72- if ( ! pkg ) {
73- res . status ( 404 ) . json ( { error : 'Package not found' } ) ;
131+ if ( pkg ) {
132+ res . json ( { package : { ...pkg , source : 'database' } } ) ;
74133 return ;
75134 }
76135
77- res . json ( { package : pkg } ) ;
136+ // Fall back to registry (in-memory loaded packages)
137+ if ( options . protocol && typeof options . protocol . getMetaItems === 'function' ) {
138+ try {
139+ const result = await options . protocol . getMetaItems ( { type : 'package' } ) ;
140+ const match = result ?. items ?. find ( ( item : any ) =>
141+ ( item . manifest ?. id || item . id ) === packageId
142+ ) ;
143+ if ( match ) {
144+ res . json ( { package : { ...match , source : 'registry' } } ) ;
145+ return ;
146+ }
147+ } catch {
148+ // Protocol unavailable
149+ }
150+ }
151+
152+ res . status ( 404 ) . json ( { error : 'Package not found' } ) ;
78153 } catch ( error ) {
79154 res . status ( 500 ) . json ( { error : ( error as Error ) . message } ) ;
80155 }
0 commit comments