Skip to main content

Type System

Sentro automatically generates TypeScript types from your database schema, providing compile-time safety for all database operations.

Auto-Generated Types File

When you run connector.start(), it introspects your database and generates a dbmanager-types.ts file with complete type definitions.

File Location

./dbmanager-types.ts

Generated Types

TableName

Union type of all table names in your database:

export type TableName =
  | "users"
  | "posts"
  | "comments"
  | "orders"
  // ... all your tables

ColumnsByTable

Column names for each table:

export type ColumnsByTable = {
  users: "id" | "email" | "name" | "created_at" | "updated_at";
  posts: "id" | "title" | "content" | "userId" | "created_at";
  comments: "id" | "text" | "postId" | "userId" | "created_at";
  // ...
}

Row Types

Complete row type for each table with all columns and their types:

export type Row_Users = {
  id: number;
  email: string;
  name: string;
  age: number | null;
  isActive: boolean;
  role: "admin" | "user";
  created_at: string;
  updated_at: string;
}

export type Row = {
  users: Row_Users;
  posts: Row_Posts;
  comments: Row_Comments;
  // ...
}

Insert Types

Types for inserting new records (excludes auto-generated fields):

export type Insert_Users = {
  email: string;
  name: string;
  age?: number | null;
  isActive?: boolean;
  role?: "admin" | "user";
}

export type Insert = {
  users: Insert_Users;
  posts: Insert_Posts;
  // ...
}

Update Types

Types for updating records (all fields optional):

export type Update_Users = {
  where: {
    id: number;
  } | {
    email: string;
  };
  patch: {
    email?: string;
    name?: string;
    age?: number | null;
    isActive?: boolean;
    role?: "admin" | "user";
  };
}

export type Update = {
  users: Update_Users;
  posts: Update_Posts;
  // ...
}

Delete Types

Types for deleting records:

export type Delete_Users = {
  where: {
    id: number;
  } | {
    email: string;
  };
  single: boolean;
}

export type Delete = {
  users: Delete_Users;
  // ...
}

Primary Key Types

Primary key column name for each table:

export type PK = {
  users: "id";
  posts: "id";
  comments: "id";
  // ...
}

Relations

Foreign key relationships for each table:

export type Relations_Posts = {
  user: Row_Users;
}

export type Relations_Comments = {
  post: Row_Posts;
  user: Row_Users;
}

export type Relations = {
  users: Relations_Users;
  posts: Relations_Posts;
  comments: Relations_Comments;
  // ...
}

Global Namespace

Types are also exported in a global namespace for easy access:

declare global {
  namespace DBManagerSchema {
    type TableName = /* ... */;

    // Get list response type
    type ListBy<TN extends TableName> = {
      rows: RowBy<TN>[];
      total: number;
    }

    // Get row type by table name
    type RowBy<TN extends TableName> = Row[TN];

    // Get insert type by table name
    type InsertBy<TN extends TableName> = Insert[TN];

    // Get update type by table name
    type UpdateBy<TN extends TableName> = Update[TN];

    // Get delete type by table name
    type DeleteBy<TN extends TableName> = Delete[TN];

    // Get primary key column name
    type PKBy<TN extends TableName> = PK[TN];

    // Get relations
    type RelationsBy<TN extends TableName> = Relations[TN];
  }
}

Using Generated Types

In Customizers

import { ModelCustomizer } from "@sentrodb/connector-node/dist/inc/customizers/modelCustomizer";

connector.customize(() => {
  const c = new ModelCustomizer("users");

  c.onBefore("CREATE", (payload: DBManagerSchema.InsertBy<"users">, ctx) => {
    // payload is typed as Insert_Users
    payload.email = payload.email.toLowerCase();
    payload.isActive = true;
    return payload;
  });

  c.onAfter("READ", (result: DBManagerSchema.ListBy<"users">, ctx) => {
    // result.rows is typed as Row_Users[]
    result.rows.forEach(user => {
      console.log(user.email); // Type-safe access
    });
    return result;
  });

  return c;
});

In Actions

connector.customize(() => {
  const c = new ModelCustomizer("users");

  c.addAction({
    type: "detail",
    id: "send-email",
    label: "Send Email",
    callback: async (
      request,
      record: DBManagerSchema.RowBy<"users">,
      db
    ) => {
      // record is typed as Row_Users
      console.log(record.email); // Type-safe

      // Send email...
      return { success: true };
    }
  });

  return c;
});

In Database Operations

// Insert with type safety
const newUser: DBManagerSchema.InsertBy<"users"> = {
  email: "user@example.com",
  name: "John Doe",
  isActive: true
};

await db.insert({
  table: "users",
  data: newUser
});

// Update with type safety
const updateData: DBManagerSchema.UpdateBy<"users"> = {
  where: { id: 1 },
  patch: { name: "Jane Doe" }
};

await db.update({
  table: "users",
  ...updateData
});

// Read with type safety
const result = await db.get({
  table: "users",
  where: { isActive: true }
});

const users: DBManagerSchema.RowBy<"users">[] = result.rows;

Enum Types

Database enums are automatically converted to TypeScript union types:

// If you have an enum column in your database:
// CREATE TYPE user_role AS ENUM ('admin', 'user', 'moderator');

// Generated type:
export type Row_Users = {
  id: number;
  email: string;
  role: "admin" | "user" | "moderator"; // Enum as union type
  // ...
}

Nullable Fields

Nullable database columns are typed as optional or union with null:

export type Row_Users = {
  id: number;
  email: string;
  age: number | null;        // Nullable column
  bio: string | null;        // Nullable column
  // ...
}

export type Insert_Users = {
  email: string;
  age?: number | null;       // Optional in insert
  bio?: string | null;       // Optional in insert
}

JSON Columns

JSON columns are typed as any or Record<string, any>:

export type Row_Users = {
  id: number;
  metadata: Record<string, any>;  // JSON column
  settings: any;                  // JSONB column
}
Tip: You can create custom type guards or use libraries like Zod to validate JSON column data at runtime.

Type Helpers

Extract Single Row Type

type User = DBManagerSchema.RowBy<"users">;

const user: User = {
  id: 1,
  email: "user@example.com",
  name: "John Doe",
  // ...
};

Extract Insert Type

type CreateUser = DBManagerSchema.InsertBy<"users">;

const newUser: CreateUser = {
  email: "user@example.com",
  name: "John Doe"
};

Extract Column Names

type UserColumns = ColumnsByTable["users"];
// "id" | "email" | "name" | "created_at" | ...

const column: UserColumns = "email"; // Type-safe

Regenerating Types

Types are automatically regenerated when you call connector.start(). If you modify your database schema, restart your application to regenerate the types.

// After database schema changes
await connector.start(); // Regenerates dbmanager-types.ts
Important: Don't manually edit the generated types file. Changes will be overwritten when types are regenerated.

TypeScript Configuration

Make sure your tsconfig.json includes the generated types:

{
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": [
    "src/**/*",
    "dbmanager-types.ts"  // Include generated types
  ]
}

Best Practices

  • Always use the generated types for type safety
  • Regenerate types after schema changes
  • Use type helpers for cleaner code
  • Leverage TypeScript's IntelliSense for better DX
  • Don't manually modify generated types
  • Commit the types file to version control
  • Use strict TypeScript configuration
Next Steps: Check out Examples for complete real-world implementation examples.