Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/neo-ciber94/next-server-task
Provides a mechanism for executing long running tasks on NextJS edge api-handlers
https://github.com/neo-ciber94/next-server-task
api-handler edge-runtime nextjs server-sent-events typescript vercel
Last synced: about 1 month ago
JSON representation
Provides a mechanism for executing long running tasks on NextJS edge api-handlers
- Host: GitHub
- URL: https://github.com/neo-ciber94/next-server-task
- Owner: Neo-Ciber94
- License: mit
- Created: 2023-10-15T22:21:52.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2024-02-17T01:50:10.000Z (9 months ago)
- Last Synced: 2024-02-17T02:30:15.611Z (9 months ago)
- Topics: api-handler, edge-runtime, nextjs, server-sent-events, typescript, vercel
- Language: TypeScript
- Homepage:
- Size: 11.6 MB
- Stars: 2
- Watchers: 1
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# next-server-task
[![CI](https://github.com/Neo-Ciber94/next-server-task/actions/workflows/ci.yml/badge.svg)](https://github.com/Neo-Ciber94/next-server-task/actions/workflows/ci.yml)
Execute long running tasks on `NextJS` edge API handlers.
You can also checkout this [Example](https://github.com/Neo-Ciber94/next-server-task/tree/main/examples/openai-image-generator).
## Table of contents
1. [Install](#install)
2. [How it works?](#how-it-works)
3. [Usage example](#usage-example)
4. [Accessing the request with TaskServerContext](#accessing-the-request-with-taskservercontext)
5. [TaskError](#taskerror)
6. [License](#license)## Install
```bash
npm install next-server-task
``````bash
yarn add next-server-task
``````bash
pnpm add next-server-task
```## How it works?
```mermaid
sequenceDiagram
participant Client
participant ServerClient->>Server: Make a request
Server->>Client: Establish SSE connectionloop Processing
Server-->>Client: Send "wait" events (while processing)
Server--xClient: An error occurred
Server->>Client: Send "server-error" or "internal-error" event
Server-->>Client: Send "settle" event (processing finished)
end
```We can keep the connection alive thanks we use [Server Sent Events](https://web.dev/articles/eventsource-basics), while the
task is running we sent a `wait` event each 300ms (this can be changed) to notify we still processing, if not error happened we send a `settle` event with the data, if an error ocurred we send an `internal-error` if the error was unexpected or a `server-error` of the error was throw using `TaskError`, these errors are rethrow on the client and the connection is closed.## Usage example
In this example we use the **OpenAI** to generate images which can take a long time to generate the images,
this usually led to timeouts when using platforms like vercel, but using `next-server-task` we can wait until the task
finish and send the result after that.On the server:
```ts
// app/api/generate-image/route.tsimport { TaskError } from "next-server-task";
import { createTask } from "next-server-task/server";
import { OpenAI } from "openai";export const runtime = "edge";
const generateImage = createTask("/api/generate-image").withAction(
async ({ prompt }: { prompt: string }) => {
const openAPI = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const results = await openAPI.images.generate({ prompt });const url = results.data[0].url;
if (url == null) {
throw new TaskError("Failed to generate image");
}return { url };
}
);export type GenerateImage = typeof generateImage;
const { handler } = generateImage.serverHandler();
export { handler as GET };
```On the client
```tsx
// ImageGenerator.tsximport React, { useState } from "react";
import Image from "next/image";
import { type GenerateImage } from "./api/generate-image/route";
import { createClient } from "next-server-task/client";const client = createClient();
export default function ImageGenerator() {
const [imageUrl, setImageUrl] = useState();
const [error, setError] = useState();
const { mutate, isMutating } = client.useTask("/api/generate-image");const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();setError(undefined);
const form = new FormData(e.currentTarget);
const prompt = form.get("prompt")?.toString() ?? "";
try {
const result = await mutate({ prompt });
setImageUrl(result.url);
}
catch (err: any) {
const message = err?.message ?? "Failed to generate image";
setError(message);
}
};return
{imageUrl &&
}
Generate
{isMutating &&
Loading...
}
{error &&{error}
}
}
```## Accessing the request with TaskServerContext
You can access the request in the task using the `TaskServerContext`.
The `TaskServerContext` had this shape:
```ts
type TaskServerContext = {
req: Request,
params: Record
}
``````ts
// server
const myTask = createTask("/api/my-task").withAction((_, ctx) => {
const url = ctx.req.url;
return { url };
})
```## TaskError
You can throw expected errors using `TaskError`, this errors are rethrow on the client side as a `TaskClientError` so can be handled in a `try-catch` block.
```ts
// server
const myTask = createTask("/api/my-task").withAction(() => {
const randomNumber = Math.random();
if (randomNumber > 0.5) {
throw new TaskError("Invalid number");
}return { randomNumber };
})
``````ts
// client
const { mutate, isMutating} = useTask("/api/my-task");try {
const { randomNumber } = mutate();
console.log(randomNumber);
}
catch (err) {
if (err instanceof TaskClientError) {
console.log(err.message, err.code);
}
}
```## License
This project is licensed under the MIT License - see the LICENSE file for details.