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

@edsallrd/vite-plugin-gasforge

v0.3.0

Published

A Vite plugin for building Google Apps Script projects with type-safe server functions.

Readme

vite-plugin-gasforge

A Vite plugin for building Google Apps Script (GAS) projects with type-safe, validated RPC server functions. Write your server and client code in TypeScript, define validated functions using Standard Schema, and let the plugin manage compilation, serialization, context injection, and tree-shaking.


Key Features

  • Type-Safe RPCs: Call server-side GAS functions from client-side browser code with compile-time type safety.
  • Standard Schema Validation: Validate input parameters and output values using any Standard Schema v1 compatible library (Zod, Valibot, ArkType).
  • Rich Serialization: SuperJSON integration allows you to send JavaScript types like Date, Map, Set, BigInt, and Uint8Array directly over the RPC bridge.
  • Middleware and Context API: Inject execution context (such as active users, spreadsheet instances, or auth tokens) through composable middleware chains.
  • TanStack Query Ready: Every server function includes .queryKey() and .queryOptions() helper adapters for React Query or Vue Query compatibility.
  • Structured Errors: Errors thrown on the server are reconstructed into typed GASForgeError instances on the client, maintaining error codes and stack traces.

Installation

pnpm add vite-plugin-gasforge vite vite-plugin-singlefile @standard-schema/spec
# or
npm install vite-plugin-gasforge vite vite-plugin-singlefile @standard-schema/spec

Setup

1. vite.config.ts

Add the plugin to your Vite configuration:

import { defineConfig } from "vite";
import gas from "@edsallrd/vite-plugin-gasforge";

export default defineConfig({
  plugins: [gas()],
});

2. Project Directory Structure

Organize your source code with folders for client and server entries:

src/
  server/
    index.ts       # Server entry point
  client/
    index.html     # Client HTML entry point

Customize these paths using plugin options:

gas({
  server: "src/server/index.ts",
  client: {
    entry: "src/client/index.html",
    plugins: [], // Additional client-side plugins (e.g. react(), vue())
    rollupOptions: {}, // Custom Rollup configuration for the client bundle
  },
});

3. tsconfig.json

Include the type definitions to enable support for google.script globals, and add the auto-generated virtual types declarations file (gasforge-virtual.d.ts) to your compilation:

{
  "compilerOptions": {
    "types": ["@edsallrd/vite-plugin-gasforge/google.script"]
  },
  "include": [
    "src",
    "gasforge-virtual.d.ts"
  ]
}

[!NOTE] The gasforge-virtual.d.ts file is automatically generated by the plugin in your project root during compilation/development. It dynamically registers TypeScript types for any server function you define.


Guide and API Reference

Defining Server Functions

Use createServerFn to declare typed endpoints. In client builds, the handler implementation is automatically stripped out, while in server builds, the validation and handler logic are preserved.

import { createServerFn } from "@edsallrd/vite-plugin-gasforge";
import { z } from "zod";

export const getGreeting = createServerFn({
  input: z.string(),
  output: z.string(),
  handler: async (name) => {
    return `Hello, ${name}!`;
  },
});

Call the function directly from client-side code:

const message = await getGreeting("World");
// => "Hello, World!"

Local Execution (Server-Side)

If you need to call your server functions locally from within other server-side logic (e.g., in unit tests or internal utility functions) without going through the RPC serialization bridge, you can use the .local method. This will still execute your input validation, middleware chain, handler, and output validation directly:

const message = await getGreeting.local("World");
// => Executes validation + middleware + handler locally

Rich Data Serialization

By incorporating SuperJSON into the RPC bridge, you can transmit rich data structures (such as Date, Map, and Set) without degrading them to strings or empty objects:

import { createServerFn } from "@edsallrd/vite-plugin-gasforge";
import { z } from "zod";

export const createTodo = createServerFn({
  input: z.object({
    title: z.string(),
    tags: z.instanceof(Set),
    dueDate: z.date(),
  }),
  output: z.object({
    id: z.string(),
    createdAt: z.date(),
  }),
  handler: async (todo) => {
    return {
      id: "todo-99",
      createdAt: new Date(),
    };
  },
});

Middleware and Context Injection

Define composable middleware to populate execution context (such as verifying spreadsheet permissions or fetching user credentials) before invoking the main handler:

import {
  createMiddleware,
  createServerFn,
} from "@edsallrd/vite-plugin-gasforge";
import { z } from "zod";

// 1. Define middleware and context outputs
const authMiddleware = createMiddleware().handler(async () => {
  const email = Session.getActiveUser().getEmail();
  if (!email) {
    throw new Error("Unauthorized access");
  }
  return { userEmail: email };
});

// 2. Attach middleware to server functions
export const getUserPreferences = createServerFn({
  middleware: [authMiddleware],
  input: z.void(),
  output: z.any(),
  handler: async (input, ctx) => {
    // ctx is fully typed and contains userEmail:
    console.log(`Access by: ${ctx.userEmail}`);
    return { theme: "dark" };
  },
});

TanStack Query Adapters

Every server function includes .queryKey() and .queryOptions() helper methods to integrate with @tanstack/react-query or @tanstack/vue-query. The query key returned is typed as a read-only tuple (as const):

import { useQuery } from "@tanstack/react-query";
import { getGreeting } from "./functions";

// getGreeting.queryKey("Alice") => readonly ["getGreeting", "Alice"]
// getGreeting.queryOptions("Alice") => { queryKey: readonly ["getGreeting", "Alice"], queryFn: ... }

function GreetingComponent() {
  const { data, isLoading } = useQuery(getGreeting.queryOptions("Alice"));

  if (isLoading) return <div>Loading...</div>;
  return <h1>{data}</h1>;
}

Structured Error Handling

All thrown errors are caught and reconstructed into GASForgeError instances, providing type-safe code categorizations:

import { GASForgeError } from "@edsallrd/vite-plugin-gasforge";
import { getGreeting } from "./functions";

try {
  await getGreeting(123 as any); // Invalid type
} catch (err) {
  if (err instanceof GASForgeError) {
    console.error(`Error Code: ${err.code}`); // e.g. "INPUT_VALIDATION_FAILED"
    console.error(`Issues:`, err.issues); // Validation problems (only for validation failures)
  }
}

Error Codes

The GASForgeError object contains a code property indicating the type of failure:

| Error Code | Description | |---|---| | INPUT_VALIDATION_FAILED | Thrown when client inputs do not conform to the defined input schema. | | OUTPUT_VALIDATION_FAILED | Thrown when server outputs do not conform to the defined output schema. | | MIDDLEWARE_ERROR | Thrown when an error occurs inside a middleware handler. | | SERVER_ERROR | Thrown when an unhandled exception occurs inside your function's handler. | | RPC_ERROR | Thrown when the called server function is not exported or defined on the Apps Script server (google.script.run). |


Server Entry Point

Your Google Apps Script server-side code should export all discovered endpoints by importing the virtual compilation file:

// src/server/index.ts
export * from "virtual:gas/server-fns";

// Add global triggers or HTML sidebar logic
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu("Sidebar Application")
    .addItem("Open App", "showSidebar")
    .addToUi();
}

function showSidebar() {
  const html = HtmlService.createHtmlOutputFromFile("Client");
  SpreadsheetApp.getUi().showSidebar(html);
}

Production Build

Run the compilation script:

pnpm vite build
# or
npm run build

This triggers the dual-build pipeline and generates the following flat bundle files:

dist/
  Server.js    # Deploy directly to Google Apps Script
  Client.html  # Deploy directly to Google Apps Script

Plugin Options

| Option | Type | Default | Description | | ---------------------- | ---------------- | ------------------------- | --------------------------------------------- | | server | string | "src/server/index.ts" | File path of the server entry-point. | | client.entry | string | "src/client/index.html" | File path of the client HTML entry-point. | | client.plugins | PluginOption[] | [] | Additional Vite plugins for the client build. | | client.rollupOptions | object | {} | Custom Rollup build options for the client. |