Skip to main content

Local Development with Prisma Schema

GASsma provides the ability to auto-generate type-safe client code from Prisma-format schema files when developing GAS locally using TypeScript with tools such as clasp+esbuild.

By using this feature, settings such as relation definitions, defaults, and map are auto-generated from the schema, eliminating the need to manually write GASsma-specific constructor options (relations, defaults, updatedAt, ignore, map, etc.). As long as you know Prisma's schema syntax, you can start developing with the same workflow as Prisma.

Without CLI (manual configuration):

import { Gassma } from "gassma";

const gassma = new Gassma.GassmaClient({
id: "SPREAD_SHEET_ID",
relations: {
User: {
posts: { type: "oneToMany", to: "Post", field: "id", reference: "authorId", onDelete: "Cascade" },
},
Post: {
author: { type: "manyToOne", to: "User", field: "authorId", reference: "id" },
},
},
defaults: { User: { role: "USER" } },
updatedAt: { Post: "updatedAt" },
map: { User: { firstName: "名前" } },
});

With CLI (auto-generated from schema):

import { GassmaClient } from "./generated/gassma/schemaClient";

// Relations, defaults, updatedAt, map, etc. are all auto-injected
const gassma = new GassmaClient();

Prerequisites

Install the GASsma CLI tool with the following command:

$ npm i gassma

Creating a Schema File

Create a .prisma file in your project. By default, the ./gassma directory is searched.

my-project/
├── gassma/
│ └── schema.prisma ← Write your schema here
├── package.json
└── ...

Basic Syntax

Define models using Prisma's syntax. Specify the output directory in the generator block's output field.

generator client {
provider = "prisma-client-js"
output = "./generated/gassma"
}

model User {
id Int @id
name String
email String?
age Int
}

Type Mapping

Prisma types are converted to the following TypeScript types:

Prisma TypeTypeScript Type
Intnumber
Floatnumber
Decimalnumber
BigIntnumber
Stringstring
Booleanboolean
DateTimeDate
Jsonstring
Bytesstring

Adding ? makes the field optional (null is allowed).

Relation Definitions

Using Prisma's @relation attribute, relation information is automatically extracted and injected into the generated client.

generator client {
provider = "prisma-client-js"
output = "./generated/gassma"
}

model User {
id Int @id
name String
posts Post[]
}

model Post {
id Int @id
title String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId Int
}

The following relation settings are auto-generated from the above definition:

  • User.posts: oneToMany (User -> Post)
  • Post.author: manyToOne (Post -> User, onDelete: Cascade)

Implicit Many-to-Many

When bidirectional array references exist, an implicit Many-to-Many relation is automatically detected.

model Post {
id Int @id
tags Tag[]
}

model Tag {
id Int @id
name String
posts Post[]
}

In the generated client, the junction table name is automatically resolved as _PostToTag (alphabetical order).

note

The junction table (sheet) itself is not automatically created. You need to manually create a sheet named _PostToTag in the spreadsheet. If you want to change the junction table name, directly edit the relation definition in the generated Client.js.

enum

Literal union types are auto-generated from Prisma's enum definitions.

enum Role {
ADMIN
USER
MODERATOR
}

model User {
id Int @id
role Role
}

Generated type:

"role": "ADMIN" | "USER" | "MODERATOR"

enum @map

Adding @map to enum members allows you to map between the name used in code and the value stored in the spreadsheet.

enum Role {
admin @map("ADMIN")
user @map("USER")
moderator @map("MODERATOR")
}

Generated constant:

const Role = {
admin: "ADMIN",
user: "USER",
moderator: "MODERATOR",
} as const;

The @map values are used in the type definition:

"role": "ADMIN" | "USER" | "MODERATOR"

@gassma.addType

By writing @gassma.addType in a Prisma field comment (///), you can add union types to the field's type.

model User {
/// @gassma.addType string
id Int @id // Generated type: number | string

/// @gassma.addType string, boolean
score Int // Generated type: number | string | boolean

name String // Generated type: string (normal when no comment)
}

@gassma.replaceType

While @gassma.addType creates a union with the base type, @gassma.replaceType replaces the base type and generates only the specified types.

model User {
/// @gassma.replaceType "admin", "user", "moderator"
role String
}

Generated type:

"role": "admin" | "user" | "moderator"  // Does not include string
note

Priority: enum > replaceType > addType. When an enum exists, replaceType / addType are ignored.

@default

Fields with @default() become optional (?) in the generated Create input type.

model User {
id Int @id @default(autoincrement())
name String
isActive Boolean @default(true)
createdAt DateTime @default(now())
}

Generated type:

"isActive"?: boolean   // @default(true) -> Optional
"createdAt"?: Date // @default(now()) -> Optional

The defaults settings are automatically embedded in the generated client JS.

@default()Generated JS
@default(true) / @default(false)true / false
@default(0) (number)0
@default("USER") (string)"USER"
@default(now())() => new Date()
@default(uuid())() => Utilities.getUuid()
@default(autoincrement())Generated separately as an autoincrement setting

@updatedAt

Fields with @updatedAt become optional in the Create input type, and the updatedAt setting is embedded in the generated client JS.

model Post {
id Int @id
title String
updatedAt DateTime @updatedAt
}

@ignore

Fields with @ignore are completely excluded from the type definition, and the ignore setting is embedded in the generated client JS.

model User {
id Int @id
name String
secret String @ignore // Not included in the type definition
}

@map

@map("name") defines a field name mapping. The map setting is embedded in the generated client JS.

model User {
id Int @id
firstName String @map("名前")
lastName String @map("名字")
}

In code, you work with firstName / lastName, which correspond to the columns named "名前" and "名字" in the spreadsheet.

@@ignore

The model-level @@ignore excludes an entire sheet. The ignoreSheets setting is embedded in the generated client JS.

model Logs {
id Int @id
message String

@@ignore
}

@@map

The model-level @@map("name") maps a sheet name.

model Users {
id Int @id
name String

@@map("ユーザー一覧")
}

In code, you access it as Users, which corresponds to the sheet named "ユーザー一覧" in the spreadsheet.

CLI Commands

gassma generate

Generates type files and client code.

$ npx gassma generate

By default, .prisma files in the ./gassma directory are searched. You can specify a particular schema file or directory using the --schema option (equivalent to Prisma's prisma generate --schema).

$ npx gassma generate --schema gassma/user.prisma
$ npx gassma generate --schema ./schemas

The --watch option monitors schema file changes and automatically regenerates.

$ npx gassma generate --watch

It can also be combined with --schema.

gassma init

Initializes a project and auto-generates a schema file and configuration file.

$ npx gassma init

The following files are generated:

  • gassma/schema.prisma -- Initial schema
  • gassma.config.ts -- Configuration file
OptionDescription
--output <path>Customize the output path
--with-modelGenerate a schema with a sample User model

If schema.prisma already exists, it safely stops with an error.

gassma validate

Performs syntax checking and consistency checking of the schema file (equivalent to Prisma's prisma validate).

$ npx gassma validate
$ npx gassma validate --schema gassma/test.prisma

Check items:

  • Syntax errors (parser error detection)
  • generator block existence check
  • output field required check

On success, the following is output:

The schema at /path/to/gassma/test.prisma is valid 🚀

gassma format

Formats .prisma files with the same formatting as the official Prisma formatter (uses @prisma/internals' formatSchema).

$ npx gassma format
OptionDescription
--schema <path>Format only a specific file
--checkCheck if already formatted (for CI; exits with code 1 if unformatted)

gassma version

Displays the GASsma CLI version.

$ npx gassma version

You can also check with the --version / -V flag.

Generated Files

The following files are generated based on the schema file name. For example, for schema.prisma:

FileContent
schema.d.tsType definitions (model types, query types, common types)
schemaClient.jsClient implementation (with auto-injected relation definitions)
schemaClient.d.tsClient type definitions

The output directory is the directory specified by the generator block's output.

Using the Generated Client

Import GassmaClient from the generated client file and use it directly. Relation definitions are auto-injected.

import { GassmaClient } from "./generated/gassma/schemaClient";

const gassma = new GassmaClient();

// Access sheets with type safety
const users = gassma.User.findMany({
where: { age: { gte: 20 } },
select: { name: true, email: true },
});

You can instantiate it using the same pattern as Prisma.

// Prisma
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

// GASsma (same pattern)
import { GassmaClient } from "./generated/gassma/schemaClient";
const gassma = new GassmaClient();

Initialization with Options

// Specify spreadsheet ID
const gassma = new GassmaClient("SPREAD_SHEET_ID");

// Options object
const gassma = new GassmaClient({
id: "SPREAD_SHEET_ID",
omit: {
User: { password: true },
},
});

Configuration File (gassma.config.ts)

By placing gassma.config.ts at the project root, you can centrally manage CLI settings (equivalent to Prisma's prisma.config.ts).

Configuration Interface

There are two ways to write the configuration file.

1. Using the defineConfig helper (recommended):

import { defineConfig } from "gassma/config";

export default defineConfig({
schema: "gassma/schema.prisma",
datasource: {
url: "https://docs.google.com/spreadsheets/d/XXXXX/edit",
},
});

2. Using the satisfies operator:

import type { GassmaConfig } from "gassma";

export default {
schema: "gassma/schema.prisma",
datasource: {
url: "https://docs.google.com/spreadsheets/d/XXXXX/edit",
},
} satisfies GassmaConfig;

The GassmaConfig type can be imported from the root of the gassma package.

Configuration Options

OptionTypeRequiredDescription
schemastringNoPath to the schema file or directory (default: ./gassma)
datasource.urlstringNoSpreadsheet URL or ID

env() Helper

Using the env() function, you can retrieve the spreadsheet URL from an environment variable (equivalent to Prisma's env()).

import "dotenv/config";
import { defineConfig, env } from "gassma/config";

export default defineConfig({
schema: "gassma",
datasource: {
url: env("SPREADSHEET_URL"),
},
});

It can also be used with the satisfies pattern.

import "dotenv/config";
import type { GassmaConfig } from "gassma";
import { env } from "gassma/config";

export default {
schema: "gassma",
datasource: {
url: env("SPREADSHEET_URL"),
},
} satisfies GassmaConfig;
note

env() throws an error if the environment variable is not set or is an empty string. For optional environment variables, use process.env directly.

datasource.url

When you specify a spreadsheet URL or ID in datasource.url, the id is automatically embedded in the generated client JS. This allows you to connect to the target spreadsheet with just new GassmaClient().

Both full URLs and spreadsheet IDs are supported.

// Full URL
datasource: {
url: "https://docs.google.com/spreadsheets/d/XXXXX/edit",
}

// Direct ID specification
datasource: {
url: "XXXXX",
}

datasource Block in Schema

You can also specify the URL by writing a datasource block in the schema file.

datasource db {
provider = "google-spreadsheet"
url = "https://docs.google.com/spreadsheets/d/XXXXX/edit"
}

URL Resolution Priority

  1. datasource block in the schema (highest priority)
  2. datasource.url in gassma.config.ts

Schema Resolution Priority

  1. --schema option (highest priority)
  2. schema setting in gassma.config.ts
  3. Default ./gassma directory

Running gassma init also auto-generates gassma.config.ts.

Multi-file Schema

When you place multiple .prisma files in the same directory (and subdirectories), they are automatically merged into a single schema. This is equivalent to Prisma's Multi-file schema feature.

gassma/
├── schema.prisma ← Write the generator block here
├── models/
│ ├── user.prisma ← User, Profile models
│ └── post.prisma ← Post, Comment models

The generator block only needs to be written in one file and is shared across all files. All models are consolidated into a single client output.

Multiple Schemas (Multiple Spreadsheets)

When working with different spreadsheets, separate schemas into different directories and generate them individually. Type names are prefixed with the schema name, so there are no conflicts even with models of the same name.

schemas/
├── user/
│ └── schema.prisma → userClient.js, user.d.ts
└── order/
└── schema.prisma → orderClient.js, order.d.ts
import { GassmaClient as UserClient } from "./generated/user/schemaClient";
import { GassmaClient as OrderClient } from "./generated/order/schemaClient";

const userGassma = new UserClient();
const orderGassma = new OrderClient();

Overview of Generated Types

The generated .d.ts includes the following types:

  • Model types: Type definitions for each field (GassmaUserUse, etc.)
  • Query types: FindData, CreateData, UpdateData, DeleteData, UpsertData, etc.
  • Select / Omit types: Types for field selection and exclusion
  • Filter types: WhereUse, FilterConditions (including FieldRef support)
  • OrderBy types: Sort conditions (relation sort, _count sort, nulls control included)
  • Include types: Types for relation fetching (including _count)
  • Nested Write types: Create, connect, update, and delete operations for related records
  • Numeric operation types: NumberOperation (increment / decrement / multiply / divide)
  • Common types: FieldRef, GassmaClientOptions, error class group
  • Configuration types: DefaultsConfig, UpdatedAtConfig, IgnoreConfig, AutoincrementConfig, MapConfig, etc.
  • Controller types: Argument and return value types for all methods