-
-
Notifications
You must be signed in to change notification settings - Fork 0
TypeScript Types
Miguel Guevara edited this page Jan 9, 2026
·
1 revision
Learn how to use Dynamite's TypeScript types for type-safe development.
Dynamite provides specialized TypeScript types to ensure type safety throughout your application:
- CreationOptional - Optional during create, required after
- NonAttribute - Excluded from database (computed/relations)
- InferAttributes - Extract DB attributes from model
- InferRelations - Extract relations from model
- CreateInput - Input type for create()
- UpdateInput - Input type for update()
Use CreationOptional<T> for fields that are optional during creation but exist after:
class User extends Table<User> {
// Optional during create (auto-generated)
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>;
// Required during create
declare name: string;
// Optional during create (has default)
@Default(() => "customer")
declare role: CreationOptional<string>;
// Optional during create (auto-set)
@CreatedAt()
declare created_at: CreationOptional<string>;
}
// Type-safe creation
const user = await User.create({
name: "John Doe"
// id, role, created_at are optional
});
// After creation, all fields exist
console.log(user.id); // string (not undefined)
console.log(user.name); // string
console.log(user.role); // string (not undefined)
console.log(user.created_at); // string (not undefined)Use CreationOptional<T> for fields with:
-
@Default()decorator -
@CreatedAt()decorator -
@UpdatedAt()decorator -
@DeleteAt()decorator - Auto-generated values
class Post extends Table<Post> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>; // ✓
declare title: string; // ✗ Required
@Default(() => "draft")
declare status: CreationOptional<string>; // ✓
@Default(() => 0)
declare views: CreationOptional<number>; // ✓
@CreatedAt()
declare created_at: CreationOptional<string>; // ✓
@UpdatedAt()
declare updated_at: CreationOptional<string>; // ✓
}Use NonAttribute<T> for properties that are not stored in the database:
class User extends Table<User> {
declare first_name: string;
declare last_name: string;
// Computed property - not stored
declare full_name: NonAttribute<string>;
constructor(data?: any) {
super(data);
Object.defineProperty(this, 'full_name', {
get: () => `${this.first_name} ${this.last_name}`,
enumerable: true
});
}
}
const user = await User.create({
first_name: "John",
last_name: "Doe"
});
console.log(user.full_name); // "John Doe" (computed, not in DB)class User extends Table<User> {
@PrimaryKey()
declare id: string;
// Relations are NOT stored in DB
@HasMany(() => Post, "user_id")
declare posts: NonAttribute<Post[]>;
@HasOne(() => Profile, "user_id")
declare profile: NonAttribute<Profile | null>;
@ManyToMany(() => Role, "user_roles", "user_id", "role_id")
declare roles: NonAttribute<Role[]>;
}class User extends Table<User> {
@PrimaryKey()
declare id: string;
declare email: string;
// Method - not stored
declare is_admin: NonAttribute<() => boolean>;
constructor(data?: any) {
super(data);
this.is_admin = () => this.email.endsWith("@admin.com");
}
}Extract database attributes from a model:
import { InferAttributes } from "@arcaelas/dynamite";
class User extends Table<User> {
@PrimaryKey()
declare id: string;
declare name: string;
declare email: string;
@HasMany(() => Post, "user_id")
declare posts: NonAttribute<Post[]>; // Excluded
}
// Type contains only: { id, name, email }
type UserAttributes = InferAttributes<User>;
function processUserData(data: UserAttributes) {
console.log(data.id); // ✓
console.log(data.name); // ✓
console.log(data.email); // ✓
console.log(data.posts); // ✗ Error: posts doesn't exist
}Extract relations from a model:
import { InferRelations } from "@arcaelas/dynamite";
class User extends Table<User> {
@PrimaryKey()
declare id: string;
declare name: string;
@HasMany(() => Post, "user_id")
declare posts: NonAttribute<Post[]>;
@HasOne(() => Profile, "user_id")
declare profile: NonAttribute<Profile | null>;
}
// Type contains only: { posts, profile }
type UserRelations = InferRelations<User>;
function processRelations(relations: UserRelations) {
console.log(relations.posts); // ✓ Post[]
console.log(relations.profile); // ✓ Profile | null
console.log(relations.id); // ✗ Error: id doesn't exist
}Type-safe input for create() method:
import { CreateInput } from "@arcaelas/dynamite";
class User extends Table<User> {
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>;
declare name: string;
declare email: string;
@Default(() => "customer")
declare role: CreationOptional<string>;
@CreatedAt()
declare created_at: CreationOptional<string>;
}
// Type: { name: string, email: string, id?: string, role?: string }
type UserCreateInput = CreateInput<User>;
async function createUser(data: UserCreateInput) {
return await User.create(data);
}
// Valid
createUser({ name: "John", email: "john@example.com" });
createUser({ name: "Jane", email: "jane@example.com", role: "admin" });
// Invalid
createUser({ name: "John" }); // ✗ Error: email required
createUser({ email: "john@example.com" }); // ✗ Error: name requiredType-safe input for update() method:
import { UpdateInput } from "@arcaelas/dynamite";
class User extends Table<User> {
@PrimaryKey()
declare id: string;
declare name: string;
declare email: string;
}
// Type: Partial<{ name: string, email: string }>
type UserUpdateInput = UpdateInput<User>;
async function updateUser(id: string, data: UserUpdateInput) {
return await User.update(data, { id });
}
// Valid
updateUser("123", { name: "New Name" });
updateUser("123", { email: "new@example.com" });
updateUser("123", { name: "New Name", email: "new@example.com" });
updateUser("123", {}); // Also valid (no changes)
// Invalid
updateUser("123", { id: "456" }); // ✗ Error: can't update primary keyclass User extends Table<User> {
// Primary key (required after creation)
@PrimaryKey()
@Default(() => crypto.randomUUID())
declare id: CreationOptional<string>;
// Required fields
declare name: string;
declare email: string;
// Optional fields with defaults
@Default(() => "customer")
declare role: CreationOptional<"customer" | "admin" | "moderator">;
@Default(() => true)
declare active: CreationOptional<boolean>;
// Timestamps
@CreatedAt()
declare created_at: CreationOptional<string>;
@UpdatedAt()
declare updated_at: CreationOptional<string>;
// Soft delete
@DeleteAt()
declare deleted_at: CreationOptional<string | null>;
// Relations
@HasMany(() => Post, "user_id")
declare posts: NonAttribute<Post[]>;
// Computed
declare is_admin: NonAttribute<boolean>;
constructor(data?: any) {
super(data);
Object.defineProperty(this, 'is_admin', {
get: () => this.role === "admin",
enumerable: false
});
}
}function isUser(value: any): value is User {
return value instanceof User;
}
function isPost(value: any): value is Post {
return value instanceof Post;
}
async function processRecord(record: User | Post) {
if (isUser(record)) {
console.log(record.name); // ✓ Type is User
console.log(record.posts); // ✓ Type is Post[]
} else if (isPost(record)) {
console.log(record.title); // ✓ Type is Post
console.log(record.user); // ✓ Type is User | null
}
}async function findById<T extends Table<T>>(
Model: new () => T,
id: string
): Promise<T | undefined> {
return await Model.first({ id } as any);
}
// Type-safe usage
const user = await findById(User, "123"); // Type: User | undefined
const post = await findById(Post, "456"); // Type: Post | undefined-
Use CreationOptional for fields with:
-
@Default()decorator -
@CreatedAt(),@UpdatedAt()decorators - Auto-generated values
-
-
Use NonAttribute for:
- Computed properties
- Relationships
- Helper methods
- Transient data
-
Leverage type inference:
- Use
InferAttributesfor database operations - Use
InferRelationsfor relation handling - Use
CreateInput/UpdateInputfor API boundaries
- Use
-
Enable strict mode in
tsconfig.json:{ "compilerOptions": { "strict": true, "experimentalDecorators": true } } -
Avoid type assertions:
// ✗ Bad const user = await User.first({ id: "123" }) as User; // ✓ Good const user = await User.first({ id: "123" }); if (user) { // user is User type here }
| Type | Purpose | Usage |
|---|---|---|
CreationOptional<T> |
Optional on create | Fields with @Default, @CreatedAt, etc. |
NonAttribute<T> |
Not stored in DB | Computed properties, relations |
InferAttributes<T> |
Extract DB attributes | Database operations |
InferRelations<T> |
Extract relations | Relation handling |
CreateInput<T> |
Input for create() | API boundaries |
UpdateInput<T> |
Input for update() | API boundaries |
See also:
- Getting Started - Learn the basics
- Decorators - Decorator reference
- Relationships - Type-safe relations