npm package: @omodaka/pinatinodb-adapter-mongoose
Description: Mongoose adapter for PinatinoDB - automatically sync MongoDB data to IPFS
Author: Branislav Đalić
Latest version: 0.1.0

# @pinatinodb/mongoose-adapter

Automatically sync your Mongoose/MongoDB data to IPFS with **zero code changes** to your existing application.

## 🚀 Why This Adapter?

- ✅ **Zero Code Changes** - Wrap your existing Mongoose models, that's it
- ✅ **Automatic Sync** - All saves, updates, and deletes sync to IPFS
- ✅ **Perfect Fit** - MongoDB's document model maps 1:1 to IPFS JSON
- ✅ **Selective Sync** - Choose which models to sync
- ✅ **Type-Safe** - Full TypeScript support
- ✅ **Production Ready** - Error handling, logging, configurable strategies

## 📦 Installation

```bash
pnpm add @pinatinodb/mongoose-adapter @pinatinodb/core mongoose
```

## 🎯 Quick Start

```typescript
import mongoose from 'mongoose';
import { withPinatino } from '@pinatinodb/mongoose-adapter';
import { PinatinoDB } from '@pinatinodb/core';

// 1. Create your Mongoose schema as usual
const UserSchema = new mongoose.Schema({
  name: String,
  email: String,
  age: Number
});

// 2. Initialize PinatinoDB
const pinatino = new PinatinoDB({
  pinata: { apiKey: process.env.PINATA_JWT! }
});
await pinatino.initialize();

// 3. Wrap your model with PinatinoDB
const User = withPinatino(mongoose.model('User', UserSchema), {
  pinatino,
  strategy: 'write-through',
  logging: true
});

// 4. Use Mongoose normally - data automatically syncs to IPFS!
const user = await User.create({
  name: 'Alice',
  email: 'alice@example.com',
  age: 28
});
// ✅ Now in both MongoDB AND IPFS!
```

## 🔧 Configuration

### Basic Configuration

```typescript
const User = withPinatino(mongoose.model('User', UserSchema), {
  pinatino, // PinatinoDB instance (required)
  strategy: 'write-through', // Sync strategy (optional)
  logging: true, // Enable logging (optional)
  onError: (error, operation, model) => {
    // Custom error handler (optional)
    console.error(`Error in ${model}.${operation}:`, error);
  }
});
```

### Selective Model Sync

Only sync specific models:

```typescript
// Wrap only the models you want to sync
const User = withPinatino(mongoose.model('User', UserSchema), {
  pinatino,
  models: ['User', 'Post'] // Only sync these
});

const Session = mongoose.model('Session', SessionSchema); // Not wrapped = not synced
```

### Exclude Models

Sync all except specific models:

```typescript
const User = withPinatino(mongoose.model('User', UserSchema), {
  pinatino,
  excludeModels: ['Log', 'Session', 'Cache'] // Skip these
});
```

## 🔄 Sync Strategies

### Write-Through (Default)

Writes to both MongoDB and IPFS synchronously.

```typescript
{
  strategy: 'write-through';
}
```

**Pros:**

- Guaranteed consistency
- Data always in sync

**Cons:**

- Slower writes (waits for IPFS)

**Use when:** Data consistency is critical

### Write-Behind

Writes to MongoDB immediately, queues IPFS async.

```typescript
{
  strategy: 'write-behind';
}
```

**Pros:**

- Fast writes
- No user-facing latency

**Cons:**

- Eventual consistency
- Requires queue infrastructure

**Use when:** Performance is critical

### Read-Through

Reads from MongoDB first, falls back to IPFS.

```typescript
{
  strategy: 'read-through';
}
```

**Pros:**

- Always returns data
- IPFS as backup

**Cons:**

- IPFS is secondary

**Use when:** MongoDB is primary, IPFS is backup

### None

No automatic sync - you handle it manually.

```typescript
{
  strategy: 'none';
}
```

## 📝 Usage Examples

### Basic CRUD

```typescript
// CREATE - Automatically synced to IPFS
const user = await User.create({
  name: 'Bob',
  email: 'bob@example.com'
});
console.log(user._id); // MongoDB ID

// READ - Query from MongoDB as usual
const users = await User.find({ age: { $gte: 18 } });

// UPDATE - Synced to IPFS
await User.findByIdAndUpdate(user._id, { age: 30 });

// DELETE - Removed from IPFS too
await User.findByIdAndDelete(user._id);
```

### Bulk Operations

```typescript
// Insert many - Each synced to IPFS
await User.insertMany([
  { name: 'Alice', email: 'alice@example.com' },
  { name: 'Charlie', email: 'charlie@example.com' }
]);

// Update many - Each updated on IPFS
await User.updateMany({ age: { $lt: 18 } }, { status: 'minor' });

// Delete many
await User.deleteMany({ status: 'inactive' });
```

### Instance Methods

```typescript
// Save instance - Synced to IPFS
const user = new User({ name: 'David', email: 'david@example.com' });
await user.save();

// Modify and save - Update synced to IPFS
user.age = 35;
await user.save();

// Remove instance - Removed from IPFS
await user.remove();
```

### Query from IPFS

```typescript
// Query MongoDB normally
const user = await User.findOne({ email: 'alice@example.com' });

// Also query directly from IPFS via PinatinoDB
const userCollection = pinatino.getCollection('user'); // lowercase model name
if (userCollection) {
  const ipfsUsers = await userCollection.findAll();
  console.log(`${ipfsUsers.length} users on IPFS`);

  ipfsUsers.forEach(u => {
    console.log(`${u.name} - CID: ${u._cid}`);
  });
}
```

## 🎨 Advanced Patterns

### Hybrid Storage

Store different data in different places:

```typescript
// Public profiles → IPFS (permanent, censorship-resistant)
const UserProfile = withPinatino(mongoose.model('UserProfile', ProfileSchema), {
  pinatino,
  logging: true
});

// Private data → MongoDB only (not wrapped)
const UserAuth = mongoose.model('UserAuth', AuthSchema);

// Temporary data → MongoDB only
const Session = mongoose.model('Session', SessionSchema);
```

### Custom Error Handling

```typescript
import * as Sentry from '@sentry/node';

const User = withPinatino(mongoose.model('User', UserSchema), {
  pinatino,
  onError: (error, operation, model) => {
    // Log to error tracking
    Sentry.captureException(error, {
      tags: { model, operation, adapter: 'mongoose' }
    });

    // Custom recovery logic
    if (operation === 'save') {
      // Maybe retry or queue for later
    }
  }
});
```

### Environment-Based Config

```typescript
const isDev = process.env.NODE_ENV === 'development';

const User = withPinatino(mongoose.model('User', UserSchema), {
  pinatino,
  strategy: isDev ? 'none' : 'write-through',
  logging: isDev
});
```

## 🔍 Querying

### MongoDB (Fast)

```typescript
// Query from MongoDB - fast, indexed
const users = await User.find({ age: { $gte: 18 } })
  .sort({ createdAt: -1 })
  .limit(10);
```

### IPFS (Decentralized)

```typescript
// Query from IPFS - decentralized, permanent
const userCollection = pinatino.getCollection('user');
const ipfsUsers = await userCollection.find().where('age', '>=', 18).limit(10).exec();
```

### Hybrid Approach

```typescript
// Try MongoDB first, fallback to IPFS
async function getUser(id: string) {
  try {
    // Fast path: MongoDB
    return await User.findById(id);
  } catch (error) {
    // Fallback: IPFS
    const collection = pinatino.getCollection('user');
    return await collection.findById(id);
  }
}
```

## 🚨 Error Handling

The adapter handles errors gracefully:

```typescript
const User = withPinatino(mongoose.model('User', UserSchema), {
  pinatino,
  logging: true,
  onError: (error, operation, model) => {
    console.error(`IPFS sync failed for ${model}.${operation}`);
    // MongoDB operation succeeds even if IPFS fails
    // Your app continues working
  }
});
```

## 🎯 Best Practices

### 1. Selective Sync

Don't sync everything:

```typescript
// ✅ Good - Only public, permanent data
const User = withPinatino(UserModel, { pinatino, models: ['User', 'Post'] });

// ❌ Bad - Syncing temporary data
const Session = withPinatino(SessionModel, { pinatino }); // Don't do this
```

### 2. Use Appropriate Strategy

```typescript
// User profiles - write-through (consistency matters)
const User = withPinatino(UserModel, { pinatino, strategy: 'write-through' });

// Analytics - write-behind (performance matters)
const Event = withPinatino(EventModel, { pinatino, strategy: 'write-behind' });
```

### 3. Monitor IPFS Sync

```typescript
const User = withPinatino(UserModel, {
  pinatino,
  logging: process.env.NODE_ENV === 'development',
  onError: error => {
    metrics.increment('ipfs.sync.errors');
    // Alert on production errors
  }
});
```

### 4. Handle ObjectId Conversion

```typescript
// MongoDB uses ObjectId, IPFS uses string IDs
const doc = await User.findById(mongoId);
const ipfsId = doc._id.toString(); // Convert for IPFS queries
```

## 📊 Performance

### MongoDB vs IPFS

| Operation      | MongoDB | IPFS         | Recommendation    |
| -------------- | ------- | ------------ | ----------------- |
| **Write**      | ~1ms    | ~50ms        | Use write-behind  |
| **Read**       | ~1ms    | ~20ms        | Read from MongoDB |
| **Query**      | ~5ms    | ~50ms        | Use MongoDB       |
| **Permanence** | Mutable | ✅ Immutable | IPFS for archives |

### Optimization Tips

```typescript
// Batch operations
await User.insertMany(records); // Better than individual saves

// Use lean() for read-only queries
const users = await User.find().lean(); // Faster, no Mongoose docs

// Index frequently queried fields
UserSchema.index({ email: 1 });
UserSchema.index({ createdAt: -1 });
```

## 🔗 Integration with PinatinoDB

### Direct PinatinoDB Queries

```typescript
// Access the underlying PinatinoDB collection
const userCollection = pinatino.getCollection('user');

// Time-travel queries (IPFS immutability)
const history = await userCollection.history(userId);

// Query by CID
const oldVersion = await userCollection.findByCid('bafybe...');
```

## 🧪 Testing

```typescript
// Use 'none' strategy in tests
const User = withPinatino(UserModel, {
  pinatino,
  strategy: 'none' // No IPFS calls during tests
});
```

## 📚 More Examples

Check out the [complete working example](../../../examples/with-mongoose/) for a full implementation.

## 🤝 Contributing

Found a bug or have a feature request? [Open an issue](https://github.com/Omodaka9375/pinatinodb/issues)!

## 📄 License

MIT

---

**Made with ❤️ for MongoDB + Web3 developers**