Skip to content

Commit 2525ef3

Browse files
authored
feat(server): migrate elysia adapter (#340)
* feat(server): migrate elysia adapter * addressing pr comments
1 parent 5699e5b commit 2525ef3

File tree

12 files changed

+394
-4
lines changed

12 files changed

+394
-4
lines changed

packages/server/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
"next": "^15.0.0",
7474
"fastify": "^5.6.1",
7575
"fastify-plugin": "^5.1.0",
76+
"elysia": "^1.3.1",
7677
"supertest": "^7.1.4",
7778
"zod": "~3.25.0"
7879
},
@@ -81,6 +82,7 @@
8182
"next": "^15.0.0",
8283
"fastify": "^5.0.0",
8384
"fastify-plugin": "^5.0.0",
85+
"elysia": "^1.3.0",
8486
"zod": "catalog:"
8587
},
8688
"peerDependenciesMeta": {
@@ -95,6 +97,9 @@
9597
},
9698
"fastify-plugin": {
9799
"optional": true
100+
},
101+
"elysia": {
102+
"optional": true
98103
}
99104
}
100105
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import type { ClientContract } from '@zenstackhq/orm';
2+
import type { SchemaDef } from '@zenstackhq/orm/schema';
3+
import { Elysia, type Context as ElysiaContext } from 'elysia';
4+
import { log } from '../../api/utils';
5+
import type { CommonAdapterOptions } from '../common';
6+
7+
/**
8+
* Options for initializing an Elysia middleware.
9+
*/
10+
export interface ElysiaOptions<Schema extends SchemaDef> extends CommonAdapterOptions<Schema> {
11+
/**
12+
* Callback method for getting a ZenStackClient instance for the given request context.
13+
*/
14+
getClient: (context: ElysiaContext) => Promise<ClientContract<Schema>> | ClientContract<Schema>;
15+
16+
/**
17+
* Optional base path to strip from the request path before passing to the API handler.
18+
*/
19+
basePath?: string;
20+
}
21+
22+
/**
23+
* Creates an Elysia middleware handler for ZenStack.
24+
* This handler provides automatic CRUD APIs through Elysia's routing system.
25+
*/
26+
export function createElysiaHandler<Schema extends SchemaDef>(options: ElysiaOptions<Schema>) {
27+
return async (app: Elysia) => {
28+
app.all('/*', async (ctx: ElysiaContext) => {
29+
const { request, body, set } = ctx;
30+
const client = await options.getClient(ctx);
31+
if (!client) {
32+
set.status = 500;
33+
return {
34+
message: 'unable to get ZenStackClient from request context',
35+
};
36+
}
37+
38+
const url = new URL(request.url);
39+
const query = Object.fromEntries(url.searchParams);
40+
let path = url.pathname;
41+
42+
if (options.basePath && path.startsWith(options.basePath)) {
43+
path = path.slice(options.basePath.length);
44+
if (!path.startsWith('/')) {
45+
path = '/' + path;
46+
}
47+
}
48+
49+
if (!path || path === '/') {
50+
set.status = 400;
51+
return {
52+
message: 'missing path parameter',
53+
};
54+
}
55+
56+
try {
57+
const r = await options.apiHandler.handleRequest({
58+
method: request.method,
59+
path,
60+
query,
61+
requestBody: body,
62+
client,
63+
});
64+
65+
set.status = r.status;
66+
return r.body;
67+
} catch (err) {
68+
set.status = 500;
69+
log(options.apiHandler.log, 'error', `An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`);
70+
return {
71+
message: 'An internal server error occurred',
72+
};
73+
}
74+
});
75+
76+
return app;
77+
};
78+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './handler';

packages/server/src/adapter/express/middleware.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ClientContract } from '@zenstackhq/orm';
22
import type { SchemaDef } from '@zenstackhq/orm/schema';
33
import type { Handler, Request, Response } from 'express';
4+
import { log } from '../../api/utils';
45
import type { CommonAdapterOptions } from '../common';
56

67
/**
@@ -70,7 +71,8 @@ const factory = <Schema extends SchemaDef>(options: MiddlewareOptions<Schema>):
7071
if (sendResponse === false) {
7172
throw err;
7273
}
73-
return response.status(500).json({ message: `An unhandled error occurred: ${err}` });
74+
log(options.apiHandler.log, 'error', `An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`);
75+
return response.status(500).json({ message: `An internal server error occurred` });
7476
}
7577
};
7678
};

packages/server/src/adapter/fastify/plugin.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ClientContract } from '@zenstackhq/orm';
22
import type { SchemaDef } from '@zenstackhq/orm/schema';
33
import type { FastifyPluginCallback, FastifyReply, FastifyRequest } from 'fastify';
44
import fp from 'fastify-plugin';
5+
import { log } from '../../api/utils';
56
import type { CommonAdapterOptions } from '../common';
67

78
/**
@@ -43,7 +44,8 @@ const pluginHandler: FastifyPluginCallback<PluginOptions<SchemaDef>> = (fastify,
4344
});
4445
reply.status(response.status).send(response.body);
4546
} catch (err) {
46-
reply.status(500).send({ message: `An unhandled error occurred: ${err}` });
47+
log(options.apiHandler.log, 'error', `An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`);
48+
reply.status(500).send({ message: `An internal server error occurred` });
4749
}
4850

4951
return reply;

packages/server/src/adapter/next/app-route-handler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { SchemaDef } from '@zenstackhq/orm/schema';
22
import { NextRequest, NextResponse } from 'next/server';
33
import type { AppRouteRequestHandlerOptions } from '.';
4+
import { log } from '../../api/utils';
45

56
type Context = { params: Promise<{ path: string[] }> };
67

@@ -58,7 +59,8 @@ export default function factory<Schema extends SchemaDef>(
5859
});
5960
return NextResponse.json(r.body, { status: r.status });
6061
} catch (err) {
61-
return NextResponse.json({ message: `An unhandled error occurred: ${err}` }, { status: 500 });
62+
log(options.apiHandler.log, 'error', `An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`);
63+
return NextResponse.json({ message: 'An internal server error occurred' }, { status: 500 });
6264
}
6365
};
6466
}

packages/server/src/adapter/next/pages-route-handler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { SchemaDef } from '@zenstackhq/orm/schema';
22
import type { NextApiRequest, NextApiResponse } from 'next';
33
import type { PageRouteRequestHandlerOptions } from '.';
4+
import { log } from '../../api/utils';
45

56
/**
67
* Creates a Next.js API endpoint "pages" router request handler that handles ZenStack CRUD requests.
@@ -34,7 +35,8 @@ export default function factory<Schema extends SchemaDef>(
3435
});
3536
res.status(r.status).send(r.body);
3637
} catch (err) {
37-
res.status(500).send({ message: `An unhandled error occurred: ${err}` });
38+
log(options.apiHandler.log, 'error', `An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`);
39+
res.status(500).send({ message: 'An internal server error occurred' });
3840
}
3941
};
4042
}

packages/server/src/api/rest/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,10 @@ export class RestApiHandler<Schema extends SchemaDef> implements ApiHandler<Sche
285285
return this.options.schema;
286286
}
287287

288+
get log(): LogConfig | undefined {
289+
return this.options.log;
290+
}
291+
288292
private buildUrlPatternMap(urlSegmentNameCharset: string): Record<UrlPatterns, UrlPattern> {
289293
const options = { segmentValueCharset: urlSegmentNameCharset };
290294

packages/server/src/api/rpc/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ export class RPCApiHandler<Schema extends SchemaDef> implements ApiHandler<Schem
3838
return this.options.schema;
3939
}
4040

41+
get log(): LogConfig | undefined {
42+
return this.options.log;
43+
}
44+
4145
async handleRequest({ client, method, path, query, requestBody }: RequestContext<Schema>): Promise<Response> {
4246
const parts = path.split('/').filter((p) => !!p);
4347
const op = parts.pop();

packages/server/src/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ export interface ApiHandler<Schema extends SchemaDef> {
7070
*/
7171
get schema(): Schema;
7272

73+
/**
74+
* Logging configuration.
75+
*/
76+
get log(): LogConfig | undefined;
77+
7378
/**
7479
* Handle an API request.
7580
*/

0 commit comments

Comments
 (0)