npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

next-server-task

v0.1.2

Published

Provides a mechanism for executing long running tasks on NextJS edge api-handlers

Downloads

57

Readme

next-server-task

CI

Execute long running tasks on NextJS edge API handlers.

You can also checkout this Example.

Table of contents

  1. Install
  2. How it works?
  3. Usage example
  4. Accessing the request with TaskServerContext
  5. TaskError
  6. License

Install

npm install next-server-task
yarn add next-server-task
pnpm add next-server-task

How it works?

sequenceDiagram
    participant Client
    participant Server

    Client->>Server: Make a request
    Server->>Client: Establish SSE connection

    loop 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, 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:

// app/api/generate-image/route.ts

import { 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

// ImageGenerator.tsx

import 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<GenerateImage>();

export default function ImageGenerator() {
    const [imageUrl, setImageUrl] = useState<string>();
    const [error, setError] = useState<string>();
    const { mutate, isMutating } = client.useTask("/api/generate-image");

    const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        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 <div>
        {imageUrl && 
            <Image 
                alt={"Generated Image"} 
                src={imageUrl} 
                width={256} 
                height={256}
            />}

        <form onSubmit={handleSubmit}>
            <input placeholder="Prompt..." name="prompt"/>
            <button type="submit">Generate</button>
        </form>

        {isMutating && <p>Loading...</p>}
        {error && <p style={{ color: "red" }}>{error}</p>}
    </div>
}

Accessing the request with TaskServerContext

You can access the request in the task using the TaskServerContext.

The TaskServerContext had this shape:

type TaskServerContext = {
    req: Request,
    params: Record<string, string | undefined>
}
// 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.

// server
const myTask = createTask("/api/my-task").withAction(() => {
    const randomNumber = Math.random();
    if (randomNumber > 0.5) {
        throw new TaskError("Invalid number");
    }

    return { randomNumber };
})
// 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.