feat: migrate nextjs to rr7

This commit is contained in:
David Nguyen
2025-01-02 15:33:37 +11:00
parent 9183f668d3
commit 383b5f78f0
898 changed files with 31175 additions and 24615 deletions

View File

@ -1,4 +1,4 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { Context as HonoContext } from 'hono';
import type { JobDefinition, SimpleTriggerJobOptions } from './_internal/job';
@ -13,7 +13,7 @@ export abstract class BaseJobProvider {
throw new Error('Not implemented');
}
public getApiHandler(): (req: NextApiRequest, res: NextApiResponse) => Promise<Response | void> {
public getApiHandler(): (req: HonoContext) => Promise<Response | void> {
throw new Error('Not implemented');
}
}

View File

@ -1,18 +1,17 @@
import { match } from 'ts-pattern';
import { env } from '../../utils/env';
import type { JobDefinition, TriggerJobOptions } from './_internal/job';
import type { BaseJobProvider as JobClientProvider } from './base';
import { InngestJobProvider } from './inngest';
import { LocalJobProvider } from './local';
import { TriggerJobProvider } from './trigger';
export class JobClient<T extends ReadonlyArray<JobDefinition> = []> {
private _provider: JobClientProvider;
public constructor(definitions: T) {
this._provider = match(process.env.NEXT_PRIVATE_JOBS_PROVIDER)
this._provider = match(env('NEXT_PRIVATE_JOBS_PROVIDER'))
.with('inngest', () => InngestJobProvider.getInstance())
.with('trigger', () => TriggerJobProvider.getInstance())
.otherwise(() => LocalJobProvider.getInstance());
definitions.forEach((definition) => {

View File

@ -0,0 +1,3 @@
// Empty file for build reasons.
// Vite build seems to assume jobs/client.ts = jobs/client/index.ts and therefore will throw an error that the file is missing.
// Could refactor the files, but this is easier.

View File

@ -1,12 +1,10 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import type { NextRequest } from 'next/server';
import type { Context as HonoContext } from 'hono';
import type { Context, Handler, InngestFunction } from 'inngest';
import { Inngest as InngestClient } from 'inngest';
import { serve as createHonoPagesRoute } from 'inngest/hono';
import type { Logger } from 'inngest/middleware/logger';
import { serve as createPagesRoute } from 'inngest/next';
import { json } from 'micro';
import { env } from '../../utils/env';
import type { JobDefinition, JobRunIO, SimpleTriggerJobOptions } from './_internal/job';
import { BaseJobProvider } from './base';
@ -26,8 +24,8 @@ export class InngestJobProvider extends BaseJobProvider {
static getInstance() {
if (!this._instance) {
const client = new InngestClient({
id: process.env.NEXT_PRIVATE_INNGEST_APP_ID || 'documenso-app',
eventKey: process.env.INNGEST_EVENT_KEY || process.env.NEXT_PRIVATE_INNGEST_EVENT_KEY,
id: env('NEXT_PRIVATE_INNGEST_APP_ID') || 'documenso-app',
eventKey: env('INNGEST_EVENT_KEY') || env('NEXT_PRIVATE_INNGEST_EVENT_KEY'),
});
this._instance = new InngestJobProvider({ client });
@ -73,24 +71,36 @@ export class InngestJobProvider extends BaseJobProvider {
});
}
// public getApiHandler() {
// const handler = createPagesRoute({
// client: this._client,
// functions: this._functions,
// });
// return async (req: NextApiRequest, res: NextApiResponse) => {
// // Since body-parser is disabled for this route we need to patch in the parsed body
// if (req.headers['content-type'] === 'application/json') {
// Object.assign(req, {
// body: await json(req),
// });
// }
// // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
// const nextReq = req as unknown as NextRequest;
// return await handler(nextReq, res);
// };
// }
// Todo: Do we need to handle the above?
public getApiHandler() {
const handler = createPagesRoute({
client: this._client,
functions: this._functions,
});
return async (context: HonoContext) => {
const handler = createHonoPagesRoute({
client: this._client,
functions: this._functions,
});
return async (req: NextApiRequest, res: NextApiResponse) => {
// Since body-parser is disabled for this route we need to patch in the parsed body
if (req.headers['content-type'] === 'application/json') {
Object.assign(req, {
body: await json(req),
});
}
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const nextReq = req as unknown as NextRequest;
return await handler(nextReq, res);
return await handler(context);
};
}

View File

@ -1,10 +1,8 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { sha256 } from '@noble/hashes/sha256';
import { json } from 'micro';
import { BackgroundJobStatus, Prisma } from '@prisma/client';
import type { Context as HonoContext } from 'hono';
import { prisma } from '@documenso/prisma';
import { BackgroundJobStatus, Prisma } from '@documenso/prisma/client';
import { NEXT_PRIVATE_INTERNAL_WEBAPP_URL } from '../../constants/app';
import { sign } from '../../server-only/crypto/sign';
@ -70,26 +68,27 @@ export class LocalJobProvider extends BaseJobProvider {
);
}
public getApiHandler() {
return async (req: NextApiRequest, res: NextApiResponse) => {
public getApiHandler(): (c: HonoContext) => Promise<Response | void> {
return async (c: HonoContext) => {
const req = c.req;
if (req.method !== 'POST') {
res.status(405).send('Method not allowed');
return c.text('Method not allowed', 405);
}
const jobId = req.headers['x-job-id'];
const signature = req.headers['x-job-signature'];
const isRetry = req.headers['x-job-retry'] !== undefined;
const jobId = req.header('x-job-id');
const signature = req.header('x-job-signature');
const isRetry = req.header('x-job-retry') !== undefined;
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
const options = await json(req)
const options = await req
.json()
.then(async (data) => ZSimpleTriggerJobOptionsSchema.parseAsync(data))
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
.then((data) => data as SimpleTriggerJobOptions)
.catch(() => null);
if (!options) {
res.status(400).send('Bad request');
return;
return c.text('Bad request', 400);
}
const definition = this._jobDefinitions[options.name];
@ -99,33 +98,28 @@ export class LocalJobProvider extends BaseJobProvider {
typeof signature !== 'string' ||
typeof options !== 'object'
) {
res.status(400).send('Bad request');
return;
return c.text('Bad request', 400);
}
if (!definition) {
res.status(404).send('Job not found');
return;
return c.text('Job not found', 404);
}
if (definition && !definition.enabled) {
console.log('Attempted to trigger a disabled job', options.name);
res.status(404).send('Job not found');
return;
return c.text('Job not found', 404);
}
if (!signature || !verify(options, signature)) {
res.status(401).send('Unauthorized');
return;
return c.text('Unauthorized', 401);
}
if (definition.trigger.schema) {
const result = definition.trigger.schema.safeParse(options.payload);
if (!result.success) {
res.status(400).send('Bad request');
return;
return c.text('Bad request', 400);
}
}
@ -148,8 +142,7 @@ export class LocalJobProvider extends BaseJobProvider {
.catch(() => null);
if (!backgroundJob) {
res.status(404).send('Job not found');
return;
return c.text('Job not found', 404);
}
try {
@ -188,8 +181,7 @@ export class LocalJobProvider extends BaseJobProvider {
},
});
res.status(500).send('Task exceeded retries');
return;
return c.text('Task exceeded retries', 500);
}
backgroundJob = await prisma.backgroundJob.update({
@ -209,7 +201,7 @@ export class LocalJobProvider extends BaseJobProvider {
});
}
res.status(200).send('OK');
return c.text('OK', 200);
};
}

View File

@ -1,73 +0,0 @@
import { createPagesRoute } from '@trigger.dev/nextjs';
import type { IO } from '@trigger.dev/sdk';
import { TriggerClient, eventTrigger } from '@trigger.dev/sdk';
import type { JobDefinition, JobRunIO, SimpleTriggerJobOptions } from './_internal/job';
import { BaseJobProvider } from './base';
export class TriggerJobProvider extends BaseJobProvider {
private static _instance: TriggerJobProvider;
private _client: TriggerClient;
private constructor(options: { client: TriggerClient }) {
super();
this._client = options.client;
}
static getInstance() {
if (!this._instance) {
const client = new TriggerClient({
id: 'documenso-app',
apiKey: process.env.NEXT_PRIVATE_TRIGGER_API_KEY,
apiUrl: process.env.NEXT_PRIVATE_TRIGGER_API_URL,
});
this._instance = new TriggerJobProvider({ client });
}
return this._instance;
}
public defineJob<N extends string, T>(job: JobDefinition<N, T>): void {
this._client.defineJob({
id: job.id,
name: job.name,
version: job.version,
trigger: eventTrigger({
name: job.trigger.name,
schema: job.trigger.schema,
}),
run: async (payload, io) => job.handler({ payload, io: this.convertTriggerIoToJobRunIo(io) }),
});
}
public async triggerJob(options: SimpleTriggerJobOptions): Promise<void> {
await this._client.sendEvent({
id: options.id,
name: options.name,
payload: options.payload,
timestamp: options.timestamp ? new Date(options.timestamp) : undefined,
});
}
public getApiHandler() {
const { handler } = createPagesRoute(this._client);
return handler;
}
private convertTriggerIoToJobRunIo(io: IO) {
return {
wait: io.wait,
logger: io.logger,
runTask: async (cacheKey, callback) => io.runTask(cacheKey, callback),
triggerJob: async (cacheKey, payload) =>
io.sendEvent(cacheKey, {
...payload,
timestamp: payload.timestamp ? new Date(payload.timestamp) : undefined,
}),
} satisfies JobRunIO;
}
}