npm package: @nodeblocks/backend-sdk
Description: Type-safe Nodeblocks backend implementation
Latest version: 0.10.0

# Nodeblocks Backend SDK

Nodeblocks Backend SDK is a comprehensive library for building backend services in Node.js using a functional and compositional approach. Instead of relying on a large, opinionated framework, you construct your application by composing small, independent functions and modules.

At the heart of the SDK is the idea of building systems by putting together smaller parts. This is achieved through a set of primitives and a composition function, allowing you to assemble services from simple building blocks.

## What's Included

The SDK provides a complete set of pre-built services and components for modern applications:

- **🔐 Authentication & Identity Management** - Complete user authentication with OAuth, MFA, and session management
- **💬 Real-time Chat System** - Full-featured chat with channels, messages, subscriptions, and WebSocket streaming
- **🛍️ E-commerce Features** - Product management, orders, categories, and inventory
- **👥 User & Profile Management** - User profiles, avatars, follows, and social features
- **🏢 Organization Management** - Multi-tenant organization support with roles and permissions
- **📁 File Storage** - File upload, management, and CDN integration
- **📍 Location Services** - Address management and location-based features
- **🔧 Dynamic Attributes** - Flexible attribute system for custom data
- **📊 Pagination & Filtering** - Built-in pagination and advanced filtering
- **🔍 Search & Discovery** - Advanced search capabilities across all entities

## Key Features

### 🚀 Production Ready

- **Comprehensive Test Coverage** - Extensive test suite with high coverage
- **TypeScript Support** - Full TypeScript definitions and type safety
- **Error Handling** - Structured error responses with proper HTTP status codes
- **Logging** - Built-in logging with configurable levels and sanitization
- **Validation** - Request validation with JSON Schema and OpenAPI support
- **Security** - Authentication, authorization, input sanitization, and NoSQL injection protection

### 🔧 Developer Experience

- **Functional Composition** - Build complex features from simple, composable functions
- **Declarative API** - Describe what you want, not how to build it
- **Hot Reloading** - Fast development with instant updates
- **Comprehensive Documentation** - Detailed docs with examples for every component
- **Flexible Architecture** - Use pre-built services or compose your own features

### 📊 Enterprise Features

- **Multi-tenancy** - Organization-based data isolation and access control
- **Role-based Access Control** - Fine-grained permissions and authorization
- **Audit Logging** - Track all changes and user actions
- **Pagination** - Built-in pagination for all list endpoints
- **File Management** - Upload, storage, and CDN integration
- **Real-time Communication** - WebSocket support for live updates

## A Functional, Compositional Approach to Backend Services

The core philosophy of the Nodeblocks Backend SDK is that complex systems can be built from simple, pure functions. Rather than creating large classes or objects that hold a lot of state and logic, you define small, focused functions and compose them into more complex ones.

This approach provides:

- **Modularity**: Each piece of your application is small and self-contained, making it easy to understand, test, and reuse.
- **Declarative Style**: You declare what your service _is_, rather than specifying how it should be built step-by-step.
- **Predictability**: With a foundation in functional principles, the behavior of your system is more predictable and easier to reason about.

## Core Concepts

### Services

A **service** is a composable piece of your application that provides HTTP and WebSocket endpoints. It's implemented as Express middleware that you can mount on your Express application. You create a service using the `defService` function, which takes a composed set of features and configurations and returns an Express router ready to be used as middleware. When using WebSockets, pass a `WebSocketServer` to `defService`.

### Composition

Composition is the central pattern in the SDK. The `compose` function (a wrapper around `ramda.pipe`) allows you to chain together different parts of your application. You start with simple functions and progressively build them up into more complex ones.

```typescript
// Compose multiple features into a single service definition
const serviceDefinition = compose(feature1, feature2, feature3);

// Create the service from the composed definition
const service = defService(serviceDefinition);
```

### Composers

**Composers** are higher-order functions that create the building blocks of your service. They are designed to be used with `compose`. The primary composers are `withSchema` and `withRoute`.

#### `withSchema`

The `withSchema` composer provides comprehensive request validation with support for both JSON Schema and OpenAPI specifications. It supports two distinct usage patterns:

**1. JSON Schema Validation (Shorthand)**

The simplified approach validates only the request body using [JSON Schema](https://json-schema.org/):

```typescript
// Define a reusable schema for request body validation
const userSchema = withSchema({
  type: 'object',
  properties: {
    name: { type: 'string' },
    email: { type: 'string', format: 'email' },
  },
  required: ['name', 'email'],
});

// Apply the same schema to multiple routes
const createUserFeature = compose(userSchema, createUserRoute);
const updateUserFeature = compose(userSchema, updateUserRoute);
```

**2. OpenAPI Validation (Enhanced)**

The enhanced OpenAPI syntax allows you to validate **URL path parameters** and **query parameters** in addition to request bodies:

```typescript
// Validate URL parameters (/:userId) and query parameters (?include=profile)
const getUserSchema = withSchema({
  parameters: [
    {
      name: 'userId', // Validates the :userId in /users/:userId
      in: 'path',
      required: true,
      schema: { type: 'string', format: 'uuid' },
    },
    {
      name: 'include', // Validates ?include=profile query parameter
      in: 'query',
      required: false,
      schema: { type: 'string', enum: ['profile', 'settings'] },
    },
  ],
  requestBody: {
    // Still validates request body like before
    required: true,
    content: {
      'application/json': {
        schema: {
          type: 'object',
          properties: {
            name: { type: 'string' },
            email: { type: 'string', format: 'email' },
          },
          required: ['name', 'email'],
        },
      },
    },
  },
});

// Now validates ALL parts: URL params, query params, AND request body
const updateUserRoute = withRoute({
  method: 'PATCH',
  path: '/users/:userId', // :userId will be validated
  handler: updateUserHandler,
});

const updateUserFeature = compose(getUserSchema, updateUserRoute);
```

This is a major enhancement because previously `withSchema` only validated request bodies. Now you can validate the entire HTTP request - URL parameters, query parameters, and request body - all in one schema definition.

**Automatic Security Enhancements**

When using `withSchema`, the SDK automatically applies security enhancements to all object schemas:

- **`additionalProperties: false`** - Prevents injection of undefined fields by rejecting any properties not explicitly defined in the schema
- **`queryFilter: true`** - Validates MongoDB query operators to prevent NoSQL injection attacks

These enhancements are applied recursively to nested object schemas as well. You can override these defaults by explicitly specifying `additionalProperties: true` or `queryFilter: false` in your schema definition.

```typescript
// Automatic security enhancements are applied
const secureSchema = withSchema({
  type: 'object',
  properties: {
    email: { type: 'string' },
  },
});
// Result: additionalProperties: false, queryFilter: true (automatically applied)

// Override if needed (e.g., for flexible data structures)
const flexibleSchema = withSchema({
  type: 'object',
  additionalProperties: true, // Explicitly allow extra properties
  properties: {
    address: {
      type: 'object',
      additionalProperties: true, // Nested objects also respect explicit settings
    },
  },
});
```

**NoSQL Injection Protection with `queryFilter`**

The `queryFilter` keyword provides comprehensive protection against NoSQL injection attacks:

- **Operator Whitelisting**: Only allows safe MongoDB operators (`$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`, `$in`, `$nin`, `$regex`, `$options`, `$and`, `$or`)
- **ReDoS Protection**: Validates `$regex` patterns using `safe-regex` to prevent catastrophic backtracking
- **Length Limits**: Restricts `$regex` patterns to 100 characters maximum
- **Prototype Pollution Protection**: Blocks dangerous keys like `__proto__`, `constructor`, and `prototype`
- **Recursive Validation**: Validates nested query objects at all levels

```typescript
// Valid query with allowed operators
const validQuery = {
  email: { $in: ['user@example.com', 'admin@example.com'] },
  age: { $gte: 18, $lte: 65 },
};

// Invalid queries are automatically rejected
const invalidQuery1 = {
  email: 'user@example.com',
  $where: 'sleep(10000)', // ❌ Forbidden operator
};

const invalidQuery2 = {
  email: { $regex: '^(a+)+$' }, // ❌ ReDoS pattern
};

const invalidQuery3 = {
  email: 'user@example.com',
  __proto__: { isAdmin: true }, // ❌ Prototype pollution attempt
};
```

#### `withRoute`

The `withRoute` composer defines an endpoint. It supports both HTTP and WebSocket routes via the `protocol` option.

```typescript
// HTTP route (default protocol: 'http')
const createUserRoute = withRoute({
  method: 'POST',
  path: '/users',
  handler: async (payload) => {
    const newUser = await payload.context.db.users.create(
      payload.params.requestBody
    );
    return { user: newUser };
  },
});
```

The handler receives a `payload` object containing:

- `params`: An object with `requestBody`, `requestParams`, and `requestQuery`
- `context`: An object with `db` connections and the original `request` object

#### WebSocket routes (protocol: `ws`)

You can expose real-time endpoints using WebSockets. Define a route with `protocol: 'ws'` and a handler that returns an RxJS `Subject` (or a `Promise<Subject>`). Messages sent by your handler via `subject.next(value)` will be delivered to connected clients. Incoming client messages are JSON-parsed and pushed into your subject.

Validation:

- **Schema validation (message-level)**: Compose `withSchema(jsonSchema)` before your WebSocket route (same order as HTTP). The schema is applied to each inbound client message. Invalid messages cause a structured error and the connection to close with a policy violation.
- **Validators (connection-level)**: Provide `validators` on the route (same shape as HTTP). They run once at connection time and can reject the connection (useful for auth/permissions) by throwing an error.

```typescript
import { withSchema, withRoute, compose } from '@nodeblocks/backend-sdk';

const chatMessageSchema = withSchema({
  type: 'object',
  required: ['type', 'text'],
  properties: {
    type: { type: 'string', enum: ['chat'] },
    text: { type: 'string', minLength: 1 },
  },
});

const authValidator = async ({ context }: any) => {
  if (!context.request?.headers['x-api-key']) {
    throw new Error('Unauthorized');
  }
};

const wsFeature = compose(
  chatMessageSchema, // validates each inbound message
  withRoute({
    protocol: 'ws',
    path: '/ws/chat',
    validators: [authValidator], // validates the connection
    handler: () => new Subject(),
  })
);
```

Example:

```typescript
import { createServer } from 'http';
import express from 'express';
import { Subject, interval } from 'rxjs';
import { primitives } from '@nodeblocks/backend-sdk';
import { WebSocketServer } from 'ws';

const { compose, withRoute, defService } = primitives;

// Feature with a WebSocket route
const wsFeature = compose(
  withRoute({
    protocol: 'ws',
    path: '/ws/time',
    handler: () => {
      const subject = new Subject<{
        type: string;
        at?: number;
        [k: string]: any;
      }>();

      // Periodically broadcast a tick to all connected clients
      interval(1000).subscribe(() => {
        subject.next({ type: 'tick', at: Date.now() });
      });

      // You can also react to client messages as they are pushed into this subject
      // e.g., subject.subscribe(msg => { ... })

      return subject;
    },
  })
);

const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });

const router = defService(wsFeature, wss);
app.use('/', router);

server.listen(3000, () => {
  console.log('HTTP on :3000, WebSocket at ws://localhost:3000/ws/time');
});
```

Rx helper for convenience:

```typescript
import { filter } from 'rxjs';
import { notFromEmitter } from '@nodeblocks/backend-sdk';

// Use inside observable pipelines to ignore messages from a specific emitter id
// observable$.pipe(filter(notFromEmitter(someEmitterId)))
```

### Handlers

A **handler** is a function that contains the business logic for a route. In the spirit of composition, handlers themselves can be composed of smaller functions. The SDK provides utilities like `lift` and `flatMapAsync` to help compose asynchronous operations and handle errors in composition.

```typescript
// A handler composed of multiple steps
const createUserHandler = compose(
  createUserInDb,
  flatMapAsync(findUserById), // Continues if createUserInDb is successful
  lift(normalizeUserResponse) // Transforms the final result
);

const createUserRoute = withRoute({
  method: 'POST',
  path: '/users',
  handler: createUserHandler,
});
```

This allows you to create reusable pieces of business logic that can be combined in different ways.

### Features

A **feature** is a logical grouping of related functionality, typically consisting of one or more routes and their associated schemas. You create a feature by composing its parts.

```typescript
// schemas/user.ts
export const createUserSchema = withSchema({
  /* ... */
});

// routes/user.ts
export const createUserRoute = withRoute({
  /* ... */
});

// features/user.ts
import { compose } from '@nodeblocks/backend-sdk';
import { createUserSchema } from '../schemas/user';
import { createUserRoute } from '../routes/user';

export const userFeature = compose(createUserSchema, createUserRoute);
```

## Building a Service from First Principles

Let's walk through the process of building a complete service from scratch.

### 1. Define a Handler

First, we define a handler function that will contain our business logic.

```typescript
// handlers/products.ts
export const createProductHandler = async (payload) => {
  const { name, price } = payload.params.requestBody;
  // In a real app, you would save this to a database
  const newProduct = { id: 'prod_123', name, price };
  return { product: newProduct };
};
```

### 2. Define a Route

Next, we use `withRoute` to associate our handler with an HTTP endpoint.

```typescript
// routes/products.ts
import { withRoute } from '@nodeblocks/backend-sdk';
import { createProductHandler } from '../handlers/products';

export const createProductRoute = withRoute({
  method: 'POST',
  path: '/products',
  handler: createProductHandler,
});
```

### 3. Define a Schema

To validate the incoming data, we define a schema using `withSchema`.

```typescript
// schemas/products.ts
import { withSchema } from '@nodeblocks/backend-sdk';

export const createProductSchema = withSchema({
  type: 'object',
  properties: {
    name: { type: 'string' },
    price: { type: 'number', minimum: 0 },
  },
  required: ['name', 'price'],
});
```

### 4. Compose into a Feature

Now we compose the schema and the route into a single feature.

```typescript
// features/products.ts
import { compose } from '@nodeblocks/backend-sdk';
import { createProductSchema } from '../schemas/products';
import { createProductRoute } from '../routes/products';

export const productFeature = compose(createProductSchema, createProductRoute);
```

The order is important here: `createProductSchema` comes first, so its validation is applied to `createProductRoute`.

### 5. Create the Service

Finally, we use `defService` to create the service from our feature.

```typescript
// index.ts
import express from 'express';
import { defService } from '@nodeblocks/backend-sdk';
import { productFeature } from './features/products';

const app = express();
const service = defService(productFeature);
app.use('/api', service);

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});
```

### 6. Composing Multiple Features

As your application grows, you can define more features and compose them together at the service level.

```typescript
// index.ts
import { compose, defService } from '@nodeblocks/backend-sdk';
import { productFeature } from './features/products';
import { userFeature } from './features/users';
import { orderFeature } from './features/orders';

// Compose all features into one service definition
const allFeatures = compose(productFeature, userFeature, orderFeature);

const service = defService(allFeatures);
```

## Advanced Concepts

### Handler Composition

For more business logic, you can compose handlers from smaller, specialized functions. The SDK provides helpers for working with `Result` types:

- `lift`: Lifts a regular function into a context where it can work with promises
- `flatMap`: Chains synchronous operations that return a `Result` type, short-circuiting on errors
- `flatMapAsync`: Chains asynchronous operations that return a `Result` type, enabling error-safe composition

```typescript
import { Result, ok, err } from 'neverthrow';
import { flatMapAsync, lift } from '@nodeblocks/backend-sdk';

const validateUser = (data: any): Result<ValidatedUser, ValidationError> => {
  // validation logic
  return ok(validatedData);
};

const saveUser = async (
  user: ValidatedUser
): Promise<Result<User, DatabaseError>> => {
  // database operation
  return ok(savedUser);
};

const formatResponse = (user: User) => ({
  user,
  message: 'Created successfully',
});

// Compose operations that can fail
const createUserHandler = compose(
  (payload) => validateUser(payload.params.requestBody),
  flatMapAsync(saveUser),
  lift(formatResponse)
);
```

This pattern ensures that if any step fails, the error is propagated without executing subsequent steps, making your error handling both explicit and safe.

### Function Combinators

The SDK provides several powerful combinators for building robust, composable functions:

#### Core Functional Primitives

**`compose`** - Function composition using `pipe` from Ramda

```typescript
import { compose } from '@nodeblocks/backend-sdk';

// Compose functions from left to right
const processUser = compose(
  validateUser,
  saveUser,
  sendWelcomeEmail,
  formatResponse
);
```

**`ifElse`** - Conditional function application

```typescript
import { ifElse } from '@nodeblocks/backend-sdk';

const processUser = ifElse(
  (user) => user.isAdmin,
  (user) => processAdminUser(user),
  (user) => processRegularUser(user)
);
```

**`match`** - Path-based condition checking

```typescript
import { match } from '@nodeblocks/backend-sdk';

const isAdminUser = match(Boolean, ['role', 'isAdmin']);
```

#### `withLogging` - Function Logging

The `withLogging` combinator wraps any function to provide logging capabilities with configurable options.

**What does `withLogging` do?**

- **Wraps any function** to add logging of function calls, arguments, and results
- **Supports multiple log levels**: `info`, `debug`, `warn`, `error`, `fatal`, and `trace`
- **Configurable options**: Pass logger, level, and redaction rules via options object
- **Automatic logger injection**: Injects logger into payload objects for downstream functions
- **Smart sanitization**: Sanitizes sensitive objects (database connections, configs) in logs
- **Promise awareness**: Handles both sync and async functions with proper promise logging

#### Basic Usage

```typescript
import { withLogging } from '@nodeblocks/backend-sdk';

// Simple logging with default settings (info level, default logger)
const loggedHandler = withLogging(createUserHandler);

// With options object
const debugHandler = withLogging(createUserHandler, {
  level: 'debug',
  logger: customLogger,
  redact: [/password/i, /token/i],
});

// With custom redaction patterns
const secureHandler = withLogging(createUserHandler, {
  level: 'trace',
  redact: [
    { approach: 'fields', pattern: /password/i, replacement: '[REDACTED]' },
    {
      approach: 'patterns',
      pattern: /Bearer\s+[^\s]+/i,
      replacement: '[REDACTED_TOKEN]',
    },
  ],
});
```

#### Log Output

All log levels produce structured logs with:

- Function name
- Sanitized input arguments
- Sanitized result/return value
- Promise detection for async functions

```typescript
// Example log output
{
  "args": [{"params": {"requestBody": {"name": "John"}}}],
  "functionName": "createUserHandler",
  "msg": "Function: createUserHandler",
  "result": {"user": {"id": "123", "name": "John"}}
}
```

#### Example: Logging a Handler

```typescript
import { withLogging } from '@nodeblocks/backend-sdk';

const createUserHandler = async (payload) => {
  const { name, email } = payload.params.requestBody;
  const newUser = await payload.context.db.users.create({ name, email });
  return { user: newUser };
};

const loggedCreateUserHandler = withLogging(createUserHandler, 'trace');

// Use in your route
const createUserRoute = withRoute({
  method: 'POST',
  path: '/users',
  handler: loggedCreateUserHandler,
});
```

#### Integration with Composition

The `withLogging` combinator works seamlessly with the SDK's composition patterns:

```typescript
import { compose, withLogging } from '@nodeblocks/backend-sdk';

// Log individual functions
const loggedValidateUser = withLogging(validateUser, 'debug');
const loggedSaveUser = withLogging(saveUser, 'debug');
const loggedFormatResponse = withLogging(formatResponse, 'info');

// Compose with logging
const createUserFeature = compose(
  createUserSchema,
  withRoute({
    method: 'POST',
    path: '/users',
    handler: compose(
      loggedValidateUser,
      flatMapAsync(loggedSaveUser),
      lift(loggedFormatResponse)
    ),
  })
);
```

#### Sanitization Features

The `withLogging` function automatically sanitizes sensitive objects in logs:

- Database connections are shown as `🗄️ [Database]`
- Config objects are shown as `⚙️ [Config]`
- Mail services are shown as `📧 [MailService]`
- Request/Response objects are shown as `📤 [Request]`/`📥 [Response]`
- Logger instances are shown as `📝 [Logger]`

This prevents logging of sensitive data while still providing useful debugging information.

### Validation and Predicates

Beyond schema validation, the SDK provides a set of validation predicates:

```typescript
import { requireParam, isUUID, isNumber } from '@nodeblocks/backend-sdk';

const getUserRoute = withRoute({
  method: 'GET',
  path: '/users/:userId',
  validators: [requireParam('userId'), isUUID('userId')],
  handler: getUserHandler,
});
```

Available validators include:

- `requireParam(key)`: Ensures a parameter is present
- `isUUID(key)`: Validates UUID format
- `isNumber(key)`: Validates numeric values

### Result-Based Error Handling

The SDK embraces functional error handling using the `Result` type from the `neverthrow` library. This approach makes error handling explicit and composable, avoiding the unpredictability of thrown exceptions.

```typescript
import { Result, ok, err } from 'neverthrow';

// A handler that returns a Result
const createUserHandler = async (payload): Promise<Result<User, Error>> => {
  const validation = validateUserData(payload.params.requestBody);
  if (validation.isErr()) {
    return err(validation.error);
  }

  const user = await createUser(validation.value);
  return ok(user);
};
```

The `Result` type has two states:

- `Ok<T>` - Contains a successful value of type T
- `Err<E>` - Contains an error of type E

This makes error handling explicit in your type signatures and enables composition patterns with `flatMap` and `flatMapAsync`.

### Throwing Errors

While `Result` types are preferred for business logic composition, you can still throw errors when appropriate. This is common in:

- **Formatters/Terminators**: Functions at the end of composition chains that format responses
- **Validation failures**: When input validation fails and you want to return an HTTP error
- **Unexpected errors**: When something goes wrong that should result in a 500 error

The SDK provides `NodeblocksError` for structured HTTP error responses:

```typescript
import {
  NodeblocksError,
  nodeBlocksErrorMiddleware,
} from '@nodeblocks/backend-sdk';

// Use for structured HTTP errors
throw new NodeblocksError(400, 'Invalid input', 'validation', [
  'Name is required',
]);

// Add middleware to handle NodeblocksError and other thrown errors
app.use(nodeBlocksErrorMiddleware());
```

The error middleware will catch thrown errors and format appropriate HTTP responses.

### Schema Merging and Extraction

You can merge multiple schemas together and extract schema definitions for reuse using helper functions.

**JSON Schema Merging (Shorthand)**

```typescript
export const createCategorySchema = withSchema(
  getSchemaDefinition(categorySchema),
  {
    $schema: 'http://json-schema.org/draft-07/schema#',
    required: ['name', 'description', 'status'],
    type: 'object',
  }
);
```

The SDK provides the `getSchemaDefinition()` function to extract JSON Schema definitions from shorthand `withSchema` composers for reuse and extension.

This function allows you to:

- **Reuse existing schemas**: Extract schema definitions from pre-built components
- **Extend schemas**: Add new properties and validation rules to existing schemas
- **Combine schemas**: Merge multiple schema definitions into a single schema
- **Share validation logic**: Reuse complex validation definitions across multiple routes

### Custom Validators

The `withRoute` configuration accepts a `validators` array for custom validation logic that goes beyond schema validation. A validator is an async function that receives the request payload and should throw an error if validation fails.

```typescript
// Custom validator to check if user has permission to access a resource
const checkUserPermission = async (payload) => {
  const { userId } = payload.params.requestParams;
  const { user } = payload.context;

  if (user.id !== userId && user.role !== 'admin') {
    throw new NodeblocksError(403, 'Access denied', 'permission', {
      requiredRole: 'admin',
      userId: user.id,
      targetUserId: userId,
    });
  }
};

// Custom validator to check business rules
const checkBusinessHours = async (payload) => {
  const now = new Date();
  const hour = now.getHours();

  if (hour < 9 || hour > 17) {
    throw new NodeblocksError(400, 'Service unavailable', 'business_hours', {
      currentHour: hour,
      availableHours: '9:00 AM - 5:00 PM',
    });
  }
};

// Use custom validators in a route
const updateUserRoute = withRoute({
  method: 'PATCH',
  path: '/users/:userId',
  validators: [checkUserPermission, checkBusinessHours],
  handler: updateUserHandler,
});
```

### Pre-built Services

The SDK includes complete services for all major functionality areas. Each service provides a full set of CRUD operations and specialized endpoints:

#### Authentication Service

Complete identity lifecycle and OAuth management:

```typescript
import { authService } from '@nodeblocks/backend-sdk';

// Register authentication service with OAuth providers
app.use(
  '/api',
  authService(database, config, {
    mailService,
    googleOAuthDriver,
    twitterOAuthDriver,
    lineOAuthDriver,
  })
);
```

**Features:**

- User registration and login with credentials
- Email verification and password reset
- Multi-factor authentication (MFA)
- OAuth integration (Google, Twitter, LINE)
- Session management and token refresh
- User invitations and role management
- Account activation/deactivation

#### Chat Service

Real-time chat system with WebSocket support:

```typescript
import { chatService } from '@nodeblocks/backend-sdk';

// Register chat service with WebSocket server
app.use('/api/chat', chatService(database, config, { webSocketServer }));
```

**Features:**

- Channel creation and management
- Real-time messaging with WebSocket streaming
- Message templates and attachments
- User subscriptions and read states
- File upload for message attachments
- Message search and filtering

#### User Service

Complete user and profile management:

```typescript
import { userService } from '@nodeblocks/backend-sdk';

// Register user service with file storage
app.use('/api/users', userService(database, config, { fileStorageDriver }));
```

**Features:**

- User profile creation and management
- Avatar upload and management
- Social features (follows, likes)
- Profile discovery and search
- User blocking and privacy controls

#### Product Service

E-commerce product management:

```typescript
import { productService } from '@nodeblocks/backend-sdk';

// Register product service
app.use(
  '/api/products',
  productService(database, config, { fileStorageDriver })
);
```

**Features:**

- Product CRUD operations
- Batch operations for bulk management
- Product variants and attributes
- Image upload and management
- Product copying and duplication
- Advanced search and filtering
- Product likes and social features

#### Organization Service

Multi-tenant organization management:

```typescript
import { organizationService } from '@nodeblocks/backend-sdk';

// Register organization service
app.use('/api/organizations', organizationService(database, config));
```

**Features:**

- Organization creation and management
- Role-based access control
- Member management
- Organization settings and configuration
- Multi-tenant data isolation

#### Order Service

Order processing and management:

```typescript
import { orderService } from '@nodeblocks/backend-sdk';

// Register order service
app.use('/api/orders', orderService(database, config));
```

**Features:**

- Order creation and processing
- Order status management
- Order history and tracking
- Payment integration hooks
- Order search and filtering

#### Category Service

Product categorization system:

```typescript
import { categoryService } from '@nodeblocks/backend-sdk';

// Register category service
app.use('/api/categories', categoryService(database, config));
```

**Features:**

- Category hierarchy management
- Category enable/disable functionality
- Category-based product filtering
- Admin-only category management

#### Attributes Service

Dynamic attribute management:

```typescript
import { attributesService } from '@nodeblocks/backend-sdk';

// Register attributes service
app.use('/api/attributes', attributesService(database, config));
```

**Features:**

- Dynamic attribute group creation
- Flexible attribute schemas
- Attribute-based filtering
- Custom data type support

#### Identity Service

Identity management and administration:

```typescript
import { identityService } from '@nodeblocks/backend-sdk';

// Register identity service
app.use('/api/identities', identityService(database, config));
```

**Features:**

- Identity administration
- User account management
- Identity type management
- Bulk identity operations

### Individual Components

You can also use individual components to build custom features:

```typescript
import {
  // Routes
  createUserRoute,
  getUserRoute,
  updateUserRoute,
  deleteUserRoute,
  createProductRoute,
  findProductsRoute,
  createChannelRoute,
  findChannelsRoute,

  // Schemas
  createUserSchema,
  updateUserSchema,
  createProductSchema,
  createChannelSchema,

  // Handlers
  createUser,
  getUserById,
  createProduct,
  findProducts,
  createChannel,
  findChannels,

  // Validators
  isAuthenticated,
  checkIdentityType,
  requireParam,
  isUUID,
} from '@nodeblocks/backend-sdk';

// Compose your own features using pre-built components
const customUserFeature = compose(
  createUserSchema,
  createUserRoute,
  getUserRoute,
  updateUserSchema,
  updateUserRoute,
  deleteUserRoute
);
```

### Available Components

#### Routes

**Authentication Routes:**

- `registerCredentialsRoute`, `loginWithCredentialsRoute`, `loginWithOnetimeTokenRoute`
- `emailVerificationRoute`, `confirmEmailRoute`, `changeEmailRoute`, `confirmNewEmailRoute`
- `sendResetPasswordLinkEmailRoute`, `completePasswordResetRoute`, `changePasswordRoute`
- `deactivateRoute`, `activateRoute`, `refreshTokenRoute`, `deleteRefreshTokensRoute`
- `resendMfaCodeRoute`, `verifyMfaCodeRoute`
- `googleOAuthRoute`, `googleOAuthCallbackRoute`
- `twitterOAuthRoute`, `twitterOAuthCallbackRoute`
- `lineOAuthRoute`, `lineOAuthCallbackRoute`

**User Routes:**

- `createUserRoute`, `getUserRoute`, `findUsersRoute`, `updateUserRoute`, `deleteUserRoute`
- `lockUserRoute`, `unlockUserRoute`, `getAvatarUploadUrlRoute`
- `createProfileFollowRoute`, `deleteProfileFollowRoute`, `getProfileFollowersRoute`
- `createOrganizationFollowRoute`, `deleteOrganizationFollowRoute`
- `createProductLikeRoute`, `deleteProductLikeRoute`

**Product Routes:**

- `createProductRoute`, `getProductRoute`, `findProductsRoute`, `updateProductRoute`, `deleteProductRoute`
- `createProductBatchRoute`, `updateProductBatchRoute`, `deleteProductBatchRoute`
- `copyProductRoute`, `copyProductBatchRoute`
- `getProductImageUploadUrlRoute`, `createProductImageRoute`, `deleteProductImageRoute`
- `createProductVariantRoute`, `getProductVariantRoute`, `updateProductVariantRoute`, `deleteProductVariantRoute`
- `findProductVariantsRoute`, `createProductVariantBulkRoute`, `updateProductVariantBulkRoute`, `deleteProductVariantBulkRoute`
- `getProductLikersRoute`

**Chat Routes:**

- `createChannelRoute`, `getChannelRoute`, `findChannelsRoute`, `updateChannelRoute`, `deleteChannelRoute`
- `createChatSubscriptionRoute`, `getChatSubscriptionRoute`, `findChatSubscriptionsRoute`, `deleteChatSubscriptionRoute`
- `createChatMessageRoute`, `getChatMessageRoute`, `findChatMessagesRoute`, `updateChatMessageRoute`, `deleteChatMessageRoute`
- `getChatMessageAttachmentUrlRoute`, `createChatMessageAttachmentRoute`, `deleteChatMessageAttachmentRoute`
- `getChannelIconUploadUrlRoute`, `createChatMessageTemplateRoute`, `getChatMessageTemplateRoute`
- `updateChatMessageTemplateRoute`, `deleteChatMessageTemplateRoute`, `findChatMessageTemplatesRoute`
- `findChatMessageTemplatesForOrganizationRoute`, `getChannelMessagesRoute`
- `upsertChatChannelReadStateRoute`, `streamChatMessagesRoute`

**Organization Routes:**

- `createOrganizationRoute`, `getOrganizationRoute`, `findOrganizationsRoute`, `updateOrganizationRoute`, `deleteOrganizationRoute`

**Order Routes:**

- `createOrderRoute`, `getOrderRoute`, `findOrdersRoute`, `updateOrderRoute`, `deleteOrderRoute`

**Category Routes:**

- `createCategoryRoute`, `getCategoryRoute`, `findCategoriesRoute`, `updateCategoryRoute`, `deleteCategoryRoute`
- `enableCategoryRoute`, `disableCategoryRoute`

**Attributes Routes:**

- `createAttributeRoute`, `getAttributeRoute`, `findAttributesRoute`, `updateAttributeRoute`, `deleteAttributeRoute`

**Identity Routes:**

- `findIdentitiesRoute`, `getIdentityRoute`, `updateIdentityRoute`, `deleteIdentityRoute`

#### Schemas

**Authentication Schemas:**

- `registerCredentialsSchema`, `loginWithCredentialsSchema`, `loginWithOnetimeTokenSchema`
- `emailVerificationSchema`, `confirmEmailSchema`, `changeEmailSchema`, `confirmNewEmailSchema`
- `sendResetPasswordLinkEmailSchema`, `completePasswordResetSchema`, `changePasswordSchema`
- `deactivateSchema`, `activateSchema`, `refreshTokenSchema`, `deleteRefreshTokensSchema`
- `resendMfaCodeSchema`, `verifyMfaCodeSchema`
- `googleOAuthSchema`, `googleOAuthCallbackSchema`
- `twitterOAuthSchema`, `twitterOAuthCallbackSchema`
- `lineOAuthSchema`, `lineOAuthCallbackSchema`

**User Schemas:**

- `createUserSchema`, `updateUserSchema`, `userSchema`, `profileIdPathParameter`
- `avatarSchema`, `createProfileFollowSchema`, `createOrganizationFollowSchema`, `createProductLikeSchema`

**Product Schemas:**

- `createProductSchema`, `updateProductSchema`, `productSchema`
- `createProductBatchSchema`, `updateProductBatchSchema`, `deleteProductBatchSchema`, `copyProductBatchSchema`
- `createProductVariantSchema`, `updateProductVariantSchema`, `productVariantSchema`
- `createProductVariantBulkSchema`, `updateProductVariantBulkSchema`, `deleteProductVariantBulkSchema`

**Chat Schemas:**

- `createChannelSchema`, `updateChannelSchema`, `channelSchema`
- `createChatSubscriptionSchema`, `chatSubscriptionSchema`
- `createChatMessageSchema`, `updateChatMessageSchema`, `chatMessageSchema`
- `createChatMessageTemplateSchema`, `updateChatMessageTemplateSchema`, `chatMessageTemplateSchema`
- `upsertChatChannelReadStateSchema`, `chatChannelReadStateSchema`

**Organization Schemas:**

- `createOrganizationSchema`, `updateOrganizationSchema`, `organizationSchema`
- `organizationCommonSchema`, `organizationAdminApprovedSchema`

**Order Schemas:**

- `createOrderSchema`, `updateOrderSchema`, `orderSchema`

**Category Schemas:**

- `createCategorySchema`, `updateCategorySchema`, `categorySchema`

**Attributes Schemas:**

- `createAttributeSchema`, `updateAttributeSchema`, `attributeSchema`

**Common Schemas:**

- `baseSchema`, `baseUpdateSchema`, `arrayOfStringsSchema`
- `auditSchema`, `paginationSchema`, `fileSchema`, `addressSchema`, `contactSchema`
- `paginationQueryParametersSchema`, `contentLengthQueryParameter`

#### Handlers

**Authentication Handlers:**

- `registerCredentials`, `loginWithCredentials`, `loginWithOnetimeToken`
- `emailVerification`, `confirmEmail`, `changeEmail`, `confirmNewEmail`
- `sendResetPasswordLinkEmail`, `completePasswordReset`, `changePassword`
- `deactivate`, `activate`, `refreshToken`, `deleteRefreshTokens`
- `resendMfaCode`, `verifyMfaCode`
- `googleOAuth`, `googleOAuthCallback`
- `twitterOAuth`, `twitterOAuthCallback`
- `lineOAuth`, `lineOAuthCallback`

**User Handlers:**

- `createUser`, `getUserById`, `findUsers`, `updateUser`, `deleteUser`
- `lockUser`, `unlockUser`, `normalizeUserTerminator`, `normalizeUsersListTerminator`
- `deleteUserTerminator`, `lockUserTerminator`, `unlockUserTerminator`
- `createProfileFollow`, `deleteProfileFollow`, `getProfileFollowers`
- `createOrganizationFollow`, `deleteOrganizationFollow`
- `createProductLike`, `deleteProductLike`, `findProfilesByIdentityId`

**Product Handlers:**

- `createProduct`, `getProductById`, `findProducts`, `updateProduct`, `deleteProduct`
- `createProductBatch`, `updateProductBatch`, `deleteProductBatch`
- `copyProduct`, `copyProductBatch`
- `normalizeProductTerminator`, `normalizeProductsListTerminator`
- `deleteProductTerminator`, `deleteBatchProductsTerminator`
- `createProductVariant`, `getProductVariant`, `updateProductVariant`, `deleteProductVariant`
- `findProductVariants`, `createProductVariantBulk`, `updateProductVariantBulk`, `deleteProductVariantBulk`
- `getProductLikers`

**Chat Handlers:**

- `createChannel`, `getChannelById`, `findChannels`, `updateChannel`, `deleteChannel`
- `createChatSubscription`, `getChatSubscriptionById`, `findChatSubscriptions`, `deleteChatSubscription`
- `createChatMessage`, `getChatMessageById`, `findChatMessages`, `updateChatMessage`, `deleteChatMessage`
- `getChatMessageAttachmentUrl`, `createChatMessageAttachment`, `deleteChatMessageAttachment`
- `getChannelIconUploadUrl`, `createChatMessageTemplate`, `getChatMessageTemplateById`
- `updateChatMessageTemplate`, `deleteChatMessageTemplate`, `findChatMessageTemplates`
- `findChatMessageTemplatesForOrganization`, `getChannelMessages`
- `upsertChatChannelReadState`, `streamChatMessages`

**Organization Handlers:**

- `createOrganization`, `getOrganizationById`, `findOrganizations`, `updateOrganization`, `deleteOrganization`
- `normalizeOrganizationTerminator`, `normalizeOrganizationsListTerminator`, `deleteOrganizationTerminator`

**Order Handlers:**

- `createOrder`, `getOrderById`, `findOrders`, `updateOrder`, `deleteOrder`
- `normalizeOrderTerminator`, `normalizeOrdersListTerminator`, `deleteOrderTerminator`

**Category Handlers:**

- `createCategory`, `getCategoryById`, `findCategories`, `updateCategory`, `deleteCategory`
- `enableCategory`, `disableCategory`
- `normalizeCategoryTerminator`, `normalizeCategoriesListTerminator`
- `deleteCategoryTerminator`, `enableCategoryTerminator`, `disableCategoryTerminator`

**Attributes Handlers:**

- `createAttributeGroup`, `getAttributeGroupById`, `findAttributeGroups`, `updateAttributeGroup`, `deleteAttributeGroup`
- `normalizeAttributeGroupTerminator`, `normalizeAttributesListTerminator`, `deleteAttributeGroupTerminator`

**Identity Handlers:**

- `findIdentities`, `getIdentityById`, `updateIdentity`, `deleteIdentity`
- `normalizeIdentitiesWithoutPassword`, `normalizeIdentity`, `deleteIdentityTerminator`

#### Validators

**Authentication Validators:**

- `isAuthenticated()`, `checkIdentityType(types)`, `isSelf(path)`, `hasOrgRole(roles, orgIdPath)`

**Resource Access Validators:**

- `validateResourceAccess(allowedSubjects)`, `validateOrganizationAccess(orgIdPath)`
- `validateChannelAccess(channelIdPath)`, `validateMessageAccess(messageIdPath)`
- `validateUserProfileAccess(profileIdPath)`

**Parameter Validators:**

- `requireParam(key)`, `isUUID(key)`, `isNumber(key)`

**Domain-Specific Validators:**

- `doesCategoryExist`, `channelExists`, `ownsOrder(path)`, `some(...validators)`

#### Features

**Authentication Features:**

- `registerCredentialsFeature`, `loginWithCredentialsFeature`, `loginWithOnetimeTokenFeature`
- `emailVerificationFeature`, `confirmEmailFeature`, `changeEmailFeature`, `confirmNewEmailFeature`
- `sendResetPasswordLinkEmailFeature`, `completePasswordResetFeature`, `changePasswordFeature`
- `deactivateFeature`, `activateFeature`, `refreshTokenFeature`, `deleteRefreshTokensFeature`
- `resendMfaCodeFeature`, `verifyMfaCodeFeature`
- `googleOAuthFeature`, `googleOAuthCallbackFeature`
- `twitterOAuthFeature`, `twitterOAuthCallbackFeature`
- `lineOAuthFeature`, `lineOAuthCallbackFeature`

**User Features:**

- `createUserFeature`, `getUserFeatures`, `findUsersFeatures`, `editUserFeatures`, `deleteUserFeatures`
- `lockUserFeatures`, `unlockUserFeatures`, `getAvatarUploadUrlFeature`
- `createProfileFollowFeature`, `deleteProfileFollowFeature`, `getProfileFollowersFeature`
- `createOrganizationFollowFeature`, `deleteOrganizationFollowFeature`
- `createProductLikeFeature`, `deleteProductLikeFeature`, `findProfilesByIdentityIdFeature`

**Product Features:**

- `createProductFeature`, `getProductFeatures`, `findProductsFeatures`, `editProductFeatures`, `deleteProductFeatures`
- `createProductBatchFeature`, `editProductBatchFeatures`, `deleteProductBatchFeatures`
- `copyProductFeatures`, `copyProductBatchFeatures`
- `getProductImageUploadUrlFeature`, `createProductImageFeature`, `deleteProductImageFeature`
- `createProductVariantFeature`, `getProductVariantFeature`, `updateProductVariantFeature`, `deleteProductVariantFeature`
- `findProductVariantsFeature`, `createProductVariantBulkFeature`, `updateProductVariantBulkFeature`, `deleteProductVariantBulkFeature`
- `getProductLikersFeature`

**Chat Features:**

- `createChannelFeature`, `getChannelFeature`, `findChannelsFeature`, `updateChannelFeature`, `deleteChannelFeature`
- `createChatSubscriptionFeature`, `getChatSubscriptionFeature`, `findChatSubscriptionsFeature`, `deleteChatSubscriptionFeature`
- `createChatMessageFeature`, `getChatMessageFeature`, `findChatMessagesFeature`, `updateChatMessageFeature`, `deleteChatMessageFeature`
- `getChatMessageAttachmentUrlFeature`, `createChatMessageAttachmentFeature`, `deleteChatMessageAttachmentFeature`
- `getChannelIconUploadUrlFeature`, `createChatMessageTemplateFeature`, `getChatMessageTemplateFeature`
- `updateChatMessageTemplateFeature`, `deleteChatMessageTemplateFeature`, `findChatMessageTemplatesFeature`
- `findChatMessageTemplatesForOrganizationFeature`, `getChannelMessagesFeature`
- `upsertChatChannelReadStateFeature`, `streamChatMessagesFeature`

**Organization Features:**

- `createOrganizationFeature`, `getOrganizationFeature`, `findOrganizationsFeature`, `updateOrganizationFeature`, `deleteOrganizationFeature`

**Order Features:**

- `createOrderFeature`, `getOrderFeature`, `findOrdersFeature`, `updateOrderFeature`, `deleteOrderFeature`

**Category Features:**

- `createCategoryFeature`, `getCategoryFeature`, `findCategoriesFeature`, `updateCategoryFeature`, `deleteCategoryFeature`
- `enableCategoryFeature`, `disableCategoryFeature`

**Attributes Features:**

- `createAttributeFeature`, `getAttributeFeature`, `findAttributesFeature`, `updateAttributeFeature`, `deleteAttributeFeature`

**Identity Features:**

- `findIdentitiesFeature`, `getIdentityFeature`, `updateIdentityFeature`, `deleteIdentityFeature`

### Pagination with `withPagination`

The SDK provides a powerful utility, `withPagination`, to add automatic pagination to your database-backed route handlers. This is especially useful for MongoDB-style collections, but can be adapted for any database interface that supports `find` and `toArray`/`count` methods.

#### What does `withPagination` do?

- **Monkey-patches** your database collections' `find` method to automatically apply pagination parameters (`page`, `limit`).
- **Automatically calculates skip** from page and limit parameters (skip = (page - 1) \* limit).
- Injects pagination metadata (like `total`, `totalPages`, `hasNext`, `hasPrev`, etc.) into the handler payload.
- Makes it easy to build paginated endpoints without manually handling skip/limit logic in every handler.

#### How to use

1. **Wrap your handler** with `withPagination`:

```typescript
import { withPagination } from '@nodeblocks/backend-sdk';

const paginatedHandler = withPagination(myHandler);
```

2. **Handler signature**: Your handler receives a payload whose `context.db` collections have a monkey-patched `find` method. When you call `.find().toArray()`, pagination is applied and metadata is injected into the payload.

3. **Query parameter handling**: The `page` and `limit` parameters are extracted from `requestQuery` and used for pagination. Default values are `page = 1` and `limit = 10`. All other query parameters are preserved as filter parameters for your handler to use.

4. **Accessing pagination metadata**: After calling `.toArray()` on a collection, the payload will have a `paginationResult` property with the following structure:

```typescript
{
  data: [...],
  pagination: {
    page: number,
    limit: number,
    total: number,
    totalPages: number,
    hasNext: boolean,
    hasPrev: boolean,
  }
}
```

#### Example: Paginated Query with MongoDB (from `__scratch__.ts`)

```typescript
import { MongoMemoryServer } from 'mongodb-memory-server';
import { MongoClient } from 'mongodb';
import { withPagination } from '@nodeblocks/backend-sdk';

const server = await MongoMemoryServer.create();
const uri = server.getUri();
const client = new MongoClient(uri);
const db = client.db();

// Insert some test data
for (let i = 0; i < 100; i++) {
  await db.collection('attributes').insertOne({ name: 'test' + i });
}

// Define a handler that returns all attribute groups
const findAttributeGroups = async (payload) => {
  const data = await payload.context.db.attributes.find({}).toArray();
  return { attributeGroups: data };
};

// Wrap the handler with pagination
const paginatedHandler = withPagination(findAttributeGroups);

// Call the paginated handler
const result = await paginatedHandler({
  context: {
    db: { attributes: db.collection('attributes') },
    request: {} as any,
  },
  params: {
    requestQuery: {
      page: 1, // Current page (1-based)
      limit: 10, // Items per page
      // Note: skip is automatically calculated as (page - 1) * limit
    },
  },
});

console.log(result.paginationResult);
// {
//   data: [...],
//   pagination: {
//     page: 1,
//     limit: 10,
//     total: 100,
//     totalPages: 10,
//     hasNext: true,
//     hasPrev: false,
//   }
// }
```

#### Testing Pagination Logic

When testing handlers wrapped with `withPagination`, make sure to call `.toArray()` on the cursor to trigger the pagination logic and metadata injection. Example:

```typescript
const payload = createMockPayload({ page: 2, limit: 5 }, mockData, 25);
const paginatedHandler = withPagination(mockHandler);
await paginatedHandler(payload);
const cursor = payload.context.db.users.find({});
await cursor.toArray();
expect(payload.paginationResult?.pagination).toEqual({
  page: 2,
  limit: 5,
  total: 25,
  totalPages: 5,
  hasNext: true,
  hasPrev: true,
});
```

This ensures your tests accurately reflect how pagination metadata is set in real usage.

#### `withSoftDelete` - Soft Delete Support

The `withSoftDelete` combinator automatically converts hard deletes into soft deletes by adding a `deletedAt` timestamp instead of removing records from the database.

**What does `withSoftDelete` do?**

- **Converts deletes to updates**: `deleteOne` and `deleteMany` operations set `deletedAt` timestamp
- **Filters out deleted records**: `find`, `findOne`, and `countDocuments` automatically exclude soft-deleted records
- **Preserves data integrity**: Records are marked as deleted but remain in the database
- **Maintains API compatibility**: Works transparently with existing handlers

```typescript
import { withSoftDelete } from '@nodeblocks/backend-sdk';

// Wrap any handler to enable soft delete
const softDeleteHandler = withSoftDelete(myHandler);

// Now all delete operations will be soft deletes
// and find operations will exclude deleted records
```

#### `withPaginatedProperty` - Property Pagination

The `withPaginatedProperty` combinator adds pagination to array properties within documents, useful for paginating nested arrays like organization members or product reviews.

**What does `withPaginatedProperty` do?**

- **Paginates array properties**: Adds pagination to specific array fields in documents
- **Preserves document structure**: Returns the full document with paginated array properties
- **Maintains metadata**: Includes pagination metadata for the paginated property
- **Flexible property paths**: Supports nested property paths like `['members', 'roles']`

```typescript
import { withPaginatedProperty } from '@nodeblocks/backend-sdk';

// Paginate the 'members' array in organization documents
const paginatedMembersHandler = withPaginatedProperty(getOrganizationHandler, [
  'members',
]);

// Paginate nested array properties
const paginatedRolesHandler = withPaginatedProperty(getUserHandler, [
  'profile',
  'roles',
]);
```

#### `applyPayloadArgs` - Function Application

The `applyPayloadArgs` combinator extracts arguments from a payload and applies them to a pure function, enabling clean separation of business logic from payload handling.

**What does `applyPayloadArgs` do?**

- **Extracts payload data**: Pulls specific paths from the payload object
- **Applies to pure functions**: Calls your business logic with extracted arguments
- **Handles async/sync functions**: Works with both synchronous and asynchronous functions
- **Supports Result types**: Automatically handles `Result` return types
- **Merges results back**: Stores function results back into the payload

```typescript
import { applyPayloadArgs } from '@nodeblocks/backend-sdk';

// Extract arguments and apply to pure function
const updateUserHandler = applyPayloadArgs(
  updateUserInDatabase, // Pure function
  [
    ['params', 'requestParams', 'userId'], // Extract userId
    ['params', 'requestBody'], // Extract request body
    ['context', 'db', 'users'], // Extract database collection
  ],
  'updatedUser' // Store result in payload.updatedUser
);
```

#### `orThrow` - Result Termination

The `orThrow` combinator handles `Result` types by either returning success data or throwing appropriate errors based on error type mapping.

**What does `orThrow` do?**

- **Maps errors to HTTP status codes**: Converts specific error types to appropriate HTTP responses
- **Extracts success data**: Optionally extracts specific data from successful results
- **Throws structured errors**: Throws `NodeblocksError` with proper status codes
- **Type-safe error handling**: Ensures all error cases are handled explicitly

```typescript
import { orThrow } from '@nodeblocks/backend-sdk';

// Map errors to HTTP status codes
const errorHandler = orThrow([
  [ValidationError, 400],
  [NotFoundError, 404],
  [DuplicateError, 409],
  [DatabaseError, 500],
]);

// With success data extraction
const successHandler = orThrow(
  [[ValidationError, 400]], // Error mappings
  [['user'], 200] // Extract user data with 200 status
);
```

#### `hasValue` - Value Validation

The `hasValue` combinator validates that a value is not null, undefined, or empty, useful for input validation and data integrity checks.

**What does `hasValue` do?**

- **Validates meaningful content**: Ensures values are not null, undefined, or empty
- **Type-safe validation**: Returns a type guard for TypeScript
- **Handles various empty types**: Checks for empty strings, arrays, and objects
- **Composable validation**: Can be used in validation pipelines

```typescript
import { hasValue } from '@nodeblocks/backend-sdk';

// Validate input has meaningful content
if (hasValue(userInput)) {
  // userInput is guaranteed to have content
  processUserInput(userInput);
}

// Use in validation pipelines
const validateInput = (input: unknown) => {
  if (!hasValue(input)) {
    throw new Error('Input is required');
  }
  return input; // TypeScript knows input has value
};
```

#### `lift` - Promise Lifting

The `lift` combinator lifts a synchronous function to work with promises, enabling composition of sync and async functions.

**What does `lift` do?**

- **Lifts sync functions**: Converts synchronous functions to work with promises
- **Enables composition**: Allows mixing sync and async functions in composition chains
- **Maintains type safety**: Preserves TypeScript types through the transformation
- **Handles promise unwrapping**: Automatically awaits promises before applying the function

```typescript
import { lift } from '@nodeblocks/backend-sdk';

// Lift a sync function to work with promises
const asyncNormalizeUser = lift(normalizeUser);

// Use in composition with async functions
const userHandler = compose(
  getUserFromDatabase, // Returns Promise<User>
  lift(normalizeUser), // Lifts sync function to work with Promise<User>
  lift(formatUserResponse) // Lifts another sync function
);
```

#### `flatMap` and `flatMapAsync` - Result Chaining

The `flatMap` and `flatMapAsync` combinators enable chaining of operations that return `Result` types, providing safe error handling in composition chains.

**What do they do?**

- **Chain Result operations**: Compose functions that return `Result` types
- **Short-circuit on errors**: Stop execution if any step fails
- **Preserve error context**: Maintain error information through the chain
- **Type-safe composition**: Ensure type safety across the composition chain

```typescript
import { flatMap, flatMapAsync } from '@nodeblocks/backend-sdk';

// Chain synchronous Result operations
const syncChain = compose(
  validateUser,
  flatMap(saveUser), // Only runs if validateUser succeeds
  flatMap(sendWelcomeEmail) // Only runs if saveUser succeeds
);

// Chain asynchronous Result operations
const asyncChain = compose(
  validateUser,
  flatMapAsync(saveUserToDatabase), // Async operation
  flatMapAsync(sendWelcomeEmail), // Another async operation
  lift(formatResponse) // Sync operation at the end
);
```

## Business Blocks

The SDK includes a comprehensive set of business blocks that provide reusable, domain-specific functionality. These blocks encapsulate complex business logic and can be composed together to build sophisticated features.

### Authentication Blocks

**Core authentication utilities and token management:**

```typescript
import {
  checkToken,
  compareStringAgainstHash,
  generateUserAccessToken,
  generateRefreshToken,
  generateOnetimeToken,
  softDeleteRefreshTokens,
} from '@nodeblocks/backend-sdk';

// Token validation with security checks
const tokenResult = await checkToken(
  db.tokens,
  authSecrets,
  request,
  'jwt-token-string',
  'user-session'
);

// Password verification
const isValidPassword = await compareStringAgainstHash(
  hashedPassword,
  plainPassword
);
```

### File Storage Blocks

**File upload, management, and CDN integration:**

```typescript
import {
  generateSignedUploadUrl,
  generateSignedDownloadUrl,
  deleteFile,
  normalizeFile,
} from '@nodeblocks/backend-sdk';

// Generate signed upload URL
const uploadUrl = await generateSignedUploadUrl(
  fileStorageDriver,
  'images/avatars',
  'image/jpeg',
  1024 * 1024 // 1MB limit
);

// Normalize file metadata
const normalizedFile = normalizeFile(uploadedFile, fileStorageDriver);
```

### Product Blocks

**Product management with variants, images, and inventory:**

```typescript
import {
  createProduct,
  updateProduct,
  deleteProduct,
  createProductVariant,
  createProductImage,
  deleteProductImage,
} from '@nodeblocks/backend-sdk';

// Create product with variants
const product = await createProduct(db.products, productData, organizationId);

// Add product variant
const variant = await createProductVariant(
  db.productVariants,
  productId,
  variantData
);
```

### Chat Blocks

**Real-time messaging and channel management:**

```typescript
import {
  createChannel,
  createChatMessage,
  createChatSubscription,
  upsertChatChannelReadState,
} from '@nodeblocks/backend-sdk';

// Create chat channel
const channel = await createChannel(db.channels, channelData, organizationId);

// Send message
const message = await createChatMessage(
  db.messages,
  channelId,
  messageData,
  senderId
);
```

### Organization Blocks

**Multi-tenant organization management:**

```typescript
import {
  createOrganization,
  addOrganizationMember,
  removeOrganizationMember,
  updateOrganizationMemberRole,
} from '@nodeblocks/backend-sdk';

// Create organization
const org = await createOrganization(
  db.organizations,
  organizationData,
  ownerId
);

// Add member with role
await addOrganizationMember(
  db.organizationMembers,
  organizationId,
  userId,
  'member'
);
```

### User & Profile Blocks

**User management and social features:**

```typescript
import {
  createUser,
  updateUser,
  createProfileFollow,
  createProductLike,
  getAvatarUploadUrl,
} from '@nodeblocks/backend-sdk';

// Create user profile
const user = await createUser(db.users, userData, identityId);

// Social features
await createProfileFollow(db.profileFollows, followerId, followeeId);
await createProductLike(db.productLikes, userId, productId);
```

### Order Blocks

**Order processing and management:**

```typescript
import {
  createOrder,
  updateOrderStatus,
  addOrderItem,
  removeOrderItem,
} from '@nodeblocks/backend-sdk';

// Create order
const order = await createOrder(db.orders, orderData, customerId);

// Update order status
await updateOrderStatus(db.orders, orderId, 'shipped');
```

### Location Blocks

**Address and location management:**

```typescript
import {
  createAddress,
  updateAddress,
  validateAddress,
  geocodeAddress,
} from '@nodeblocks/backend-sdk';

// Create address
const address = await createAddress(db.addresses, addressData, userId);

// Validate and geocode
const validatedAddress = await validateAddress(address);
const geocoded = await geocodeAddress(validatedAddress);
```

### MongoDB Utility Blocks

**Database operation helpers:**

```typescript
import {
  findResources,
  createBaseEntity,
  updateBaseEntity,
} from '@nodeblocks/backend-sdk';

// Find resources with error handling
const resources = await findResources(
  db.collection,
  { filter: { status: 'active' }, options: { limit: 10 } },
  CustomError,
  'Failed to find resources'
);

// Entity management
const newEntity = createBaseEntity(data);
const updatedEntity = updateBaseEntity(existingEntity, changes);
```

### Error Handling

All blocks use structured error handling with specific error types:

```typescript
import {
  AuthenticationBlockError,
  ProductBlockError,
  ChatChannelBlockError,
  OrganizationBlockError,
  UserBlockError,
  OrderBlockError,
  LocationBlockError,
} from '@nodeblocks/backend-sdk';

// Each block has its own error hierarchy
try {
  await createProduct(db, data);
} catch (error) {
  if (error instanceof ProductBlockError) {
    // Handle product-specific error
  }
}
```

### Block Composition

Blocks can be composed together to create complex business operations:

```typescript
import { compose, flatMapAsync, lift } from '@nodeblocks/backend-sdk';

// Compose multiple blocks for a complete workflow
const createUserWithProfile = compose(
  (data) => createUser(db.users, data, data.identityId),
  flatMapAsync((user) => createAddress(db.addresses, data.address, user.id)),
  flatMapAsync((user) =>
    createProfileFollow(db.follows, user.id, data.followingId)
  ),
  lift((user) => ({ user, success: true }))
);
```

## Drivers

The SDK includes a comprehensive set of drivers that provide integrations with external services. These drivers abstract away the complexity of third-party APIs and provide a consistent interface for your applications.

### MongoDB Driver

**Database connection and collection management with curried functions:**

```typescript
import { withMongo, getMongoClient } from '@nodeblocks/backend-sdk';

// Simple connection (non-curried)
const db = getMongoClient('mongodb://localhost:27017', 'myapp');

// Curried connection with authentication - returns a function
const connectToDatabase = withMongo(
  'mongodb+srv://nodeblocks-dev-cluster.8pxjylf.mongodb.net/' +
    '?retryWrites=true&w=majority&appName=nodeblocks-dev-cluster',
  'myapp',
  'adam_local_user',
  '4irpPYzyox7iV3Hyh3et'
);

// Use the curried function to get collections
const products = await connectToDatabase('products');
const users = await connectToDatabase('users');
const orders = await connectToDatabase('orders');

// Each call returns an object with the collection
// products = { products: Collection<Document> }
// users = { users: Collection<Document> }
// orders = { orders: Collection<Document> }

// Use with handlers directly
const payload = {
  context: {
    db: await connectToDatabase('products'),
  },
  params: {
    requestBody: {
      name: 'Product 1',
      price: 100,
    },
  },
};

// Use with services
app.use(
  '/api/products',
  productService(await connectToDatabase('products'), config)
);
app.use('/api/users', userService(await connectToDatabase('users'), config));
```

**Advanced curried usage patterns:**

```typescript
// Create a reusable connection factory
const connectToMyApp = withMongo(
  'mongodb://localhost:27017',
  'myapp',
  'admin',
  'password'
);

// Use with different collections
const users = await connectToMyApp('users');
const orders = await connectToMyApp('orders');
const products = await connectToMyApp('products');

// Fully curried application (all parameters at once)
const productsCollection = await withMongo(
  'mongodb://localhost:27017',
  'myapp',
  'admin',
  'password',
  'products'
);

// Step-by-step currying
const step1 = withMongo('mongodb://localhost:27017');
const step2 = step1('myapp');
const step3 = step2('admin');
const step4 = step3('password');
const final = await step4('products');
```

**Practical usage with handlers and soft delete:**

```typescript
import { withMongo } from '@nodeblocks/backend-sdk';
import { withSoftDelete } from '@nodeblocks/backend-sdk';
import {
  createProduct,
  updateProduct,
  deleteProduct,
  findProducts,
} from '@nodeblocks/backend-sdk';

// Set up database connection
const connectToDatabase = withMongo(
  'mongodb+srv://nodeblocks-dev-cluster.8pxjylf.mongodb.net/' +
    '?retryWrites=true&w=majority&appName=nodeblocks-dev-cluster',
  'adam_tst_1007',
  'adam_local_user',
  '4irpPYzyox7iV3Hyh3et'
);

// Create a handler with soft delete enabled
const createProductWithSoftDelete = withSoftDelete(createProduct);

// Use with handlers
const payload = {
  context: {
    db: await connectToDatabase('products'),
  },
  params: {
    requestBody: {
      name: 'Product 1',
      price: 100,
    },
  },
};

// Execute handler with soft delete
const result = await createProductWithSoftDelete(payload);

// The handler will automatically:
// - Connect to the products collection
// - Apply soft delete logic (deletedAt field)
// - Return the created product
```

### File Storage Driver

**Google Cloud Storage integration with signed URLs:**

```typescript
import { createFileStorageDriver } from '@nodeblocks/backend-sdk';

// Initialize file storage driver
const fileStorage = await createFileStorageDriver(
  'my-project-id',
  'my-storage-bucket',
  {
    signedUrlExpiresInSeconds: 900, // 15 minutes
  }
);

// Generate signed upload URL
const uploadUrl = await fileStora