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
2 changes: 1 addition & 1 deletion packages/cli/src/actions/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ async function runReset(prismaSchemaFile: string, options: ResetOptions) {
'prisma migrate reset',
` --schema "${prismaSchemaFile}"`,
' --skip-generate',
options.force ? ' --force' : ''
options.force ? ' --force' : '',
].join('');

await execPackage(cmd);
Expand Down
10 changes: 10 additions & 0 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@
"types": "./dist/sveltekit.d.cts",
"default": "./dist/sveltekit.cjs"
}
},
"./tanstack-start": {
"import": {
"types": "./dist/tanstack-start.d.ts",
"default": "./dist/tanstack-start.js"
},
"require": {
"types": "./dist/tanstack-start.d.cts",
"default": "./dist/tanstack-start.cjs"
}
}
},
"dependencies": {
Expand Down
14 changes: 9 additions & 5 deletions packages/server/src/adapter/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { SchemaDef } from "@zenstackhq/orm/schema";
import { log } from "../api/utils";
import type { ApiHandler, LogConfig } from "../types";
import type { SchemaDef } from '@zenstackhq/orm/schema';
import { log } from '../api/utils';
import type { ApiHandler, LogConfig } from '../types';

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

export function logInternalError(logger: LogConfig | undefined, err: unknown) {
log(logger, 'error', `An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`);
}
log(
logger,
'error',
`An unhandled error occurred while processing the request: ${err}${err instanceof Error ? '\n' + err.stack : ''}`,
);
}
1 change: 0 additions & 1 deletion packages/server/src/adapter/fastify/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export { ZenStackFastifyPlugin, type FastifyPluginOptions } from './plugin';

6 changes: 4 additions & 2 deletions packages/server/src/adapter/fastify/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { logInternalError, type CommonAdapterOptions } from '../common';
* Fastify plugin options
*/
export interface FastifyPluginOptions<Schema extends SchemaDef> extends CommonAdapterOptions<Schema> {

/**
* Url prefix, e.g.: /api
*/
Expand All @@ -17,7 +16,10 @@ export interface FastifyPluginOptions<Schema extends SchemaDef> extends CommonAd
/**
* Callback for getting a ZenStackClient for the given request
*/
getClient: (request: FastifyRequest, reply: FastifyReply) => ClientContract<Schema> | Promise<ClientContract<Schema>>;
getClient: (
request: FastifyRequest,
reply: FastifyReply,
) => ClientContract<Schema> | Promise<ClientContract<Schema>>;
}

/**
Expand Down
9 changes: 1 addition & 8 deletions packages/server/src/adapter/nuxt/handler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import type { ClientContract } from '@zenstackhq/orm';
import type { SchemaDef } from '@zenstackhq/orm/schema';
import {
H3Event,
defineEventHandler,
getQuery,
getRouterParams,
readBody,
type EventHandlerRequest
} from 'h3';
import { H3Event, defineEventHandler, getQuery, getRouterParams, readBody, type EventHandlerRequest } from 'h3';
import { setResponseStatus } from 'nuxt/app';
import { logInternalError, type CommonAdapterOptions } from '../common';

Expand Down
73 changes: 73 additions & 0 deletions packages/server/src/adapter/tanstack-start/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { SchemaDef } from '@zenstackhq/orm/schema';
import type { TanStackStartOptions } from '.';
import { logInternalError } from '../common';

/**
* Creates a TanStack Start server route handler which encapsulates ZenStack CRUD operations.
*
* @param options Options for initialization
* @returns A TanStack Start server route handler
*/
export default function factory<Schema extends SchemaDef>(
options: TanStackStartOptions<Schema>,
): ({ request, params }: { request: Request; params: Record<string, string> }) => Promise<Response> {
return async ({ request, params }: { request: Request; params: Record<string, string> }) => {
const client = await options.getClient(request, params);
if (!client) {
return new Response(JSON.stringify({ message: 'unable to get ZenStackClient from request context' }), {
status: 500,
headers: {
'Content-Type': 'application/json',
},
});
}

const url = new URL(request.url);
const query = Object.fromEntries(url.searchParams);

// Extract path from params._splat for catch-all routes
const path = params['_splat'];

if (!path) {
return new Response(JSON.stringify({ message: 'missing path parameter' }), {
status: 400,
headers: {
'Content-Type': 'application/json',
},
});
}

let requestBody: unknown;
if (request.body) {
try {
requestBody = await request.json();
} catch {
// noop
}
}

try {
const r = await options.apiHandler.handleRequest({
method: request.method!,
path,
query,
requestBody,
client,
});
return new Response(JSON.stringify(r.body), {
status: r.status,
headers: {
'Content-Type': 'application/json',
},
});
} catch (err) {
logInternalError(options.apiHandler.log, err);
return new Response(JSON.stringify({ message: 'An internal server error occurred' }), {
status: 500,
headers: {
'Content-Type': 'application/json',
},
});
}
};
}
29 changes: 29 additions & 0 deletions packages/server/src/adapter/tanstack-start/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { ClientContract } from '@zenstackhq/orm';
import type { SchemaDef } from '@zenstackhq/orm/schema';
import type { CommonAdapterOptions } from '../common';
import { default as Handler } from './handler';

/**
* Options for initializing a TanStack Start server route handler.
*/
export interface TanStackStartOptions<Schema extends SchemaDef> extends CommonAdapterOptions<Schema> {
/**
* Callback method for getting a ZenStackClient instance for the given request and params.
*/
getClient: (
request: Request,
params: Record<string, string>,
) => ClientContract<Schema> | Promise<ClientContract<Schema>>;
}

/**
* Creates a TanStack Start server route handler.
* @see https://zenstack.dev/docs/reference/server-adapters/tanstack-start
*/
export function TanStackStartHandler<Schema extends SchemaDef>(
options: TanStackStartOptions<Schema>,
): ReturnType<typeof Handler> {
return Handler(options);
}

export default TanStackStartHandler;
22 changes: 13 additions & 9 deletions packages/server/test/adapter/elysia.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ describe('Elysia adapter tests - rpc handler', () => {
const client = await createTestClient(schema);

const handler = await createElysiaApp(
createElysiaHandler({ getClient: () => client, basePath: '/api', apiHandler: new RPCApiHandler({ schema: client.schema }) })
createElysiaHandler({
getClient: () => client,
basePath: '/api',
apiHandler: new RPCApiHandler({ schema: client.schema }),
}),
);

let r = await handler(makeRequest('GET', makeUrl('/api/post/findMany', { where: { id: { equals: '1' } } })));
Expand All @@ -31,7 +35,7 @@ describe('Elysia adapter tests - rpc handler', () => {
],
},
},
})
}),
);
expect(r.status).toBe(201);
expect((await unmarshal(r)).data).toMatchObject({
Expand All @@ -51,7 +55,7 @@ describe('Elysia adapter tests - rpc handler', () => {
expect((await unmarshal(r)).data).toHaveLength(1);

r = await handler(
makeRequest('PUT', '/api/user/update', { where: { id: 'user1' }, data: { email: '[email protected]' } })
makeRequest('PUT', '/api/user/update', { where: { id: 'user1' }, data: { email: '[email protected]' } }),
);
expect(r.status).toBe(200);
expect((await unmarshal(r)).data.email).toBe('[email protected]');
Expand All @@ -65,14 +69,14 @@ describe('Elysia adapter tests - rpc handler', () => {
expect((await unmarshal(r)).data._sum.viewCount).toBe(3);

r = await handler(
makeRequest('GET', makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } }))
makeRequest('GET', makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } })),
);
expect(r.status).toBe(200);
expect((await unmarshal(r)).data).toEqual(
expect.arrayContaining([
expect.objectContaining({ published: true, _sum: { viewCount: 1 } }),
expect.objectContaining({ published: false, _sum: { viewCount: 2 } }),
])
]),
);

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

let r = await handler(makeRequest('GET', makeUrl('/api/post/1')));
Expand All @@ -102,7 +106,7 @@ describe('Elysia adapter tests - rest handler', () => {
type: 'user',
attributes: { id: 'user1', email: '[email protected]' },
},
})
}),
);
expect(r.status).toBe(201);
expect(await unmarshal(r)).toMatchObject({
Expand All @@ -129,7 +133,7 @@ describe('Elysia adapter tests - rest handler', () => {
r = await handler(
makeRequest('PUT', makeUrl('/api/user/user1'), {
data: { type: 'user', attributes: { email: '[email protected]' } },
})
}),
);
expect(r.status).toBe(200);
expect((await unmarshal(r)).data.attributes.email).toBe('[email protected]');
Expand Down
6 changes: 3 additions & 3 deletions packages/server/test/adapter/fastify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe('Fastify adapter tests - rpc handler', () => {
app.register(ZenStackFastifyPlugin, {
prefix: '/api',
getClient: () => client,
apiHandler: new RPCApiHandler({ schema: client.schema })
apiHandler: new RPCApiHandler({ schema: client.schema }),
});

let r = await app.inject({
Expand Down Expand Up @@ -49,7 +49,7 @@ describe('Fastify adapter tests - rpc handler', () => {
expect.objectContaining({ title: 'post1' }),
expect.objectContaining({ title: 'post2' }),
]),
})
}),
);

r = await app.inject({
Expand Down Expand Up @@ -97,7 +97,7 @@ describe('Fastify adapter tests - rpc handler', () => {
expect.arrayContaining([
expect.objectContaining({ published: true, _sum: { viewCount: 1 } }),
expect.objectContaining({ published: false, _sum: { viewCount: 2 } }),
])
]),
);

r = await app.inject({
Expand Down
18 changes: 10 additions & 8 deletions packages/server/test/adapter/hono.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ describe('Hono adapter tests - rpc handler', () => {
it('properly handles requests', async () => {
const client = await createTestClient(schema);

const handler = await createHonoApp(createHonoHandler({ getClient: () => client, apiHandler: new RPCApiHandler({schema: client.$schema}) }));
const handler = await createHonoApp(
createHonoHandler({ getClient: () => client, apiHandler: new RPCApiHandler({ schema: client.$schema }) }),
);

let r = await handler(makeRequest('GET', makeUrl('/api/post/findMany', { where: { id: { equals: '1' } } })));
expect(r.status).toBe(200);
Expand All @@ -29,7 +31,7 @@ describe('Hono adapter tests - rpc handler', () => {
],
},
},
})
}),
);
expect(r.status).toBe(201);
expect((await unmarshal(r)).data).toMatchObject({
Expand All @@ -49,7 +51,7 @@ describe('Hono adapter tests - rpc handler', () => {
expect((await unmarshal(r)).data).toHaveLength(1);

r = await handler(
makeRequest('PUT', '/api/user/update', { where: { id: 'user1' }, data: { email: '[email protected]' } })
makeRequest('PUT', '/api/user/update', { where: { id: 'user1' }, data: { email: '[email protected]' } }),
);
expect(r.status).toBe(200);
expect((await unmarshal(r)).data.email).toBe('[email protected]');
Expand All @@ -63,14 +65,14 @@ describe('Hono adapter tests - rpc handler', () => {
expect((await unmarshal(r)).data._sum.viewCount).toBe(3);

r = await handler(
makeRequest('GET', makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } }))
makeRequest('GET', makeUrl('/api/post/groupBy', { by: ['published'], _sum: { viewCount: true } })),
);
expect(r.status).toBe(200);
expect((await unmarshal(r)).data).toEqual(
expect.arrayContaining([
expect.objectContaining({ published: true, _sum: { viewCount: 1 } }),
expect.objectContaining({ published: false, _sum: { viewCount: 2 } }),
])
]),
);

r = await handler(makeRequest('DELETE', makeUrl('/api/user/deleteMany', { where: { id: 'user1' } })));
Expand All @@ -87,7 +89,7 @@ describe('Hono adapter tests - rest handler', () => {
createHonoHandler({
getClient: () => client,
apiHandler: new RestApiHandler({ endpoint: 'http://localhost/api', schema: client.$schema }),
})
}),
);

let r = await handler(makeRequest('GET', makeUrl('/api/post/1')));
Expand All @@ -99,7 +101,7 @@ describe('Hono adapter tests - rest handler', () => {
type: 'user',
attributes: { id: 'user1', email: '[email protected]' },
},
})
}),
);
expect(r.status).toBe(201);
expect(await unmarshal(r)).toMatchObject({
Expand All @@ -126,7 +128,7 @@ describe('Hono adapter tests - rest handler', () => {
r = await handler(
makeRequest('PUT', makeUrl('/api/user/user1'), {
data: { type: 'user', attributes: { email: '[email protected]' } },
})
}),
);
expect(r.status).toBe(200);
expect((await unmarshal(r)).data.attributes.email).toBe('[email protected]');
Expand Down
Loading