npm package: @roarexclamation/mobile-base
Description: Reusable base library for React Native apps using Supabase with domain-driven repositories and services.
Author: Roar Exclamation
Latest version: 0.1.0

# @roar/mobile-base

Reusable base library for React Native apps using Supabase with domain-driven repositories and services. It helps you:

- Configure a single Supabase client for your app
- Implement typed repositories and services per domain
- Use generic CRUD that covers 90% of cases
- Keep features consistent across apps and update from one place

## Install

Add as a dependency in each app (recommended via workspace or package registry):

```sh
# if using npm workspaces or local path
npm install @roar/mobile-base --save

# or add via Git URL until published
npm install github:roar/mobile-base
```

This package expects `@supabase/supabase-js` v2 as a peer dependency.

## Quick start

1. Configure Supabase (once at app startup):

```ts
import { configureSupabase } from "@roar/mobile-base";

configureSupabase({
  url: process.env.EXPO_PUBLIC_SUPABASE_URL!,
  anonKey: process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,
});
```

2. Create a typed repository and service for a domain (Users example):

```ts
// users.repository.ts
import { BaseRepository } from "@roar/mobile-base";

type UserRow = {
  id: string;
  email: string;
  full_name: string | null;
  created_at: string;
};
interface UserInsert {
  email: string;
  full_name?: string | null;
}
interface UserUpdate {
  full_name?: string | null;
}

export class UsersRepository extends BaseRepository<
  UserRow,
  UserInsert,
  UserUpdate
> {
  constructor() {
    super({ table: "users", primaryKey: "id" });
  }
}

// users.service.ts
import { BaseService } from "@roar/mobile-base";
import { UsersRepository } from "./users.repository";

export class UsersService extends BaseService<
  Parameters<UsersRepository["create"]>[0] extends infer I ? any : any,
  Parameters<UsersRepository["create"]>[0],
  Parameters<UsersRepository["update"]>[1]
> {
  constructor(repo = new UsersRepository()) {
    super(repo);
  }
}
```

3. Use in your screens/hooks (with React Query example):

```ts
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { UsersRepository } from "../data/users.repository";

const usersRepo = new UsersRepository();

export function useUsersList() {
  return useQuery({
    queryKey: ["users"],
    queryFn: () => usersRepo.list({ withCount: true }),
  });
}

export function useCreateUser() {
  const qc = useQueryClient();
  return useMutation({
    mutationFn: (payload: { email: string; full_name?: string | null }) =>
      usersRepo.create(payload),
    onSuccess: () => qc.invalidateQueries({ queryKey: ["users"] }),
  });
}
```

## API Overview

- `configureSupabase({ url, anonKey, options? })` — set up a shared client
- `setSupabaseClient(client)` — provide your own (typed) client instance
- `getSupabaseClient()` — retrieve the shared client

### BaseRepository<Row, Insert = Row, Update = Partial<Row>>

Constructor: `new BaseRepository({ table, primaryKey = "id", softDeleteColumn? }, client?)`

Methods:

- `getById(id, { select? })`
- `findOne({ where, select? })`
- `list({ where?, orderBy?, limit?, offset?, select?, withCount?, page?, pageSize? })`
- `create(payload, { select? })`
- `update(id, patch, { select? })`
- `delete(id)` — soft deletes when `softDeleteColumn` is provided

### BaseService<Row, Insert, Update>

Thin wrapper around a repository; extend to add validation and orchestration.

## Expo adapter and Auth

Use an Expo-friendly setup that chooses SecureStore on native and AsyncStorage on web:

```ts
import {
  configureSupabaseExpo,
  AuthProvider,
  useAuth,
} from "@roar/mobile-base";

configureSupabaseExpo({
  url: process.env.EXPO_PUBLIC_SUPABASE_URL!,
  anonKey: process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,
});

// In your root component
function AppRoot() {
  return <AuthProvider>{/* your app */}</AuthProvider>;
}

// In screens
const { user, session, signIn, signOut, loading } = useAuth();
```

## Styling with NativeWind (Tailwind for RN)

This package ships a Tailwind preset. In each app:

1. Install deps in the app:

```sh
npm i nativewind tailwindcss
```

2. Configure Babel (app `babel.config.js`):

```js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: ["nativewind/babel"],
  };
};
```

3. Create `tailwind.config.js` in the app:

```js
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  presets: [require("@roar/mobile-base/tailwind-preset")],
  content: [
    "./App.{js,jsx,ts,tsx}",
    "./src/**/*.{js,jsx,ts,tsx}",
    "./components/**/*.{js,jsx,ts,tsx}",
  ],
  theme: { extend: {} },
};
```

4. Use prebuilt components or className in RN components:

```tsx
import { Button, Input, Screen, Card, Text } from "@roar/mobile-base";

<Screen className="bg-neutral-50">
  <Card>
    <Text className="text-xl font-semibold mb-2">Sign in</Text>
    <Input label="Email" placeholder="email" className="mb-3" />
    <Input
      label="Password"
      placeholder="password"
      secureTextEntry
      className="mb-4"
    />
    <Button title="Continue" />
  </Card>
</Screen>;
```

## Filters & pagination

Use `where` with operators: `eq, neq, gt, gte, lt, lte, like, ilike, is, in, contains, containedBy, overlaps`.

```ts
repo.list({
  where: [
    { column: "email", op: "ilike", value: "%@company.com" },
    { column: "status", op: "eq", value: "active" },
  ],
  orderBy: { column: "created_at", ascending: false },
  page: 1,
  pageSize: 20,
  withCount: true,
});
```

## Type-safety with Supabase

For full type-safety, generate your Supabase `Database` types and create a typed client in your app, then pass it to the library:

```ts
import { createClient } from "@supabase/supabase-js";
import type { Database } from "../types/supabase"; // generated
import { setSupabaseClient } from "@roar/mobile-base";

const client = createClient<Database>(SUPABASE_URL, SUPABASE_ANON);
setSupabaseClient(client);
```

You can then extend `BaseRepository` with strongly-typed row/insert/update interfaces derived from `Database["public"]["Tables"]["table_name"]`.

## Keeping apps up to date

Recommended strategies:

- Package: publish `@roar/mobile-base` to a private npm registry; apps just bump the version.
- Monorepo: use workspaces (pnpm/npm/yarn) so apps depend on `base` locally.
- Git submodule: link this repo as a submodule; update by pulling submodule changes.

This base avoids React or Expo dependencies so it works across RN apps. UI helpers or hooks can live in a separate adapter package if needed.

## ORM-like workflow with migrations

Supabase is Postgres under the hood. For an ORM-like developer experience (models + migrations), use the Supabase CLI to manage SQL migrations and generate types. This keeps your schema as code and works great with the repository/service pattern in this base.

See `db/README.md` for commands. Highlights:

- `npm run db:migration:new -- <name>`: new SQL migration file
- `npm run db:reset`: apply migrations to local db
- `npm run db:push`: push migrations to remote project
- `npm run db:types:local` or `db:types:remote`: generate TypeScript types

You can also scaffold repository/service files quickly:

```bash
node ./scripts/scaffold-model.mjs fraud_reports FraudReport ./scaffold
```

Then move the generated files into your app and replace the row/insert/update types with your generated `Database` types for full type-safety.

## Migration tips for existing apps (fraud-tracker-app, marle)

- Replace `lib/supabase.ts` with a call to `configureSupabaseExpo` in your app startup.
- Swap local `AuthContext` usage to the exported `AuthProvider` and `useAuth`.
- Move direct Supabase calls out of screens into `repositories/` and `services/` extending the provided base classes.
- Adopt NativeWind by adding the Babel plugin and Tailwind config using the preset, then gradually replace StyleSheet styles with className utilities or shared UI components.

## License

MIT