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 Type | TypeScript Type |
|---|---|
Int | number |
Float | number |
Decimal | number |
BigInt | number |
String | string |
Boolean | boolean |
DateTime | Date |
Json | string |
Bytes | string |
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).
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
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 schemagassma.config.ts-- Configuration file
| Option | Description |
|---|---|
--output <path> | Customize the output path |
--with-model | Generate 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)
generatorblock existence checkoutputfield 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
| Option | Description |
|---|---|
--schema <path> | Format only a specific file |
--check | Check 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:
| File | Content |
|---|---|
schema.d.ts | Type definitions (model types, query types, common types) |
schemaClient.js | Client implementation (with auto-injected relation definitions) |
schemaClient.d.ts | Client 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
| Option | Type | Required | Description |
|---|---|---|---|
schema | string | No | Path to the schema file or directory (default: ./gassma) |
datasource.url | string | No | Spreadsheet 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;
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
datasourceblock in the schema (highest priority)datasource.urlingassma.config.ts
Schema Resolution Priority
--schemaoption (highest priority)schemasetting ingassma.config.ts- Default
./gassmadirectory
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(includingFieldRefsupport) - OrderBy types: Sort conditions (relation sort,
_countsort, 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