Skip to main content
This covers common TypeScript type-related errors in Sonamu and how to resolve them.

Reserved Keywords Collision

Symptoms

class UserModelClass extends BaseModel {
  async delete(id: number) {  // ❌
    // delete is a JavaScript reserved keyword
  }
}
Runtime error:
Unexpected token 'delete'
Or during Sonamu sync:
Error: Reserved keyword 'delete' cannot be used as method name

Cause

Used JavaScript/TypeScript reserved keywords as method or property names.

Solution

Avoid using reserved keywords or use different names:
// ❌ Using reserved keywords
async delete() { }
async switch() { }
async return() { }

// βœ… Use different names
async remove() { }
async del() { }
async toggle() { }
async getReturn() { }
72 reserved keywords validated by Sonamu:
const RESERVED_KEYWORDS = [
  "break", "case", "catch", "class", "const", "continue", "debugger",
  "default", "delete", "do", "else", "enum", "export", "extends",
  "false", "finally", "for", "function", "if", "import", "in",
  "instanceof", "new", "null", "return", "super", "switch",
  "this", "throw", "true", "try", "typeof", "var", "void",
  "while", "with", "yield", "let", "static", "implements",
  "interface", "package", "private", "protected", "public",
  "await", "abstract", "as", "asserts", "any", "async",
  "boolean", "constructor", "declare", "get", "infer", "is",
  "keyof", "module", "namespace", "never", "readonly", "require",
  "number", "object", "set", "string", "symbol", "type",
  "undefined", "unique", "unknown", "from", "of"
];

Type Inference Failure

Symptoms

const users = await UserModel.findMany();
// Type: any[]  ❌ Type not properly inferred

Causes

  1. Sonamu syncer didn’t run properly
  2. .generated file is outdated
  3. TypeScript server cache issue

Solutions

1. Re-run Syncer

pnpm sonamu sync

2. Restart TypeScript Server

VSCode:
Command Palette (Cmd+Shift+P)
> TypeScript: Restart TS Server

3. Check Generated Files

// src/application/sonamu.generated.ts
export type UserSave = {
  id?: number;
  email: string;
  name: string;
  // ...
};
If file is missing or outdated, re-run sync.

BaseModel Method Type Error

Symptoms

class UserModelClass extends BaseModel {
  async findByEmail(email: string) {
    return this.findOne({ email });
    // Error: Property 'findOne' does not exist on type 'UserModelClass'
  }
}

Cause

BaseModel’s auto-generated method types are not properly applied.

Solutions

1. Check entity.json

{
  "properties": {
    "id": { "type": "id" },
    "email": { "type": "string" },
    "name": { "type": "string" }
  }
}

2. Check Model Class Definition

class UserModelClass extends BaseModel<User, UserSave> {
  entityName = "User" as const;
  // ...
}

export const UserModel = new UserModelClass();

3. Regenerate Types with Syncer

pnpm sonamu sync

Union Type Error

Symptoms

type OrderStatus = "pending" | "processing" | "completed";

class OrderModelClass extends BaseModel {
  async updateStatus(id: number, status: string) {  // ❌
    // status should be OrderStatus
  }
}
Type safety is not guaranteed.

Solution

Specify explicit type:
type OrderStatus = "pending" | "processing" | "completed";

class OrderModelClass extends BaseModel {
  async updateStatus(id: number, status: OrderStatus) {  // βœ…
    return this.updateOne(
      { id },
      { status }
    );
  }
}

Zod Schema Type Mismatch

Symptoms

const UserSchema = z.object({
  email: z.string(),
  age: z.number()
});

@api()
async createUser(email: string, age: string) {  // ❌ age should be number
  // ...
}

Cause

API method parameter type doesn’t match Zod schema.

Solutions

1. Fix Parameter Type

@api()
async createUser(email: string, age: number) {  // βœ…
  // Sonamu automatically validates with Zod
}

2. Use Explicit Zod Schema

const CreateUserSchema = z.object({
  email: z.string().email(),
  age: z.number().int().positive()
});

@api({ schema: CreateUserSchema })
async createUser(data: z.infer<typeof CreateUserSchema>) {
  // Type safety guaranteed
}

Intersection Type Error

Symptoms

type WithTimestamps = {
  created_at: Date;
  updated_at: Date;
};

type User = {
  id: number;
  email: string;
} & WithTimestamps;  // Intersection type

// Type displayed complexly

Solution

From Sonamu 0.7.29+, intersection/union types are automatically wrapped with parentheses:
// Generated type
type User = {
  id: number;
  email: string;
} & (WithTimestamps);  // Parentheses added for clarity
Re-run sync after update:
pnpm add sonamu@latest
pnpm sonamu sync

Template Literal Type Error

Symptoms

type EventName = `user:${string}`;

// Error when using template literal type with Zod v4

Cause

Zod v4 changed how template literal types are handled.

Solution

Sonamu automatically handles backslash escaping:
// entity.json
{
  "columns": {
    "event_name": {
      "type": "string",
      "literalType": "user:${string}"  // Automatically handled
    }
  }
}
Generated Zod schema:
z.literal(`user:$\{string}`)  // Properly escaped

Circular Reference Type Error

Symptoms

// user.model.ts
export class UserModelClass extends BaseModel {
  // ...
}
export type User = { ... posts: Post[] };

// post.model.ts
export class PostModelClass extends BaseModel {
  // ...
}
export type Post = { ... author: User };

// Error: Circular dependency detected

Cause

Two entities reference each other, causing circular dependency.

Solutions

1. Use Type-only Import

// user.model.ts
import type { Post } from "../post/post.model";

export type User = {
  id: number;
  email: string;
  posts: Post[];
};
// post.model.ts
import type { User } from "../user/user.model";

export type Post = {
  id: number;
  title: string;
  author: User;
};

2. Create Common Types File

// types/index.ts
export type { User } from "../application/user/user.model";
export type { Post } from "../application/post/post.model";
// Use in other files
import type { User, Post } from "@/types";

File Type Error (@upload)

Symptoms

@api()
@upload({ mode: "single" })
async uploadFile() {
  const { files } = Sonamu.getContext();
  const file = files?.[0]; // Use first file
  // Type: UploadedFile | undefined

  const buffer = file.buffer;  // ❌ Property 'buffer' does not exist
}

Cause

UploadedFile class doesn’t have a buffer property. You need to use the toBuffer() method.

Solution

@api()
@upload({ mode: "single" })
async uploadFile() {
  const { files } = Sonamu.getContext();
  const file = files?.[0]; // Use first file

  if (!file) {
    throw new BadRequestException("File is required");
  }

  // βœ… Use toBuffer() method
  const buffer = await file.toBuffer();

  // Or save file
  const url = await file.saveToDisk(`uploads/${file.filename}`);

  return { url, size: file.size };
}

Type Guard Error

Symptoms

function isUser(value: any): boolean {  // ❌
  return value && typeof value.id === "number";
}

if (isUser(data)) {
  console.log(data.email);  // Error: Property 'email' does not exist
}

Cause

Type guard function doesn’t perform type narrowing.

Solution

Use type predicate:
function isUser(value: any): value is User {  // βœ…
  return (
    value &&
    typeof value.id === "number" &&
    typeof value.email === "string"
  );
}

if (isUser(data)) {
  console.log(data.email);  // βœ… Type safe
}

Generic Type Inference Failure

Symptoms

async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  return response.json();  // Type: any
}

const users = await fetchData("/api/users");
// Type: unknown  ❌

Solution

Specify explicit type:
const users = await fetchData<User[]>("/api/users");
// Type: User[]  βœ…
Or type validation:
async function fetchData<T>(
  url: string,
  validator: (data: unknown) => data is T
): Promise<T> {
  const response = await fetch(url);
  const data = await response.json();

  if (!validator(data)) {
    throw new Error("Invalid data format");
  }

  return data;
}

// Usage
const users = await fetchData("/api/users", isUserArray);