npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dataql/react-native

v0.5.0

Published

DataQL React Native SDK with offline-first capabilities and clean API

Downloads

319

Readme

@dataql/react-native

An offline-first React Native SDK for DataQL with automatic synchronization using Drizzle ORM and Expo SQLite.

Features

  • Offline-First: All data operations work offline and sync automatically when online
  • Drizzle ORM: Built on top of Drizzle ORM for type-safe database operations
  • Expo SQLite: Uses Expo SQLite for local storage with change listeners
  • Live Queries: Real-time data updates using Drizzle's live query capabilities
  • Automatic Sync: Background synchronization with configurable intervals
  • Conflict Resolution: Built-in conflict detection and resolution strategies
  • React Hooks: Easy-to-use React hooks for data operations
  • TypeScript: Full TypeScript support with type inference

Installation

npm install @dataql/react-native drizzle-orm expo-sqlite@next
npm install -D drizzle-kit babel-plugin-inline-import

Setup

1. Configure Babel and Metro

Update your babel.config.js:

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: [["inline-import", { extensions: [".sql"] }]],
  };
};

Update your metro.config.js:

const { getDefaultConfig } = require("expo/metro-config");

const config = getDefaultConfig(__dirname);
config.resolver.sourceExts.push("sql");

module.exports = config;

2. Generate Migrations

Create a drizzle.config.ts file:

import type { Config } from "drizzle-kit";

export default {
  schema: "./src/db/schema.ts",
  out: "./drizzle",
  dialect: "sqlite",
  driver: "expo",
} satisfies Config;

Generate migrations:

npx drizzle-kit generate

Quick Start

1. Initialize DataQL Client

import { DataQLClient, createDefaultConfig } from "@dataql/react-native";

const config = createDefaultConfig("https://your-worker-url.com/api");
const dataQLClient = new DataQLClient(config);

// Initialize the client
await dataQLClient.initialize();

Using Custom Network Transport

The SDK supports custom network transport, just like the Core SDK:

import {
  DataQLClient,
  createDefaultConfig,
  CustomRequestConnection,
} from "@dataql/react-native";

// Create a custom connection
const customConnection: CustomRequestConnection = {
  async request(url: string, options: RequestInit): Promise<Response> {
    // Your custom network implementation
    // Could route through other SDKs, add authentication, etc.
    console.log("Custom request to:", url);
    return fetch(url, options);
  },
};

// Configure with custom connection
const config = createDefaultConfig(
  "https://your-worker-url.com/api",
  "myapp.db",
  {
    customConnection,
  }
);

const dataQLClient = new DataQLClient(config);

// Or set it later
dataQLClient.setCustomConnection(customConnection);

2. Use in React Components

import React from 'react';
import { View, Text, Button } from 'react-native';
import { useQuery, useMutation, useSync } from '@dataql/react-native';

function UserList() {
  const { data: users, loading, error, refetch } = useQuery('users');
  const { create, update, delete: deleteUser } = useMutation();
  const { sync, syncStatus, isOnline } = useSync();

  const handleCreateUser = async () => {
    await create('users', {
      name: 'John Doe',
      email: '[email protected]',
    });
    refetch();
  };

  return (
    <View>
      <Text>Users ({isOnline ? 'Online' : 'Offline'})</Text>
      <Text>Pending: {syncStatus.pendingOperations}</Text>

      {loading && <Text>Loading...</Text>}
      {error && <Text>Error: {error}</Text>}

      {users.map(user => (
        <View key={user.id}>
          <Text>{user.name} - {user.email}</Text>
          <Button
            title="Delete"
            onPress={() => deleteUser('users', user.id)}
          />
        </View>
      ))}

      <Button title="Add User" onPress={handleCreateUser} />
      <Button title="Sync Now" onPress={sync} />
    </View>
  );
}

3. Handle Migrations

import { useDatabaseMigrations } from '@dataql/react-native';
import migrations from './drizzle/migrations';

function App() {
  const { success, error } = useDatabaseMigrations(
    dataQLClient.getDatabase(),
    migrations
  );

  if (error) {
    return <Text>Migration error: {error.message}</Text>;
  }

  if (!success) {
    return <Text>Migration in progress...</Text>;
  }

  return <UserList />;
}

API Reference

DataQLClient

The main client class for managing offline data and synchronization.

const client = new DataQLClient(config);
await client.initialize();

// Data operations
await client.createOffline("users", userData);
await client.updateOffline("users", userId, updateData);
await client.deleteOffline("users", userId);
const results = await client.queryOffline("users");

// Sync operations
await client.sync();
client.startAutoSync();
client.stopAutoSync();

// Status
const status = await client.getSyncStatus();
const isOnline = client.isOnline();

// Custom network transport
client.setCustomConnection(customConnection);
client.setWorkerBinding(workerBinding);
client.clearCustomConnection();

Hooks

useQuery

Query data with offline-first approach:

const { data, loading, error, refetch, isFromCache, lastUpdated } = useQuery(
  "tableName",
  filter
);

useMutation

Perform create, update, delete operations:

const { create, update, delete, loading, error } = useMutation();

await create('users', userData);
await update('users', userId, updateData);
await delete('users', userId);

Unique Document Creation

DataQL React Native supports createUnique() method through the underlying BaseDataQLClient, preventing duplicate documents based on comparable fields (excluding ID and subdocument fields).

import { useCallback } from 'react';
import { useMutation } from '@dataql/react-native';

function UserRegistration() {
  const { create, loading, error } = useMutation();

  const createUniqueUser = useCallback(async (userData) => {
    // Note: createUnique is accessed through the base client
    // You can implement this via custom hooks or direct client access
    const dataQLClient = /* get your client instance */;

    // Use the core SDK's createUnique functionality
    const userCollection = dataQLClient.collection('users', userSchema);

    const result = await userCollection.createUnique({
      name: userData.name,
      email: userData.email,
      age: userData.age,
      preferences: {
        theme: 'dark',
        notifications: true,
      },
    });

    if (result.isExisting) {
      console.log('User already exists:', result.insertedId);
    } else {
      console.log('Created new user:', result.insertedId);
    }

    return result;
  }, []);

  return (
    <View>
      <Button
        title="Register User"
        onPress={() => createUniqueUser({
          name: 'John Doe',
          email: '[email protected]',
          age: 30
        })}
        disabled={loading}
      />
      {error && <Text>Error: {error}</Text>}
    </View>
  );
}

Excluded from comparison:

  • ID fields: id, _id, and any field containing 'id'
  • Auto-generated timestamps: createdAt, updatedAt
  • Subdocument objects: nested objects like preferences, profile
  • Subdocument arrays: arrays of objects like addresses, reviews

Compared fields:

  • Primitive fields: strings, numbers, booleans
  • Enum fields: category selections
  • Simple arrays: arrays of primitive values

useSync

Manage synchronization:

const {
  syncStatus,
  sync,
  startAutoSync,
  stopAutoSync,
  loading,
  error,
  isOnline,
  isSyncing,
} = useSync();

useNetworkStatus

Monitor network connectivity:

const { isOnline } = useNetworkStatus();

Live Queries

DataQL React Native supports live queries with two approaches:

  1. DataQL-style - Consistent with other DataQL operations (tableName, filter)
  2. Raw Drizzle - Direct database queries for advanced use cases

Requirements:

  • Database opened with enableChangeListener: true (DataQL does this by default)
  • Latest versions: drizzle-orm@^0.44.2 and expo-sqlite@^15.2.12

DataQL-Style Live Queries (Recommended):

import { useLiveQuery } from '@dataql/react-native';

function LiveUserList() {
  // Same signature as useQuery - automatically updates when data changes
  const { data: users, error, updatedAt } = useLiveQuery('users', { isActive: true });

  return (
    <View>
      <Text>Active Users: {users?.length || 0}</Text>
      {updatedAt && (
        <Text>Last Updated: {updatedAt.toLocaleTimeString()}</Text>
      )}
      {error && <Text>Error: {error}</Text>}

      {users?.map(user => (
        <Text key={user.id}>{user.name}</Text>
      ))}
    </View>
  );
}

// Consistent with other DataQL operations:
const { data: allUsers } = useLiveQuery('users');                    // All users
const { data: activeUsers } = useLiveQuery('users', { isActive: true }); // Filtered users
const { data: adminUsers } = useLiveQuery('users', { role: 'admin' });   // Admin users

Raw Drizzle Live Queries (Advanced):

import { useRawLiveQuery } from '@dataql/react-native';

function AdvancedLiveQuery() {
  // Get database instance from DataQL client
  const db = dataQLClient.getDatabase();

  // Complex live queries with joins, aggregations, etc.
  const { data: userStats, error, updatedAt } = useRawLiveQuery(
    db.select({
      totalUsers: count(users.id),
      activeUsers: count(users.id).where(eq(users.isActive, true)),
    }).from(users)
  );

  const { data: userProjects } = useRawLiveQuery(
    db.select()
      .from(projects)
      .innerJoin(users, eq(projects.ownerId, users.id))
      .where(eq(users.id, currentUserId))
      .orderBy(desc(projects.createdAt))
  );

  return (
    <View>
      <Text>Total Users: {userStats?.totalUsers}</Text>
      <Text>Active Users: {userStats?.activeUsers}</Text>
    </View>
  );
}

API Comparison:

| Hook | Signature | Use Case | | ----------------- | ---------------------- | ------------------------------------- | | useQuery | (tableName, filter?) | Standard offline-first queries | | useLiveQuery | (tableName, filter?) | Real-time updates with same API | | useRawLiveQuery | (drizzleQuery) | Advanced queries, joins, aggregations |

Features:

  • ✅ Consistent API with other DataQL operations
  • ✅ Automatic re-rendering on data changes
  • ✅ Built-in error handling and timestamps
  • ✅ Works offline with local SQLite data
  • ✅ Seamless integration with DataQL's CRUD operations
  • ✅ Optimized performance for complex queries

For complete Drizzle documentation, see: Drizzle ORM Expo SQLite Guide

Configuration

DataQLReactNativeConfig

interface DataQLReactNativeConfig {
  databaseName: string;
  syncConfig: SyncConfig;
  enableChangeListener?: boolean;
  debug?: boolean;
}

interface SyncConfig {
  workerUrl: string;
  syncInterval: number; // milliseconds
  retryCount: number;
  batchSize: number;
  autoSync: boolean;
  // Network transport options
  customConnection?: CustomRequestConnection;
  workerBinding?: WorkerBinding;
}

interface CustomRequestConnection {
  request(url: string, options: RequestInit): Promise<Response>;
}

interface WorkerBinding {
  fetch(request: Request): Promise<Response>;
}

Default Configuration

const config = createDefaultConfig("https://your-api.com", "myapp.db");
// Returns:
// {
//   databaseName: 'myapp.db',
//   syncConfig: {
//     workerUrl: 'https://your-api.com',
//     syncInterval: 30000, // 30 seconds
//     retryCount: 3,
//     batchSize: 50,
//     autoSync: true,
//   },
//   enableChangeListener: true,
//   debug: false,
// }

Architecture

The SDK follows an offline-first architecture:

  1. Local SQLite Database: All data is stored locally using Expo SQLite
  2. Operation Queue: Changes are queued for synchronization
  3. Background Sync: Automatic synchronization when online
  4. Conflict Resolution: Handles conflicts between local and server data
  5. Event System: Real-time updates via event listeners

Best Practices

  1. Initialize Early: Initialize the DataQL client early in your app lifecycle
  2. Handle Offline States: Always show appropriate UI for offline states
  3. Monitor Sync Status: Display sync status and pending operations to users
  4. Error Handling: Implement proper error handling for sync failures
  5. Performance: Use live queries sparingly for better performance

Troubleshooting

Migration Issues

If you encounter migration errors:

  1. Ensure babel-plugin-inline-import is properly configured
  2. Check that .sql files are included in Metro resolver
  3. Verify Drizzle configuration uses driver: 'expo'

Sync Issues

If synchronization fails:

  1. Check network connectivity
  2. Verify worker URL configuration
  3. Monitor pending operations
  4. Check server API compatibility

License

MIT