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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@panter/cloud-tasks

v3.6.0

Published

A lightweight library that streamlines the use of **Google Cloud Tasks**.

Readme

🚀 Google Cloud Tasks, Simplified

A lightweight library that streamlines the use of Google Cloud Tasks.

🔹 Why Use This Library?

Using Google Cloud Tasks typically requires:

  1. Implementing a worker service.
  2. Defining and managing the API between the worker and client.
  3. Handling task queue management and scheduling.
  4. Setting up a local development environment.
  5. Configuring Google Cloud Tasks to send HTTP requests to the worker.

This library eliminates much of this complexity by leveraging tRPC to define the API between the worker and the client.

✅ Key Features

  • Seamless API communication between workers and clients.
  • Simple task scheduling with an intuitive API.
  • Type safety and jump-to-definition support via tRPC.
  • Built-in support for local development with a Cloud Tasks emulator.
  • Direct, synchronous calls to the same worker when you need the result back.

📂 Example Usage

Suppose we have one tasks worker (tasks-worker) and a backend API (api) that will be scheduling tasks.

Project Structure

An example project structure with Turborepo might look like this:

.
└── apps/
    ├── tasks-worker/
    │   ├── api.ts
    │   └── index.ts
    └── api/
        └── src/
            └── someEndpoint.ts

Pro tip: If you have multiple task workers, use naming convention tasks-worker-<name>.

Two apps that are deployed as two services: api and tasks-worker.


🛠 Step 1: Create the Task Worker (api.ts)

Define a tRPC-powered task server:

// apps/tasks-worker/api.ts

import { logger } from "@repo/logger";
// yarn add @panter/cloud-tasks
import { createTasksServer } from "@panter/cloud-tasks/server";
import { z } from "zod";

logger.info("Starting tasks server...");

export const { runServer, router } = createTasksServer({
  createRouter: (t) =>
    t.router({
      createUser: t.procedure
        .input(z.object({ name: z.string().min(5) }))
        .mutation(async (opts) => {
          logger.info(`creating user ${opts.input.name}`);
        }),
      doNothing: t.procedure.mutation(() => {
        logger.info("doing nothing");
      }),
      sendEmail: t.procedure.mutation(() => {
        logger.info("sending email");
      }),
    }),
});

// Export the router type for use in the client
export type Router = typeof router;

This creates a task server with three tRPC mutations: createUser, doNothing, and sendEmail.

Notice that mutations return nothing, as they are called by Google Cloud Tasks and their response is ignored.


🚀 Step 2: Start the Task Worker (index.ts)

Initialize the worker server:

// apps/tasks-worker/index.ts

import { logger } from "@repo/logger";
import { runServer } from "./api";

const port = parseInt(process.env.PORT);
runServer(port);
logger.info(`🚀 Task worker tRPC server running at http://localhost:${port}/`);

Pro tip: Set execution environment for tasks workers to "gen2", deny unauthenticated requests and set higher timeout in catladder:

// catladder.ts

"tasks-worker": {
  dir: "./apps/tasks-worker",
  deploy: {
    service: {
      allowUnauthenticated: false,
      executionEnvironment: "gen2",
      timeout: "1800s",
    },
  },
}

🏗 Step 3: Create the Task Client in api

Now, in the backend API (api), define a task client to send tasks:

// apps/api/src/someEndpoint.ts

import { builder } from "../builder";
import { logger } from "@repo/logger";

// NOTE: we import Router type from the tasks-worker without introducing a runtime dependency
import type { Router } from "../../tasks-worker-gitlab/api.ts";
// yarn add @panter/cloud-tasks
import { createTasksClient } from "@panter/cloud-tasks/client";

const tasks = createTasksClient<Router>({
  // TASKS_WORKER_URL is the URL of the task worker service
  tasksWorkerUrl: new URL(process.env.TASKS_WORKER_URL),
  // special characters are not allowed in queue name
  queueName: "reasonable-queue-name",
  // enable emulator in local development
  emulator: process.env.ENV_SHORT === "local" ? { port: 6020 } : false,
  // optional: provide a custom logger
  // assumes `logger.info(metadata, message)` order of arguments (pino logger)
  logger,
  // or provide custom adapter (e.g. for winston logger):
  // logger: {
  //   info: (meta, message) => logger.info(message, meta),
  //   warn: (meta, message) => logger.warn(message, meta),
  //   error: (meta, message) => logger.error(message, meta),
  // },
});

builder.mutationField("runJob", (t) =>
  t.field({
    type: "String",
    resolve: async () => {
      // Schedule a task with a payload
      await tasks.createUser.schedule({ name: "Bilbo Baggins" });
      //          ^^^^^^^^^^ Try jumping to definition here!
      return "ok";
    },
  }),
);

Pro tip: Set the TASKS_WORKER_URL environment variable to the URL of the task worker service in catladder:

// catladder.ts

api: {
  dir: "./apps/api",
  vars: {
    public: {
      TASKS_WORKER_URL: "${tasks-worker:ROOT_URL_INTERNAL}",
    }
  },
}

📌 Important: Avoid Circular Dependencies

Notice that Router is imported as a type and by relative path. This makes sure there won't be a circular dependency when e.g. taks-worker needs to import api to use some business logic.


⚡️ Step 4 (optional): Call the worker directly when you need the result

createTasksClient queues the call: Cloud Tasks delivers it to the worker, retries it if it fails, and never gives you the procedure's return value — the queue only ever sees an HTTP status. That is exactly what you want for background work.

Sometimes the caller needs the result right now — an admin button that reports per-item success and failure, say. For those, createDirectClient calls the very same worker procedures over plain HTTP and resolves with their return value:

// apps/api/src/someEndpoint.ts

import type { Router } from "../../tasks-worker/api.ts";
import { createDirectClient } from "@panter/cloud-tasks/client";

const worker = createDirectClient<Router>({
  // same URL you pass to createTasksClient — the /trpc prefix is added for you
  tasksWorkerUrl: new URL(process.env.TASKS_WORKER_URL),
  // optional: workers deployed with `allowUnauthenticated: false` need an ID
  // token. May be async; called once per request. Omit it locally.
  headers: async () => ({ authorization: `Bearer ${await getIdToken()}` }),
  // optional: same logger shape as createTasksClient takes
  logger,
});

// resolves with whatever the procedure returned, fully typed
const { updated } = await worker.updateStats.mutate({ recipeId: "42" });

| | createTasksClient | createDirectClient | | ------------------- | ------------------------------------ | ------------------------------------ | | transport | Google Cloud Tasks queue | direct HTTP to the worker | | call style | client.foo.schedule(input) | client.foo.mutate(input) | | returns | a task handle, never the result | the procedure's return value | | retries | yes, handled by the queue | no — a failure just rejects | | caller waits | no, fire-and-forget | yes, for as long as the worker takes | | use it for | background work | on-demand / interactive calls |

Both clients talk to the same worker and the same procedures, so a procedure can be queued from one place and called directly from another. Errors surface as TRPCClientError, and because a direct call has no retry and no queue-level durability behind it, keep long-running or must-not-be-lost work on createTasksClient.


🛠 Local Development: Using Cloud Tasks Emulator

For local testing, use the Cloud Tasks Emulator with Docker:

# docker-compose.yml

services:
  gcloud-tasks-emulator:
    image: ghcr.io/aertje/cloud-tasks-emulator:latest
    command: -host 0.0.0.0 -port 8123
    ports:
      - "6020:8123"
    # NOTE: Comment out `extra_hosts` if using Podman (see: https://github.com/containers/podman/issues/21681)
    extra_hosts:
      - "host.containers.internal:host-gateway"

🎯 Summary

This library makes Google Cloud Tasks easy to use by:

  • Removing the need for manual HTTP request handling.
  • Providing a type-safe, tRPC-powered API.
  • Enabling seamless communication between workers and clients.
  • Offering built-in support for local development.

💡 With this library, scheduling background tasks is as simple as calling a function! 🚀