Skip to content

Commit f9cbedc

Browse files
authored
feat(server): migrate tanstack-start adapter (#344)
* feat(server): migrate tanstack-start adapter * prettier format
1 parent fe5f61d commit f9cbedc

File tree

32 files changed

+3076
-1633
lines changed

32 files changed

+3076
-1633
lines changed

packages/cli/src/actions/migrate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ async function runReset(prismaSchemaFile: string, options: ResetOptions) {
8686
'prisma migrate reset',
8787
` --schema "${prismaSchemaFile}"`,
8888
' --skip-generate',
89-
options.force ? ' --force' : ''
89+
options.force ? ' --force' : '',
9090
].join('');
9191

9292
await execPackage(cmd);

packages/server/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@
108108
"types": "./dist/sveltekit.d.cts",
109109
"default": "./dist/sveltekit.cjs"
110110
}
111+
},
112+
"./tanstack-start": {
113+
"import": {
114+
"types": "./dist/tanstack-start.d.ts",
115+
"default": "./dist/tanstack-start.js"
116+
},
117+
"require": {
118+
"types": "./dist/tanstack-start.d.cts",
119+
"default": "./dist/tanstack-start.cjs"
120+
}
111121
}
112122
},
113123
"dependencies": {
Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { SchemaDef } from "@zenstackhq/orm/schema";
2-
import { log } from "../api/utils";
3-
import type { ApiHandler, LogConfig } from "../types";
1+
import type { SchemaDef } from '@zenstackhq/orm/schema';
2+
import { log } from '../api/utils';
3+
import type { ApiHandler, LogConfig } from '../types';
44

55
/**
66
* Options common to all adapters
@@ -13,5 +13,9 @@ export interface CommonAdapterOptions<Schema extends SchemaDef> {
1313
}
1414

1515
export function logInternalError(logger: LogConfig | undefined, err: unknown) {
16-
log(logger, 'error', `An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`);
17-
}
16+
log(
17+
logger,
18+
'error',
19+
`An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`,
20+
);
21+
}
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
export { ZenStackFastifyPlugin, type FastifyPluginOptions } from './plugin';
2-

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { logInternalError, type CommonAdapterOptions } from '../common';
88
* Fastify plugin options
99
*/
1010
export interface FastifyPluginOptions<Schema extends SchemaDef> extends CommonAdapterOptions<Schema> {
11-
1211
/**
1312
* Url prefix, e.g.: /api
1413
*/
@@ -17,7 +16,10 @@ export interface FastifyPluginOptions<Schema extends SchemaDef> extends CommonAd
1716
/**
1817
* Callback for getting a ZenStackClient for the given request
1918
*/
20-
getClient: (request: FastifyRequest, reply: FastifyReply) => ClientContract<Schema> | Promise<ClientContract<Schema>>;
19+
getClient: (
20+
request: FastifyRequest,
21+
reply: FastifyReply,
22+
) => ClientContract<Schema> | Promise<ClientContract<Schema>>;
2123
}
2224

2325
/**

packages/server/src/adapter/nuxt/handler.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
import type { ClientContract } from '@zenstackhq/orm';
22
import type { SchemaDef } from '@zenstackhq/orm/schema';
3-
import {
4-
H3Event,
5-
defineEventHandler,
6-
getQuery,
7-
getRouterParams,
8-
readBody,
9-
type EventHandlerRequest
10-
} from 'h3';
3+
import { H3Event, defineEventHandler, getQuery, getRouterParams, readBody, type EventHandlerRequest } from 'h3';
114
import { setResponseStatus } from 'nuxt/app';
125
import { logInternalError, type CommonAdapterOptions } from '../common';
136

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import type { SchemaDef } from '@zenstackhq/orm/schema';
2+
import type { TanStackStartOptions } from '.';
3+
import { logInternalError } from '../common';
4+
5+
/**
6+
* Creates a TanStack Start server route handler which encapsulates ZenStack CRUD operations.
7+
*
8+
* @param options Options for initialization
9+
* @returns A TanStack Start server route handler
10+
*/
11+
export default function factory<Schema extends SchemaDef>(
12+
options: TanStackStartOptions<Schema>,
13+
): ({ request, params }: { request: Request; params: Record<string, string> }) => Promise<Response> {
14+
return async ({ request, params }: { request: Request; params: Record<string, string> }) => {
15+
const client = await options.getClient(request, params);
16+
if (!client) {
17+
return new Response(JSON.stringify({ message: 'unable to get ZenStackClient from request context' }), {
18+
status: 500,
19+
headers: {
20+
'Content-Type': 'application/json',
21+
},
22+
});
23+
}
24+
25+
const url = new URL(request.url);
26+
const query = Object.fromEntries(url.searchParams);
27+
28+
// Extract path from params._splat for catch-all routes
29+
const path = params['_splat'];
30+
31+
if (!path) {
32+
return new Response(JSON.stringify({ message: 'missing path parameter' }), {
33+
status: 400,
34+
headers: {
35+
'Content-Type': 'application/json',
36+
},
37+
});
38+
}
39+
40+
let requestBody: unknown;
41+
if (request.body) {
42+
try {
43+
requestBody = await request.json();
44+
} catch {
45+
// noop
46+
}
47+
}
48+
49+
try {
50+
const r = await options.apiHandler.handleRequest({
51+
method: request.method!,
52+
path,
53+
query,
54+
requestBody,
55+
client,
56+
});
57+
return new Response(JSON.stringify(r.body), {
58+
status: r.status,
59+
headers: {
60+
'Content-Type': 'application/json',
61+
},
62+
});
63+
} catch (err) {
64+
logInternalError(options.apiHandler.log, err);
65+
return new Response(JSON.stringify({ message: 'An internal server error occurred' }), {
66+
status: 500,
67+
headers: {
68+
'Content-Type': 'application/json',
69+
},
70+
});
71+
}
72+
};
73+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import type { ClientContract } from '@zenstackhq/orm';
2+
import type { SchemaDef } from '@zenstackhq/orm/schema';
3+
import type { CommonAdapterOptions } from '../common';
4+
import { default as Handler } from './handler';
5+
6+
/**
7+
* Options for initializing a TanStack Start server route handler.
8+
*/
9+
export interface TanStackStartOptions<Schema extends SchemaDef> extends CommonAdapterOptions<Schema> {
10+
/**
11+
* Callback method for getting a ZenStackClient instance for the given request and params.
12+
*/
13+
getClient: (
14+
request: Request,
15+
params: Record<string, string>,
16+
) => ClientContract<Schema> | Promise<ClientContract<Schema>>;
17+
}
18+
19+
/**
20+
* Creates a TanStack Start server route handler.
21+
* @see https://zenstack.dev/docs/reference/server-adapters/tanstack-start
22+
*/
23+
export function TanStackStartHandler<Schema extends SchemaDef>(
24+
options: TanStackStartOptions<Schema>,
25+
): ReturnType<typeof Handler> {
26+
return Handler(options);
27+
}
28+
29+
export default TanStackStartHandler;

packages/server/test/adapter/elysia.test.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ describe('Elysia adapter tests - rpc handler', () => {
1111
const client = await createTestClient(schema);
1212

1313
const handler = await createElysiaApp(
14-
createElysiaHandler({ getClient: () => client, basePath: '/api', apiHandler: new RPCApiHandler({ schema: client.schema }) })
14+
createElysiaHandler({
15+
getClient: () => client,
16+
basePath: '/api',
17+
apiHandler: new RPCApiHandler({ schema: client.schema }),
18+
}),
1519
);
1620

1721
let r = await handler(makeRequest('GET', makeUrl('/api/post/findMany', { where: { id: { equals: '1' } } })));
@@ -31,7 +35,7 @@ describe('Elysia adapter tests - rpc handler', () => {
3135
],
3236
},
3337
},
34-
})
38+
}),
3539
);
3640
expect(r.status).toBe(201);
3741
expect((await unmarshal(r)).data).toMatchObject({
@@ -51,7 +55,7 @@ describe('Elysia adapter tests - rpc handler', () => {
5155
expect((await unmarshal(r)).data).toHaveLength(1);
5256

5357
r = await handler(
54-
makeRequest('PUT', '/api/user/update', { where: { id: 'user1' }, data: { email: 'user1@def.com' } })
58+
makeRequest('PUT', '/api/user/update', { where: { id: 'user1' }, data: { email: 'user1@def.com' } }),
5559
);
5660
expect(r.status).toBe(200);
5761
expect((await unmarshal(r)).data.email).toBe('user1@def.com');
@@ -65,14 +69,14 @@ describe('Elysia adapter tests - rpc handler', () => {
6569
expect((await unmarshal(r)).data._sum.viewCount).toBe(3);
6670

6771
r = await handler(
68-
makeRequest('GET', makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } }))
72+
makeRequest('GET', makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } })),
6973
);
7074
expect(r.status).toBe(200);
7175
expect((await unmarshal(r)).data).toEqual(
7276
expect.arrayContaining([
7377
expect.objectContaining({ published: true, _sum: { viewCount: 1 } }),
7478
expect.objectContaining({ published: false, _sum: { viewCount: 2 } }),
75-
])
79+
]),
7680
);
7781

7882
r = await handler(makeRequest('DELETE', makeUrl('/api/user/deleteMany', { where: { id: 'user1' } })));
@@ -89,8 +93,8 @@ describe('Elysia adapter tests - rest handler', () => {
8993
createElysiaHandler({
9094
getClient: () => client,
9195
basePath: '/api',
92-
apiHandler: new RestApiHandler({schema: client.$schema, endpoint: 'http://localhost/api' }),
93-
})
96+
apiHandler: new RestApiHandler({ schema: client.$schema, endpoint: 'http://localhost/api' }),
97+
}),
9498
);
9599

96100
let r = await handler(makeRequest('GET', makeUrl('/api/post/1')));
@@ -102,7 +106,7 @@ describe('Elysia adapter tests - rest handler', () => {
102106
type: 'user',
103107
attributes: { id: 'user1', email: 'user1@abc.com' },
104108
},
105-
})
109+
}),
106110
);
107111
expect(r.status).toBe(201);
108112
expect(await unmarshal(r)).toMatchObject({
@@ -129,7 +133,7 @@ describe('Elysia adapter tests - rest handler', () => {
129133
r = await handler(
130134
makeRequest('PUT', makeUrl('/api/user/user1'), {
131135
data: { type: 'user', attributes: { email: 'user1@def.com' } },
132-
})
136+
}),
133137
);
134138
expect(r.status).toBe(200);
135139
expect((await unmarshal(r)).data.attributes.email).toBe('user1@def.com');

packages/server/test/adapter/fastify.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describe('Fastify adapter tests - rpc handler', () => {
1313
app.register(ZenStackFastifyPlugin, {
1414
prefix: '/api',
1515
getClient: () => client,
16-
apiHandler: new RPCApiHandler({ schema: client.schema })
16+
apiHandler: new RPCApiHandler({ schema: client.schema }),
1717
});
1818

1919
let r = await app.inject({
@@ -49,7 +49,7 @@ describe('Fastify adapter tests - rpc handler', () => {
4949
expect.objectContaining({ title: 'post1' }),
5050
expect.objectContaining({ title: 'post2' }),
5151
]),
52-
})
52+
}),
5353
);
5454

5555
r = await app.inject({
@@ -97,7 +97,7 @@ describe('Fastify adapter tests - rpc handler', () => {
9797
expect.arrayContaining([
9898
expect.objectContaining({ published: true, _sum: { viewCount: 1 } }),
9999
expect.objectContaining({ published: false, _sum: { viewCount: 2 } }),
100-
])
100+
]),
101101
);
102102

103103
r = await app.inject({

0 commit comments

Comments
 (0)