Type-Safe ORM in TypeScript
Building an ORM (Object-Relational Mapper) allows developers to interact with databases using object-oriented paradigms, abstracting away the complexities of raw SQL queries. This challenge asks you to implement a simplified, type-safe ORM in TypeScript, focusing on core functionalities like defining models, querying, and creating records. This will solidify your understanding of TypeScript's type system and its application in building robust data access layers.
Problem Description
You are tasked with creating a basic type-safe ORM. The ORM should allow you to define data models with TypeScript types, and then provide methods for querying and creating records based on these models. The ORM will interact with an in-memory data store (simulated database) for simplicity.
What needs to be achieved:
- Model Definition: Create a mechanism to define data models using TypeScript types. The model definition should include the type of each field.
- Data Storage: Implement an in-memory data store to hold the records. This can be a simple array of objects.
- Querying: Provide a
findmethod that allows querying records based on a filter object. The filter object should use exact matches for simplicity. - Creation: Provide a
createmethod that allows creating new records based on the model definition. - Type Safety: Ensure that all operations are type-safe, meaning that the ORM should enforce the types defined in the model.
Key Requirements:
- The ORM should be generic, allowing it to work with different data models.
- The
findmethod should return an array of objects that match the filter criteria. - The
createmethod should add a new record to the data store. - Type safety is paramount. The compiler should catch type errors when interacting with the ORM.
Expected Behavior:
- When defining a model, the ORM should infer the types of the fields.
- When querying, the filter object should have keys that match the model's fields, and the values should match the field types.
- When creating, the provided object should conform to the model's type.
Edge Cases to Consider:
- Empty filter object: Should return all records.
- No matching records: Should return an empty array.
- Invalid filter values: Should return an empty array (no error thrown, just no results).
- Missing fields during creation: Should result in a TypeScript compile-time error.
- Incorrect field types during creation: Should result in a TypeScript compile-time error.
Examples
Example 1:
interface User {
id: number;
name: string;
age: number;
}
const users: User[] = [
{ id: 1, name: "Alice", age: 30 },
{ id: 2, name: "Bob", age: 25 },
{ id: 3, name: "Charlie", age: 35 },
];
const orm = new ORM<User>(users);
const alice = orm.find({ name: "Alice" });
console.log(alice); // Output: [{ id: 1, name: "Alice", age: 30 }]
const youngUsers = orm.find({ age: 25 });
console.log(youngUsers); // Output: [{ id: 2, name: "Bob", age: 25 }]
const noMatch = orm.find({ city: "New York" });
console.log(noMatch); // Output: []
Example 2:
interface Product {
id: number;
name: string;
price: number;
}
const products: Product[] = [
{ id: 101, name: "Laptop", price: 1200 },
{ id: 102, name: "Mouse", price: 25 },
];
const productOrm = new ORM<Product>(products);
const laptop = productOrm.find({ name: "Laptop" });
console.log(laptop); // Output: [{ id: 101, name: "Laptop", price: 1200 }]
productOrm.create({ id: 103, name: "Keyboard", price: 75 });
console.log(productOrm.data); // Output: [{ id: 101, name: "Laptop", price: 1200 }, { id: 102, name: "Mouse", price: 25 }, { id: 103, name: "Keyboard", price: 75 }]
Example 3: (Edge Case - Empty Filter)
interface Book {
id: number;
title: string;
author: string;
}
const books: Book[] = [
{ id: 201, title: "The Lord of the Rings", author: "J.R.R. Tolkien" },
{ id: 202, title: "Pride and Prejudice", author: "Jane Austen" },
];
const bookOrm = new ORM<Book>(books);
const allBooks = bookOrm.find({});
console.log(allBooks); // Output: [{ id: 201, title: "The Lord of the Rings", author: "J.R.R. Tolkien" }, { id: 202, title: "Pride and Prejudice", author: "Jane Austen" }]
Constraints
- Data Store: The data store must be an in-memory array. No external database connections are allowed.
- Querying: The
findmethod should only support exact matches for filter criteria. No complex queries (e.g., LIKE, >, <) are required. - Model ID: Assume that the
idfield is always a number and is unique within the data store. Thecreatemethod should automatically assign a new unique ID. - Performance: Performance is not a primary concern for this challenge. Focus on correctness and type safety.
- Error Handling: Do not implement explicit error handling (e.g., try/catch blocks). Rely on TypeScript's type system to catch errors.
Notes
- Start by defining the
ORMclass and its generic type parameter. - Consider using utility types like
Record<string, any>to represent the filter object. - Think carefully about how to enforce type safety when querying and creating records.
- The
createmethod should generate a new unique ID for each record. A simple incrementing counter is sufficient. - This is a simplified ORM. Do not attempt to implement features like relationships, joins, or transactions. The focus is on demonstrating type safety and basic CRUD operations.