Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/adapters/express/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @objectstack/express

## 3.2.8

### Patch Changes

- 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
- Only auth (service check), storage (file upload), GraphQL (raw result), and discovery (response wrapper) remain as explicit routes

## 3.2.7

### Patch Changes
Expand Down
55 changes: 20 additions & 35 deletions packages/adapters/express/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ interface AuthService {

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

// ─── Explicit routes (framework-specific handling required) ────────────────

// --- Discovery ---
router.get('/', async (_req: Request, res: Response) => {
res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
});

// --- Auth ---
// --- Auth (needs auth service integration) ---
router.all('/auth/{*path}', async (req: Request, res: Response) => {
try {
const path = (req.params as any).path;
Expand Down Expand Up @@ -129,7 +136,7 @@ export function createExpressRouter(options: ExpressAdapterOptions): Router {
}
});

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

// --- Metadata ---
router.all('/meta/{*path}', async (req: Request, res: Response) => {
try {
const subPath = '/' + (req.params as any).path;
const method = req.method;
const body = (method === 'PUT' || method === 'POST') ? req.body : undefined;
const result = await dispatcher.handleMetadata(subPath, { request: req }, method, body);
return sendResult(result, res);
} catch (err: any) {
return errorResponse(err, res);
}
});

router.all('/meta', async (req: Request, res: Response) => {
try {
const method = req.method;
const body = (method === 'PUT' || method === 'POST') ? req.body : undefined;
const result = await dispatcher.handleMetadata('', { request: req }, method, body);
return sendResult(result, res);
} catch (err: any) {
return errorResponse(err, res);
}
});

// --- Data ---
router.all('/data/{*path}', async (req: Request, res: Response) => {
// --- Storage (needs file upload handling) ---
router.all('/storage/{*path}', async (req: Request, res: Response) => {
try {
const subPath = '/' + (req.params as any).path;
const method = req.method;
const body = (method === 'POST' || method === 'PATCH') ? req.body : {};
const result = await dispatcher.handleData(subPath, method, body, req.query, { request: req });
const file = (req as any).file || (req as any).files?.file;
const result = await dispatcher.handleStorage(subPath, method, file, { request: req });
return sendResult(result, res);
} catch (err: any) {
return errorResponse(err, res);
}
});

// --- Storage ---
router.all('/storage/{*path}', async (req: Request, res: Response) => {
// ─── Catch-all: delegate to dispatcher.dispatch() ─────────────────────────
// Handles meta, data, packages, analytics, automation, i18n, ui, openapi,
// custom API endpoints, and any future routes added to HttpDispatcher.
router.all('/{*path}', async (req: Request, res: Response) => {
try {
const subPath = '/' + (req.params as any).path;
const method = req.method;
const file = (req as any).file || (req as any).files?.file;
const result = await dispatcher.handleStorage(subPath, method, file, { request: req });
const body = (method === 'POST' || method === 'PUT' || method === 'PATCH') ? req.body : undefined;
const result = await dispatcher.dispatch(method, subPath, body, req.query, { request: req, response: res });
return sendResult(result, res);
} catch (err: any) {
return errorResponse(err, res);
Expand Down
7 changes: 7 additions & 0 deletions packages/adapters/fastify/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @objectstack/fastify

## 3.2.8

### Patch Changes

- 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
- Only auth (service check), storage (file upload), GraphQL (raw result), and discovery (response wrapper) remain as explicit routes

## 3.2.7

### Patch Changes
Expand Down
60 changes: 22 additions & 38 deletions packages/adapters/fastify/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ interface AuthService {

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

// ─── Explicit routes (framework-specific handling required) ────────────────

// --- Discovery ---
fastify.get(`${prefix}`, async (_request: FastifyRequest, reply: FastifyReply) => {
return reply.send({ data: await dispatcher.getDiscoveryInfo(prefix) });
Expand All @@ -78,7 +85,7 @@ export async function objectStackPlugin(fastify: FastifyInstance, options: Fasti
return reply.redirect(prefix);
});

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

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

// --- Metadata ---
fastify.all(`${prefix}/meta/*`, async (request: FastifyRequest, reply: FastifyReply) => {
try {
const urlPath = request.url.split('?')[0];
const subPath = urlPath.substring(`${prefix}/meta`.length);
const method = request.method;
const body = (method === 'PUT' || method === 'POST') ? request.body : undefined;
const result = await dispatcher.handleMetadata(subPath, { request: request.raw }, method, body);
return sendResult(result, reply);
} catch (err: any) {
return errorResponse(err, reply);
}
});

fastify.all(`${prefix}/meta`, async (request: FastifyRequest, reply: FastifyReply) => {
try {
const method = request.method;
const body = (method === 'PUT' || method === 'POST') ? request.body : undefined;
const result = await dispatcher.handleMetadata('', { request: request.raw }, method, body);
return sendResult(result, reply);
} catch (err: any) {
return errorResponse(err, reply);
}
});

// --- Data ---
fastify.all(`${prefix}/data/*`, async (request: FastifyRequest, reply: FastifyReply) => {
// --- Storage (needs file upload handling) ---
fastify.all(`${prefix}/storage/*`, async (request: FastifyRequest, reply: FastifyReply) => {
try {
const urlPath = request.url.split('?')[0];
const subPath = urlPath.substring(`${prefix}/data`.length);
const subPath = urlPath.substring(`${prefix}/storage`.length);
const method = request.method;
const body = (method === 'POST' || method === 'PATCH') ? request.body : {};
const result = await dispatcher.handleData(subPath, method, body, request.query, { request: request.raw });
const file = (request as any).file || (request.body as any)?.file;
const result = await dispatcher.handleStorage(subPath, method, file, { request: request.raw });
return sendResult(result, reply);
} catch (err: any) {
return errorResponse(err, reply);
}
});

// --- Storage ---
fastify.all(`${prefix}/storage/*`, async (request: FastifyRequest, reply: FastifyReply) => {
// ─── Catch-all: delegate to dispatcher.dispatch() ─────────────────────────
// Handles meta, data, packages, analytics, automation, i18n, ui, openapi,
// custom API endpoints, and any future routes added to HttpDispatcher.
fastify.all(`${prefix}/*`, async (request: FastifyRequest, reply: FastifyReply) => {
try {
const urlPath = request.url.split('?')[0];
const subPath = urlPath.substring(`${prefix}/storage`.length);
const subPath = urlPath.substring(prefix.length);
const method = request.method;
const file = (request as any).file || (request.body as any)?.file;
const result = await dispatcher.handleStorage(subPath, method, file, { request: request.raw });
const body = (method === 'POST' || method === 'PUT' || method === 'PATCH') ? request.body : undefined;
const result = await dispatcher.dispatch(method, subPath, body, request.query, { request: request.raw });
return sendResult(result, reply);
Comment on lines +169 to 176
} catch (err: any) {
return errorResponse(err, reply);
Expand Down
9 changes: 9 additions & 0 deletions packages/adapters/hono/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# @objectstack/hono

## 3.2.8

### Patch Changes

- 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
- fix: resolves 404 errors for `/api/v1/meta` and `/api/v1/packages` after Vercel deployment
- Only auth (service check), storage (formData), GraphQL (raw result), and discovery (response wrapper) remain as explicit routes
- Added comprehensive tests for the catch-all dispatch pattern

Comment on lines +3 to +11
## 3.2.7

### Patch Changes
Expand Down
Loading
Loading