@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
Markdown
# 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