@omodaka/pinatinodb-adapter-neon
v0.1.0
Published
Neon/PostgreSQL adapter for PinatinoDB - serverless Postgres with IPFS sync
Downloads
6
Maintainers
Readme
@pinatinodb/neon-adapter
Automatically sync your Neon/PostgreSQL data to IPFS - perfect for serverless and edge functions.
🚀 Why Neon + PinatinoDB?
- ✅ Serverless-First - Built for Vercel, Netlify, Cloudflare Workers
- ✅ Edge-Compatible - Runs in edge runtimes (no Node.js required)
- ✅ Zero Cold Starts - Neon's instant connections + IPFS backup
- ✅ Automatic Sync - SQL queries auto-sync to IPFS
- ✅ Type-Safe - Full TypeScript support
- ✅ Cost-Effective - Pay only for what you use
📦 Installation
pnpm add @pinatinodb/neon-adapter @pinatinodb/core @neondatabase/serverless🎯 Quick Start
import { neon } from '@neondatabase/serverless';
import { withPinatino } from '@pinatinodb/neon-adapter';
import { PinatinoDB } from '@pinatinodb/core';
// 1. Initialize PinatinoDB
const pinatino = new PinatinoDB({
pinata: { apiKey: process.env.PINATA_JWT! }
});
await pinatino.initialize();
// 2. Wrap Neon SQL with PinatinoDB
const sql = withPinatino(neon(process.env.DATABASE_URL!), {
pinatino,
strategy: 'write-through',
logging: true
});
// 3. Use SQL normally - auto-syncs to IPFS!
const users = await sql`
INSERT INTO users (name, email)
VALUES ('Alice', '[email protected]')
RETURNING *
`;
// ✅ Now in both Postgres AND IPFS!🔧 Configuration
Basic Configuration
const sql = withPinatino(neon(DATABASE_URL), {
pinatino, // PinatinoDB instance (required)
strategy: 'write-through', // Sync strategy (optional)
tables: ['users', 'posts'], // Only sync these (optional)
excludeTables: ['sessions'], // Skip these (optional)
logging: true, // Enable logging (optional)
primaryKey: 'id', // Primary key field (default: 'id')
onError: (error, operation, table) => {
// Custom error handler (optional)
console.error(`Error in ${table}.${operation}:`, error);
}
});Selective Table Sync
// Only sync important tables
const sql = withPinatino(neon(DATABASE_URL), {
pinatino,
tables: ['users', 'posts', 'products']
});
// Or exclude temporary tables
const sql = withPinatino(neon(DATABASE_URL), {
pinatino,
excludeTables: ['sessions', 'logs', 'cache']
});🔄 Sync Strategies
Write-Through (Default)
{
strategy: 'write-through';
}- Writes to both Postgres and IPFS synchronously
- Pros: Guaranteed consistency
- Cons: Slower writes (~50ms IPFS overhead)
- Use when: Data integrity is critical
Write-Behind
{
strategy: 'write-behind';
}- Writes to Postgres immediately, queues IPFS async
- Pros: Fast writes, no user-facing latency
- Cons: Eventual consistency
- Use when: Performance is critical (recommended for production)
None
{
strategy: 'none';
}- No automatic sync
- Use when: You want manual control or testing
📝 Usage Examples
Basic CRUD
// INSERT - Automatically synced to IPFS
const result = await sql`
INSERT INTO users (name, email, age)
VALUES ('Bob', '[email protected]', 35)
RETURNING *
`;
// SELECT - Query from Postgres
const users = await sql`
SELECT * FROM users
WHERE age >= 18
ORDER BY created_at DESC
LIMIT 10
`;
// UPDATE - Synced to IPFS
await sql`
UPDATE users
SET age = 36
WHERE email = '[email protected]'
RETURNING *
`;
// DELETE - Removed from IPFS
await sql`
DELETE FROM users
WHERE id = ${userId}
`;Transactions
import { neon, neonConfig } from '@neondatabase/serverless';
neonConfig.fetchConnectionCache = true;
const sql = withPinatino(neon(DATABASE_URL), { pinatino });
// Transactions work normally
await sql.transaction(async tx => {
await tx`INSERT INTO users (name) VALUES ('Charlie')`;
await tx`INSERT INTO posts (title, author_id) VALUES ('Hello', currval('users_id_seq'))`;
});
// Both inserts synced to IPFS after commitBatch Operations
// Batch insert
const newUsers = [
{ name: 'Alice', email: '[email protected]' },
{ name: 'Bob', email: '[email protected]' },
{ name: 'Charlie', email: '[email protected]' }
];
for (const user of newUsers) {
await sql`INSERT INTO users ${sql(user)} RETURNING *`;
}
// All synced to IPFS🌐 Serverless Examples
Vercel Edge Function
// app/api/users/route.ts
import { neon } from '@neondatabase/serverless';
import { withPinatino } from '@pinatinodb/neon-adapter';
import { PinatinoDB } from '@pinatinodb/core';
export const runtime = 'edge';
const pinatino = new PinatinoDB({
pinata: { apiKey: process.env.PINATA_JWT! }
});
await pinatino.initialize();
const sql = withPinatino(neon(process.env.DATABASE_URL!), {
pinatino,
strategy: 'write-behind' // Fast for edge
});
export async function POST(request: Request) {
const body = await request.json();
const user = await sql`
INSERT INTO users ${sql(body)}
RETURNING *
`;
return Response.json(user);
}Cloudflare Workers
import { neon } from '@neondatabase/serverless';
import { withPinatino } from '@pinatinodb/neon-adapter';
export default {
async fetch(request: Request, env: Env) {
const pinatino = new PinatinoDB({
pinata: { apiKey: env.PINATA_JWT }
});
await pinatino.initialize();
const sql = withPinatino(neon(env.DATABASE_URL), {
pinatino,
strategy: 'write-behind'
});
const users = await sql`SELECT * FROM users LIMIT 10`;
return new Response(JSON.stringify(users), {
headers: { 'Content-Type': 'application/json' }
});
}
};Next.js API Route
// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { neon } from '@neondatabase/serverless';
import { withPinatino } from '@pinatinodb/neon-adapter';
import { PinatinoDB } from '@pinatinodb/core';
const pinatino = new PinatinoDB({
pinata: { apiKey: process.env.PINATA_JWT! }
});
const sql = withPinatino(neon(process.env.DATABASE_URL!), {
pinatino,
logging: process.env.NODE_ENV === 'development'
});
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'POST') {
const user = await sql`
INSERT INTO users (name, email)
VALUES (${req.body.name}, ${req.body.email})
RETURNING *
`;
res.json(user[0]);
} else {
const users = await sql`SELECT * FROM users`;
res.json(users);
}
}🎨 Advanced Patterns
Hybrid Query (Postgres + IPFS)
// Fast queries from Postgres
const recentUsers = await sql`
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 10
`;
// Historical queries from IPFS
const userCollection = pinatino.getCollection('users');
const allIPFSUsers = await userCollection.findAll();
console.log(`Total users in IPFS: ${allIPFSUsers.length}`);Environment-Based Sync
const isDev = process.env.NODE_ENV === 'development';
const sql = withPinatino(neon(DATABASE_URL), {
pinatino,
strategy: isDev ? 'none' : 'write-behind',
logging: isDev
});Custom Error Handling
import * as Sentry from '@sentry/node';
const sql = withPinatino(neon(DATABASE_URL), {
pinatino,
onError: (error, operation, table) => {
Sentry.captureException(error, {
tags: { table, operation, adapter: 'neon' }
});
}
});📊 Performance
Neon vs Traditional Postgres
| Feature | Traditional Postgres | Neon | | --------------- | -------------------- | ----------------- | | Cold Start | ~1-5s | ~0ms (instant) | | Scaling | Manual | Automatic | | Edge Deploy | ❌ | ✅ | | Cost | Fixed | Pay-per-use | | IPFS Sync | ✅ (with adapter) | ✅ (with adapter) | | Serverless | Limited | ✅ Optimized |
Query Performance
| Operation | Neon | + IPFS (write-through) | + IPFS (write-behind) | | ---------- | ----- | ---------------------- | --------------------- | | SELECT | ~5ms | ~5ms (no sync) | ~5ms (no sync) | | INSERT | ~10ms | ~60ms | ~10ms | | UPDATE | ~10ms | ~60ms | ~10ms | | DELETE | ~5ms | ~5ms | ~5ms |
Recommendation: Use write-behind for production (10ms latency instead of 60ms)
🔍 Querying
From Postgres (Fast)
// Indexed, fast queries
const users = await sql`
SELECT * FROM users
WHERE email = ${email}
LIMIT 1
`;From IPFS (Decentralized)
// Permanent, decentralized storage
const userCollection = pinatino.getCollection('users');
const ipfsUser = await userCollection.findById(userId);
console.log(`User CID: ${ipfsUser._cid}`);Hybrid Fallback
async function getUser(id: string) {
try {
// Try Postgres first (fast)
const result = await sql`SELECT * FROM users WHERE id = ${id}`;
return result[0];
} catch (error) {
// Fallback to IPFS
const collection = pinatino.getCollection('users');
return await collection.findById(id);
}
}🎯 Best Practices
1. Use Write-Behind in Production
const sql = withPinatino(neon(DATABASE_URL), {
pinatino,
strategy: 'write-behind' // Fast, async sync
});2. Selective Sync
// Only sync permanent data
const sql = withPinatino(neon(DATABASE_URL), {
pinatino,
tables: ['users', 'posts', 'products'],
excludeTables: ['sessions', 'logs', 'cache']
});3. Enable Connection Pooling
import { neonConfig } from '@neondatabase/serverless';
neonConfig.fetchConnectionCache = true;4. Handle Errors Gracefully
const sql = withPinatino(neon(DATABASE_URL), {
pinatino,
onError: error => {
// Postgres operation succeeds even if IPFS fails
console.error('IPFS sync failed:', error);
}
});🚨 Limitations
- DELETE Operations: Cannot automatically sync deletes (need to query before delete)
- Complex Queries: Only basic INSERT/UPDATE/DELETE are auto-synced
- Bulk Operations: Large batch operations may be slow with write-through
🧪 Testing
// Disable IPFS sync in tests
const sql = withPinatino(neon(TEST_DATABASE_URL), {
pinatino,
strategy: 'none'
});📚 More Examples
Check out the complete working example for serverless patterns.
🤝 Contributing
Found a bug? Open an issue!
📄 License
MIT
Made with ❤️ for serverless developers who want Web3 without complexity
