Skip to content

Latest commit

 

History

History
396 lines (280 loc) · 9.15 KB

File metadata and controls

396 lines (280 loc) · 9.15 KB

Setup Guide

Plugin Development Setup

Prerequisites

  • Node.js 18+
  • npm or bun
  • Xcode (for iOS development)
  • Android Studio (for Android development)

Initial Setup

  1. Install dependencies:
cd capacitor-native-sql
npm install
  1. Build the plugin:
npm run build

iOS Development

The iOS implementation uses Swift and requires a proper HTTP server library for production use.

Recommended HTTP Server Library

Add one of these to your Package.swift:

Option 1: GCDWebServer (Recommended)

.package(url: "https://github.com/swisspol/GCDWebServer.git", from: "3.5.4")

Option 2: Telegraph

.package(url: "https://github.com/Building42/Telegraph.git", from: "0.30.0")

Option 3: Swifter

.package(url: "https://github.com/httpswift/swifter.git", from: "1.5.0")

iOS Notes

  • The current iOS implementation includes placeholder HTTP server code
  • For production, integrate one of the recommended libraries in SQLHTTPServer.swift
  • See comments in the file for GCDWebServer integration example

Android Development

The Android implementation uses NanoHTTPD which is already configured in build.gradle.

Testing Android

npm run verify:android

Web Development

The web implementation uses sql.js (SQLite compiled to WebAssembly) loaded from CDN.

Example App Setup

  1. Navigate to example app:
cd example-app
npm install
  1. Run development server:
npm run dev
  1. Open browser to http://localhost:3000

Integration into Your App

Installation

npm install @capgo/capacitor-native-sql
npx cap sync

iOS Configuration

  1. Required: Allow local networking in Info.plist. This plugin runs a local HTTP server that iOS ATS blocks by default:
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsLocalNetworking</key>
    <true/>
</dict>

This only permits cleartext to loopback addresses (localhost / 127.0.0.1) — it does not weaken ATS for external connections.

  1. If using CocoaPods, the plugin will be automatically linked

  2. If using SPM, add to your project's Package Dependencies

Android Configuration

  1. The plugin is automatically linked via Gradle

  2. Required: Allow cleartext traffic to localhost. This plugin runs a local HTTP server that Android 9+ blocks by default.

    Option A — Scoped to localhost only (recommended):

    Create android/app/src/main/res/xml/network_security_config.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
        <domain-config cleartextTrafficPermitted="true">
            <domain includeSubdomains="false">localhost</domain>
            <domain includeSubdomains="false">127.0.0.1</domain>
        </domain-config>
    </network-security-config>

    Then reference it in your AndroidManifest.xml:

    <application
        android:networkSecurityConfig="@xml/network_security_config">
        ...
    </application>

    Option B — Allow all cleartext (simpler but less secure):

    <application
        android:usesCleartextTraffic="true">
        ...
    </application>

Architecture Details

Communication Protocol

The plugin uses a custom HTTP-based protocol:

  1. JavaScript Layer: Uses fetch() API to communicate with native
  2. Native Layer: Runs a local HTTP server on 127.0.0.1:PORT
  3. Authentication: Bearer token generated per connection
  4. Data Format: JSON with special handling for binary data (base64)

Endpoints

  • POST /execute - Execute single SQL statement
  • POST /batch - Execute multiple statements
  • POST /transaction/begin - Begin transaction
  • POST /transaction/commit - Commit transaction
  • POST /transaction/rollback - Rollback transaction

Security

  • Server only listens on localhost (127.0.0.1)
  • Each connection generates a unique authentication token
  • Token must be provided in Authorization header
  • Database name must be provided in X-Database header

Performance Tuning

Batch Operations

Always use batch operations for multiple inserts:

// Slow - multiple round trips
for (let i = 0; i < 1000; i++) {
  await db.run('INSERT INTO items (name) VALUES (?)', [`Item ${i}`]);
}

// Fast - single batch operation
const operations = [];
for (let i = 0; i < 1000; i++) {
  operations.push({
    statement: 'INSERT INTO items (name) VALUES (?)',
    params: [`Item ${i}`]
  });
}
await db.executeBatch(operations);

Transactions

Use transactions for related operations:

await db.transaction(async (tx) => {
  // All operations here are atomic
  await tx.run('...');
  await tx.run('...');
});

Binary Data

Store binary data directly (no base64 encoding needed in your code):

const imageData = new Uint8Array([...]);
await db.run('INSERT INTO images (data) VALUES (?)', [imageData]);

Sync System Integration

Example: CRDTs

interface CRDTOperation {
  id: string;
  timestamp: number;
  table: string;
  operation: 'INSERT' | 'UPDATE' | 'DELETE';
  data: any;
  vector_clock: Record<string, number>;
}

class CRDTSyncEngine {
  async applyRemoteOperation(op: CRDTOperation) {
    await db.transaction(async (tx) => {
      // Check if operation already applied
      const existing = await tx.query(
        'SELECT * FROM operations WHERE id = ?',
        [op.id]
      );

      if (existing.length > 0) {
        // Resolve conflict using vector clock
        // ...
      }

      // Apply operation
      await tx.run(op.operation, op.data);

      // Record operation
      await tx.run(
        'INSERT INTO operations (id, timestamp, data) VALUES (?, ?, ?)',
        [op.id, op.timestamp, JSON.stringify(op)]
      );
    });
  }
}

Example: Operational Transform

class OTSyncEngine {
  async applyOperation(op: Operation) {
    await db.transaction(async (tx) => {
      // Get all operations since this one's parent
      const concurrent = await tx.query(
        'SELECT * FROM operations WHERE timestamp > ? ORDER BY timestamp',
        [op.parentTimestamp]
      );

      // Transform operation against concurrent operations
      let transformed = op;
      for (const concurrentOp of concurrent) {
        transformed = this.transform(transformed, concurrentOp);
      }

      // Apply transformed operation
      await tx.run(transformed.sql, transformed.params);
    });
  }
}

Encryption

Encryption is optional and uses SQLCipher. The plugin does not bundle SQLCipher — add it to your app only when you need encryption.

Android

Add the SQLCipher runtime dependency to your app-level android/app/build.gradle:

dependencies {
    implementation 'net.zetetic:sqlcipher-android:4.13.0'
}

Then connect with encryption:

const db = await FastSQL.connect({
  database: 'secure',
  encrypted: true,
  encryptionKey: 'my-secret-key',
});

iOS

SQLCipher is optional on iOS. To enable it with CocoaPods, update your ios/App/Podfile to use the SQLCipher subspec:

pod 'CapgoCapacitorFastSql/SQLCipher', :path => '../../node_modules/@capgo/capacitor-fast-sql'

Then run pod install from ios/App. If you skip this subspec, keep encrypted: false — encryption calls will return a descriptive error.

What Happens Without SQLCipher

If you pass encrypted: true without the SQLCipher dependency, the plugin returns a descriptive error:

Encryption is not available. Add SQLCipher to your build to enable encryption.

The app will not crash — Android additionally catches failures at two levels:

  1. Class check: Before attempting to load EncryptedSQLDatabase, the plugin verifies the SQLCipher class is on the classpath via Class.forName.
  2. Native library check: EncryptedSQLDatabase catches UnsatisfiedLinkError from System.loadLibrary("sqlcipher") and converts it to a descriptive exception.

Troubleshooting

iOS: Server Not Starting

  • Check that your Info.plist allows local networking
  • Verify that a proper HTTP server library is integrated
  • Check console logs for port binding errors

Android: Connection Refused

  • Verify cleartext traffic is allowed in AndroidManifest.xml
  • Check that NanoHTTPD dependency is properly included
  • Check logcat for server startup errors

Web: sql.js Not Loading

  • Check browser console for CDN load errors
  • Verify network connectivity
  • Consider hosting sql.js locally for offline use

All Platforms: Slow Performance

  • Use batch operations instead of individual statements
  • Wrap related operations in transactions
  • Consider indexing frequently queried columns
  • Profile with browser DevTools or native profilers

Testing

Unit Tests

npm run test

iOS Integration Tests

npm run verify:ios

Android Integration Tests

npm run verify:android

Publishing

  1. Update version in package.json
  2. Build the plugin: npm run build
  3. Publish to npm: npm publish

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Write tests
  5. Submit a pull request

License

MIT - See LICENSE file for details