@omodaka/pinatinodb-adapter-mongoose
v0.1.0
Published
Mongoose adapter for PinatinoDB - automatically sync MongoDB data to IPFS
Downloads
18
Maintainers
Readme
@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
pnpm add @pinatinodb/mongoose-adapter @pinatinodb/core mongoose🎯 Quick Start
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: '[email protected]',
age: 28
});
// ✅ Now in both MongoDB AND IPFS!🔧 Configuration
Basic Configuration
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:
// 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 syncedExclude Models
Sync all except specific models:
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.
{
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.
{
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.
{
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.
{
strategy: 'none';
}📝 Usage Examples
Basic CRUD
// CREATE - Automatically synced to IPFS
const user = await User.create({
name: 'Bob',
email: '[email protected]'
});
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
// Insert many - Each synced to IPFS
await User.insertMany([
{ name: 'Alice', email: '[email protected]' },
{ name: 'Charlie', email: '[email protected]' }
]);
// Update many - Each updated on IPFS
await User.updateMany({ age: { $lt: 18 } }, { status: 'minor' });
// Delete many
await User.deleteMany({ status: 'inactive' });Instance Methods
// Save instance - Synced to IPFS
const user = new User({ name: 'David', email: '[email protected]' });
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
// Query MongoDB normally
const user = await User.findOne({ email: '[email protected]' });
// 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:
// 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
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
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)
// Query from MongoDB - fast, indexed
const users = await User.find({ age: { $gte: 18 } })
.sort({ createdAt: -1 })
.limit(10);IPFS (Decentralized)
// Query from IPFS - decentralized, permanent
const userCollection = pinatino.getCollection('user');
const ipfsUsers = await userCollection.find().where('age', '>=', 18).limit(10).exec();Hybrid Approach
// 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:
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:
// ✅ 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 this2. Use Appropriate Strategy
// 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
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
// 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
// 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
// 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
// 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 for a full implementation.
🤝 Contributing
Found a bug or have a feature request? Open an issue!
📄 License
MIT
Made with ❤️ for MongoDB + Web3 developers
