agentsclimarketplace

Rn data layer

Skill almasumdev/awesome-react-native-agent-skills/.github/skills/architecture/rn-data-layer

Expert guidance on building an offline-first data layer in React Native with MMKV, WatermelonDB, or SQLite behind a repository abstraction. Use when asked about persistence, caching, or offline support.From its SKILL.md

Install
npx -y skills add almasumdev/awesome-react-native-agent-skills --skill rn-data-layer

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

4.1 KB, 981 tokens by cl100k_base, as published. Nobody here has run it

React Native Data Layer & Offline-First

Instructions

Data flows through a repository interface defined in domain/. Screens and hooks never touch storage clients directly.

1. Pick the Right Store

NeedUse
Small key-value (settings, tokens)react-native-mmkv
Structured rows, moderate sizeop-sqlite (New Architecture)
Rich reactive models, sync@nozbe/watermelondb
Files / blobsreact-native-fs / expo-file-system

Avoid @react-native-async-storage/async-storage for hot paths -- it is serialized and slow.

2. MMKV Adapter

// src/infrastructure/storage/mmkv.ts
import { MMKV } from 'react-native-mmkv';
import type { StateStorage } from 'zustand/middleware';

export const kv = new MMKV({ id: 'app' });

export const mmkvStorage: StateStorage = {
  getItem: (k) => kv.getString(k) ?? null,
  setItem: (k, v) => kv.set(k, v),
  removeItem: (k) => kv.delete(k),
};

3. Repository Interface

// src/domain/articles/ArticleRepository.ts
import type { Article } from './Article';

export interface ArticleRepository {
  list(opts?: { signal?: AbortSignal }): Promise<Article[]>;
  get(id: string): Promise<Article | null>;
  save(article: Article): Promise<void>;
}

4. SQLite Implementation with op-sqlite

// src/infrastructure/articleRepository.sqlite.ts
import { open } from '@op-engineering/op-sqlite';
import type { ArticleRepository } from '@domain/articles/ArticleRepository';
import type { Article } from '@domain/articles/Article';

const db = open({ name: 'app.db' });

db.execute(`
  CREATE TABLE IF NOT EXISTS articles (
    id TEXT PRIMARY KEY NOT NULL,
    title TEXT NOT NULL,
    body TEXT NOT NULL,
    updated_at INTEGER NOT NULL
  )
`);

export const sqliteArticleRepository: ArticleRepository = {
  async list() {
    const { rows } = await db.executeAsync('SELECT * FROM articles ORDER BY updated_at DESC');
    return (rows?._array ?? []) as Article[];
  },
  async get(id) {
    const { rows } = await db.executeAsync('SELECT * FROM articles WHERE id = ?', [id]);
    return (rows?._array?.[0] as Article | undefined) ?? null;
  },
  async save(a) {
    await db.executeAsync(
      'INSERT OR REPLACE INTO articles (id, title, body, updated_at) VALUES (?, ?, ?, ?)',
      [a.id, a.title, a.body, a.updatedAt],
    );
  },
};

5. Offline-First Read-Through Cache

// src/features/articles/api/useArticles.ts
import { useQuery } from '@tanstack/react-query';
import { sqliteArticleRepository as local } from '@infrastructure/articleRepository.sqlite';
import { httpArticleRepository as remote } from '@infrastructure/articleRepository.http';

export function useArticles() {
  return useQuery({
    queryKey: ['articles'],
    queryFn: async ({ signal }) => {
      const cached = await local.list();
      try {
        const fresh = await remote.list({ signal });
        await Promise.all(fresh.map(local.save));
        return fresh;
      } catch {
        if (cached.length) return cached;
        throw new Error('offline and no cache');
      }
    },
    staleTime: 30_000,
  });
}

6. Validate at Boundaries

Validate every remote payload with Zod before it enters the domain:

import { z } from 'zod';

export const ArticleSchema = z.object({
  id: z.string(),
  title: z.string().min(1),
  body: z.string(),
  updatedAt: z.number().int(),
});
export type Article = z.infer<typeof ArticleSchema>;

Checklist

  • All persistence is accessed through a repository interface in domain/.
  • Remote payloads are validated with Zod (or equivalent) before entering the cache.
  • Hot-path key-value reads use MMKV, not AsyncStorage.
  • SQLite / WatermelonDB migrations are versioned and idempotent.
  • Repositories accept an AbortSignal and forward it to fetch.
  • Offline reads return cached data when the network fails; errors bubble only when nothing is cached.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.