Hone logo
Hone
Problems

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:

  1. Model Definition: Create a mechanism to define data models using TypeScript types. The model definition should include the type of each field.
  2. Data Storage: Implement an in-memory data store to hold the records. This can be a simple array of objects.
  3. Querying: Provide a find method that allows querying records based on a filter object. The filter object should use exact matches for simplicity.
  4. Creation: Provide a create method that allows creating new records based on the model definition.
  5. 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 find method should return an array of objects that match the filter criteria.
  • The create method 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 find method should only support exact matches for filter criteria. No complex queries (e.g., LIKE, >, <) are required.
  • Model ID: Assume that the id field is always a number and is unique within the data store. The create method 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 ORM class 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 create method 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.
Loading editor...
typescript