UNPKG

@aradox/multi-orm

Version:

Type-safe ORM with multi-datasource support, row-level security, and Prisma-like API for PostgreSQL, SQL Server, and HTTP APIs

602 lines (456 loc) 13.7 kB
# CRUD Operations Guide ## Overview This guide covers all Create, Read, Update, Delete (CRUD) operations available in the ORM. All operations provide full TypeScript type safety and IntelliSense support. ## Table of Contents 1. [CREATE Operations](#create-operations) 2. [READ Operations](#read-operations) 3. [UPDATE Operations](#update-operations) 4. [DELETE Operations](#delete-operations) 5. [UPSERT Operations](#upsert-operations) 6. [COUNT Operations](#count-operations) 7. [IntelliSense Features](#intellisense-features) --- ## CREATE Operations ### Create Single Record Creates a single record and returns the created object with all fields. ```typescript const newCustomer = await client.Customers.create({ data: { Name: 'John Doe', Email: 'john@example.com', PostId: null, IsActive: true, CreatedAt: null // Use null for ODBC compatibility } }); console.log(`Created customer ID: ${newCustomer.Id}`); // Id is auto-generated by the database (IDENTITY column) ``` **IntelliSense Features:** - Hover over `data` to see all required fields marked with `(required)` - Optional fields are marked with `(optional)` and use `?:` - Id field is omitted (auto-generated) ### Create with Partial Return (SELECT) Create a record but only return specific fields. ```typescript const newProduct = await client.Products.create({ data: { Name: 'Laptop', Price: 1299.99, StockQuantity: 50, Description: 'High-performance laptop' }, select: { Id: true, Name: true, Price: true } }); // Only Id, Name, Price are available console.log(newProduct.Id); // Available console.log(newProduct.Price); // Available // console.log(newProduct.Description); // Type error - not selected ``` **IntelliSense Features:** - TypeScript prevents accessing non-selected fields - Hover shows only the selected field types ### Create Many (Bulk Insert) Insert multiple records in a single operation. ```typescript const employees = await client.Employees.createMany({ data: [ { FirstName: 'Alice', LastName: 'Smith', Email: 'alice@company.com', Position: 'Developer', Salary: 85000, HireDate: null }, { FirstName: 'Bob', LastName: 'Johnson', Email: 'bob@company.com', Position: 'Designer', Salary: 75000, HireDate: null } ] }); console.log(`Created ${employees.count} employees`); ``` **Note:** `createMany` returns `{ count: number }` instead of the created records. --- ## READ Operations ### Find Unique Retrieve a single record by unique identifier. ```typescript const customer = await client.Customers.findUnique({ where: { Id: 1 } }); if (customer) { console.log(`Found: ${customer.Name}`); } ``` ### Find Many Retrieve multiple records with filtering, sorting, pagination. ```typescript const products = await client.Products.findMany({ where: { Price: { gte: 100, lte: 1000 } }, orderBy: { Price: 'asc' }, take: 10, skip: 0 }); ``` ### Find with Relations (Include) Fetch records with related data from other datasources. ```typescript const customers = await client.Customers.findMany({ include: { orders: true, // Include all Order fields posts: { // Include Post from HTTP API select: { title: true, body: true } } } }); ``` --- ## UPDATE Operations ### Update Single Record Update a single record and return the updated object. ```typescript const updatedCustomer = await client.Customers.update({ where: { Id: 5 }, data: { Name: 'Updated Name', Email: 'newemail@example.com' } }); console.log(`Updated to: ${updatedCustomer.Name}`); ``` **IntelliSense Features:** - Hover over `data` fields to see `(optional)` - all fields are optional for updates - Only provide fields you want to change ### Update with Partial Return (SELECT) Update a record but only return specific fields. ```typescript const updatedProduct = await client.Products.update({ where: { Id: 3 }, data: { Price: 149.99, StockQuantity: 75 }, select: { Id: true, Name: true, Price: true, StockQuantity: true } }); // Only selected fields available console.log(`${updatedProduct.Name}: $${updatedProduct.Price}`); ``` ### Update Many (Bulk Update) Update multiple records matching a condition. ```typescript const result = await client.Employees.updateMany({ where: { Position: 'Developer', Salary: { lt: 90000 } }, data: { Salary: 95000 } }); console.log(`Updated ${result.count} employees`); ``` **Note:** `updateMany` returns `{ count: number }` instead of the updated records. --- ## DELETE Operations ### Delete Single Record Delete a single record and return the deleted object. ```typescript const deletedCustomer = await client.Customers.delete({ where: { Id: 5 } }); console.log(`Deleted: ${deletedCustomer.Name}`); ``` **SQL Server Note:** Uses `OUTPUT DELETED.*` to return the deleted row. ### Delete with Partial Return (SELECT) Delete a record but only return specific fields. ```typescript const deletedProduct = await client.Products.delete({ where: { Id: 6 }, select: { Id: true, Name: true, Price: true } }); console.log(`Deleted ${deletedProduct.Name} ($${deletedProduct.Price})`); ``` ### Delete Many (Bulk Delete) Delete multiple records matching a condition. ```typescript const result = await client.Customers.deleteMany({ where: { Email: { contains: '@test.com' } } }); console.log(`Deleted ${result.count} test customers`); ``` **Note:** `deleteMany` returns `{ count: number }` instead of the deleted records. --- ## UPSERT Operations Create a record if it doesn't exist, or update it if it does. ```typescript const product = await client.Products.upsert({ where: { Name: 'Unique Product' }, // Use unique field for lookup create: { Name: 'Unique Product', Price: 199.99, StockQuantity: 100, Description: 'A unique product' }, update: { Price: 249.99, StockQuantity: 150 } }); console.log(`Upserted product ID: ${product.Id}`); ``` **Important:** - Use a unique field (not Id) in the `where` clause - Id fields are auto-generated, so you won't know the Id before creation - Common unique fields: Email, Name, SKU, etc. **Behavior:** 1. First upsert: Record doesn't exist executes `create` block 2. Second upsert: Record exists executes `update` block --- ## COUNT Operations Count records matching a condition. ```typescript // Count all customers const totalCustomers = await client.Customers.count(); console.log(`Total customers: ${totalCustomers}`); // Count with WHERE clause const activeCustomers = await client.Customers.count({ where: { IsActive: true } }); console.log(`Active customers: ${activeCustomers}`); // Count test records const testProducts = await client.Products.count({ where: { Name: { startsWith: 'Test' } } }); console.log(`Test products: ${testProducts}`); ``` --- ## IntelliSense Features ### CreateInput Type Hints When hovering over fields in `create()` operations: ```typescript const customer = await client.Customers.create({ data: { Name, // Hover: /** @type {string} (required) */ Email, // Hover: /** @type {string} (required) */ PostId, // Hover: /** @type {number | null} (optional) */ IsActive, // Hover: /** @type {boolean} (required) */ CreatedAt // Hover: /** @type {Date | null} (optional) */ } }); ``` **Features:** - `(required)` - Must be provided - `(optional)` - Can be omitted, uses `?:` syntax - Type shown: `string`, `number`, `boolean`, `Date`, etc. - `null` allowed for nullable fields ### UpdateInput Type Hints When hovering over fields in `update()` operations: ```typescript const customer = await client.Customers.update({ where: { Id: 5 }, data: { Name, // Hover: /** @type {string} (optional) */ Email // Hover: /** @type {string} (optional) */ } }); ``` **Features:** - All fields marked `(optional)` - Only provide fields you want to change - Type safety prevents typos and wrong types ### WhereUniqueInput Type Hints ```typescript await client.Customers.findUnique({ where: { Id: 1 } // Hover shows all unique/scalar fields available }); ``` **Features:** - Shows all scalar fields (not just @id fields) - Allows lookup by any unique column - Type safety ensures correct field types ### Type Inference for SELECT ```typescript const partial = await client.Customers.findUnique({ where: { Id: 1 }, select: { Name: true, Email: true } }); // TypeScript knows only Name and Email are available partial.Name; // OK partial.Email; // OK partial.Id; // Type error - not selected ``` **Features:** - TypeScript infers exact type based on selected fields - Prevents runtime errors from accessing unselected fields - Works with nested includes too --- ## Best Practices ### 1. Use SELECT for Performance Only fetch fields you need to reduce payload size: ```typescript const customers = await client.Customers.findMany({ select: { Id: true, Name: true, Email: true // Omit large fields like Description } }); ``` ### 2. Use Unique Fields for Upsert Don't use Id in upsert WHERE clause (it's auto-generated): ```typescript // BAD - Id won't exist on first create await client.Products.upsert({ where: { Id: 999 }, // This Id might not exist create: { ... }, update: { ... } }); // GOOD - Use unique business key await client.Products.upsert({ where: { SKU: 'PROD-123' }, // Business identifier create: { ... }, update: { ... } }); ``` ### 3. Handle Null for Date Fields Use `null` instead of `new Date()` for ODBC compatibility: ```typescript await client.Customers.create({ data: { Name: 'John', CreatedAt: null // ODBC-compatible // CreatedAt: new Date() // May fail with ODBC driver } }); ``` ### 4. Use Bulk Operations for Performance Prefer `createMany`, `updateMany`, `deleteMany` for multiple records: ```typescript // BAD - Multiple round trips for (const item of items) { await client.Products.create({ data: item }); } // GOOD - Single bulk insert await client.Products.createMany({ data: items }); ``` ### 5. Cleanup Test Data Properly Use unique identifiers (Email, Name) for cleanup instead of hardcoded Ids: ```typescript // GOOD - Cleanup by unique field await client.Customers.deleteMany({ where: { Email: { contains: '@test.com' } } }); // BAD - Hardcoded Ids might not exist await client.Customers.deleteMany({ where: { Id: { in: [9999, 9998, 9997] } } }); ``` --- ## Testing CRUD Operations See `Example/test-crud-operations.ts` for a comprehensive test suite covering: 1. CREATE single record 2. CREATE with SELECT 3. CREATE MANY bulk insert 4. READ verification 5. UPDATE single record 6. UPDATE with SELECT 7. UPDATE MANY bulk update 8. UPSERT create or update 9. COUNT records 10. DELETE single record 11. DELETE with SELECT 12. DELETE MANY bulk delete 13. IntelliSense verification 14. Complex CRUD lifecycle Run tests with: ```bash npm run build $env:LOG_LEVEL='off'; node dist/Example/test-crud-operations.js ``` --- ## Schema Requirements For CRUD operations to work properly, ensure: 1. **Auto-increment Ids**: Add `@default(autoincrement())` to @id fields ``` model Customers { Id Int @id @default(autoincrement()) // Correct Name String ... } ``` 2. **Nullable Fields**: Mark optional fields as nullable ``` model Customers { PostId Int? // Optional foreign key CreatedAt Date? // Optional timestamp ... } ``` 3. **Unique Constraints**: Define unique fields for upsert operations ``` model Products { Id Int @id @default(autoincrement()) SKU String @unique // Business identifier ... } ``` --- ## Troubleshooting ### "Cannot insert explicit value for identity column" **Cause:** Trying to insert an Id when IDENTITY_INSERT is OFF **Solution:** Add `@default(autoincrement())` to your @id field and regenerate types: ```bash npm run generate npm run build ``` ### "Parameter markers do not equal bind values" **Cause:** MSSQL adapter parameter binding issue **Solution:** Ensure you're using the latest version with fixed `update()` and `delete()` methods. The adapter now properly converts named parameters to positional `?` style. ### "Type error: Property does not exist" **Cause:** Trying to access a field that wasn't selected **Solution:** Either add the field to your `select` clause or remove the `select` to get all fields: ```typescript // Option 1: Add field to select select: { Id: true, Name: true, Email: true } // Option 2: Remove select to get all fields // (no select clause) ``` --- ## Related Guides - [Type Hints Guide](./TYPE_HINTS_GUIDE.md) - IntelliSense features - [Schema DSL Guide](../Plan.txt) - Schema syntax reference - [Multi-Database Guide](../README.md) - Cross-datasource queries --- **Last Updated:** 2025-11-12 **Version:** 1.0.0