Skip to content

Commit be64bc0

Browse files
authored
Merge pull request #931 from objectstack-ai/copilot/fix-http-404-api-routes
2 parents 878d0ef + 408f23a commit be64bc0

15 files changed

Lines changed: 355 additions & 293 deletions

File tree

packages/adapters/express/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# @objectstack/express
22

3+
## 3.2.8
4+
5+
### Patch Changes
6+
7+
- fix: unified catch-all dispatch pattern — `createExpressRouter()` now delegates all non-framework-specific routes to `HttpDispatcher.dispatch()`, automatically supporting packages, analytics, automation, i18n, ui, openapi, custom endpoints, and any future routes
8+
- Only auth (service check), storage (file upload), GraphQL (raw result), and discovery (response wrapper) remain as explicit routes
9+
310
## 3.2.7
411

512
### Patch Changes

packages/adapters/express/src/index.ts

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ interface AuthService {
1717

1818
/**
1919
* Creates an Express Router with all ObjectStack route dispatchers.
20-
* Provides Auth, GraphQL, Metadata, Data, and Storage routes.
20+
*
21+
* Only routes that need framework-specific handling (auth service, storage
22+
* file upload, GraphQL raw result, discovery wrapper) are registered explicitly.
23+
* All other routes are handled by a catch-all that delegates to
24+
* `HttpDispatcher.dispatch()`, making the adapter automatically support
25+
* new routes added to the dispatcher.
2126
*
2227
* @example
2328
* ```ts
@@ -70,12 +75,14 @@ export function createExpressRouter(options: ExpressAdapterOptions): Router {
7075
});
7176
};
7277

78+
// ─── Explicit routes (framework-specific handling required) ────────────────
79+
7380
// --- Discovery ---
7481
router.get('/', async (_req: Request, res: Response) => {
7582
res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
7683
});
7784

78-
// --- Auth ---
85+
// --- Auth (needs auth service integration) ---
7986
router.all('/auth/{*path}', async (req: Request, res: Response) => {
8087
try {
8188
const path = (req.params as any).path;
@@ -129,7 +136,7 @@ export function createExpressRouter(options: ExpressAdapterOptions): Router {
129136
}
130137
});
131138

132-
// --- GraphQL ---
139+
// --- GraphQL (returns raw result, not HttpDispatcherResult) ---
133140
router.post('/graphql', async (req: Request, res: Response) => {
134141
try {
135142
const result = await dispatcher.handleGraphQL(req.body, { request: req });
@@ -139,50 +146,28 @@ export function createExpressRouter(options: ExpressAdapterOptions): Router {
139146
}
140147
});
141148

142-
// --- Metadata ---
143-
router.all('/meta/{*path}', async (req: Request, res: Response) => {
144-
try {
145-
const subPath = '/' + (req.params as any).path;
146-
const method = req.method;
147-
const body = (method === 'PUT' || method === 'POST') ? req.body : undefined;
148-
const result = await dispatcher.handleMetadata(subPath, { request: req }, method, body);
149-
return sendResult(result, res);
150-
} catch (err: any) {
151-
return errorResponse(err, res);
152-
}
153-
});
154-
155-
router.all('/meta', async (req: Request, res: Response) => {
156-
try {
157-
const method = req.method;
158-
const body = (method === 'PUT' || method === 'POST') ? req.body : undefined;
159-
const result = await dispatcher.handleMetadata('', { request: req }, method, body);
160-
return sendResult(result, res);
161-
} catch (err: any) {
162-
return errorResponse(err, res);
163-
}
164-
});
165-
166-
// --- Data ---
167-
router.all('/data/{*path}', async (req: Request, res: Response) => {
149+
// --- Storage (needs file upload handling) ---
150+
router.all('/storage/{*path}', async (req: Request, res: Response) => {
168151
try {
169152
const subPath = '/' + (req.params as any).path;
170153
const method = req.method;
171-
const body = (method === 'POST' || method === 'PATCH') ? req.body : {};
172-
const result = await dispatcher.handleData(subPath, method, body, req.query, { request: req });
154+
const file = (req as any).file || (req as any).files?.file;
155+
const result = await dispatcher.handleStorage(subPath, method, file, { request: req });
173156
return sendResult(result, res);
174157
} catch (err: any) {
175158
return errorResponse(err, res);
176159
}
177160
});
178161

179-
// --- Storage ---
180-
router.all('/storage/{*path}', async (req: Request, res: Response) => {
162+
// ─── Catch-all: delegate to dispatcher.dispatch() ─────────────────────────
163+
// Handles meta, data, packages, analytics, automation, i18n, ui, openapi,
164+
// custom API endpoints, and any future routes added to HttpDispatcher.
165+
router.all('/{*path}', async (req: Request, res: Response) => {
181166
try {
182167
const subPath = '/' + (req.params as any).path;
183168
const method = req.method;
184-
const file = (req as any).file || (req as any).files?.file;
185-
const result = await dispatcher.handleStorage(subPath, method, file, { request: req });
169+
const body = (method === 'POST' || method === 'PUT' || method === 'PATCH') ? req.body : undefined;
170+
const result = await dispatcher.dispatch(method, subPath, body, req.query, { request: req, response: res });
186171
return sendResult(result, res);
187172
} catch (err: any) {
188173
return errorResponse(err, res);

packages/adapters/fastify/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# @objectstack/fastify
22

3+
## 3.2.8
4+
5+
### Patch Changes
6+
7+
- fix: unified catch-all dispatch pattern — `objectStackPlugin()` now delegates all non-framework-specific routes to `HttpDispatcher.dispatch()`, automatically supporting packages, analytics, automation, i18n, ui, openapi, custom endpoints, and any future routes
8+
- Only auth (service check), storage (file upload), GraphQL (raw result), and discovery (response wrapper) remain as explicit routes
9+
310
## 3.2.7
411

512
### Patch Changes

packages/adapters/fastify/src/index.ts

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ interface AuthService {
1717

1818
/**
1919
* Registers ObjectStack routes as a Fastify plugin.
20-
* Provides Auth, GraphQL, Metadata, Data, and Storage routes.
20+
*
21+
* Only routes that need framework-specific handling (auth service, storage
22+
* file upload, GraphQL raw result, discovery wrapper) are registered explicitly.
23+
* All other routes are handled by a catch-all that delegates to
24+
* `HttpDispatcher.dispatch()`, making the adapter automatically support
25+
* new routes added to the dispatcher.
2126
*
2227
* @example
2328
* ```ts
@@ -68,6 +73,8 @@ export async function objectStackPlugin(fastify: FastifyInstance, options: Fasti
6873
});
6974
};
7075

76+
// ─── Explicit routes (framework-specific handling required) ────────────────
77+
7178
// --- Discovery ---
7279
fastify.get(`${prefix}`, async (_request: FastifyRequest, reply: FastifyReply) => {
7380
return reply.send({ data: await dispatcher.getDiscoveryInfo(prefix) });
@@ -78,7 +85,7 @@ export async function objectStackPlugin(fastify: FastifyInstance, options: Fasti
7885
return reply.redirect(prefix);
7986
});
8087

81-
// --- Auth ---
88+
// --- Auth (needs auth service integration) ---
8289
fastify.all(`${prefix}/auth/*`, async (request: FastifyRequest, reply: FastifyReply) => {
8390
try {
8491
const path = request.url.substring(`${prefix}/auth/`.length).split('?')[0];
@@ -132,7 +139,7 @@ export async function objectStackPlugin(fastify: FastifyInstance, options: Fasti
132139
}
133140
});
134141

135-
// --- GraphQL ---
142+
// --- GraphQL (returns raw result, not HttpDispatcherResult) ---
136143
fastify.post(`${prefix}/graphql`, async (request: FastifyRequest, reply: FastifyReply) => {
137144
try {
138145
const result = await dispatcher.handleGraphQL(request.body as any, { request: request.raw });
@@ -142,53 +149,30 @@ export async function objectStackPlugin(fastify: FastifyInstance, options: Fasti
142149
}
143150
});
144151

145-
// --- Metadata ---
146-
fastify.all(`${prefix}/meta/*`, async (request: FastifyRequest, reply: FastifyReply) => {
147-
try {
148-
const urlPath = request.url.split('?')[0];
149-
const subPath = urlPath.substring(`${prefix}/meta`.length);
150-
const method = request.method;
151-
const body = (method === 'PUT' || method === 'POST') ? request.body : undefined;
152-
const result = await dispatcher.handleMetadata(subPath, { request: request.raw }, method, body);
153-
return sendResult(result, reply);
154-
} catch (err: any) {
155-
return errorResponse(err, reply);
156-
}
157-
});
158-
159-
fastify.all(`${prefix}/meta`, async (request: FastifyRequest, reply: FastifyReply) => {
160-
try {
161-
const method = request.method;
162-
const body = (method === 'PUT' || method === 'POST') ? request.body : undefined;
163-
const result = await dispatcher.handleMetadata('', { request: request.raw }, method, body);
164-
return sendResult(result, reply);
165-
} catch (err: any) {
166-
return errorResponse(err, reply);
167-
}
168-
});
169-
170-
// --- Data ---
171-
fastify.all(`${prefix}/data/*`, async (request: FastifyRequest, reply: FastifyReply) => {
152+
// --- Storage (needs file upload handling) ---
153+
fastify.all(`${prefix}/storage/*`, async (request: FastifyRequest, reply: FastifyReply) => {
172154
try {
173155
const urlPath = request.url.split('?')[0];
174-
const subPath = urlPath.substring(`${prefix}/data`.length);
156+
const subPath = urlPath.substring(`${prefix}/storage`.length);
175157
const method = request.method;
176-
const body = (method === 'POST' || method === 'PATCH') ? request.body : {};
177-
const result = await dispatcher.handleData(subPath, method, body, request.query, { request: request.raw });
158+
const file = (request as any).file || (request.body as any)?.file;
159+
const result = await dispatcher.handleStorage(subPath, method, file, { request: request.raw });
178160
return sendResult(result, reply);
179161
} catch (err: any) {
180162
return errorResponse(err, reply);
181163
}
182164
});
183165

184-
// --- Storage ---
185-
fastify.all(`${prefix}/storage/*`, async (request: FastifyRequest, reply: FastifyReply) => {
166+
// ─── Catch-all: delegate to dispatcher.dispatch() ─────────────────────────
167+
// Handles meta, data, packages, analytics, automation, i18n, ui, openapi,
168+
// custom API endpoints, and any future routes added to HttpDispatcher.
169+
fastify.all(`${prefix}/*`, async (request: FastifyRequest, reply: FastifyReply) => {
186170
try {
187171
const urlPath = request.url.split('?')[0];
188-
const subPath = urlPath.substring(`${prefix}/storage`.length);
172+
const subPath = urlPath.substring(prefix.length);
189173
const method = request.method;
190-
const file = (request as any).file || (request.body as any)?.file;
191-
const result = await dispatcher.handleStorage(subPath, method, file, { request: request.raw });
174+
const body = (method === 'POST' || method === 'PUT' || method === 'PATCH') ? request.body : undefined;
175+
const result = await dispatcher.dispatch(method, subPath, body, request.query, { request: request.raw });
192176
return sendResult(result, reply);
193177
} catch (err: any) {
194178
return errorResponse(err, reply);

packages/adapters/hono/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# @objectstack/hono
22

3+
## 3.2.8
4+
5+
### Patch Changes
6+
7+
- fix: unified catch-all dispatch pattern — `createHonoApp()` now delegates all non-framework-specific routes to `HttpDispatcher.dispatch()`, automatically supporting packages, analytics, automation, i18n, ui, openapi, custom endpoints, and any future routes
8+
- fix: resolves 404 errors for `/api/v1/meta` and `/api/v1/packages` after Vercel deployment
9+
- Only auth (service check), storage (formData), GraphQL (raw result), and discovery (response wrapper) remain as explicit routes
10+
- Added comprehensive tests for the catch-all dispatch pattern
11+
312
## 3.2.7
413

514
### Patch Changes

0 commit comments

Comments
 (0)