Development rules for Safarnak - AI-powered offline-first travel companion with Expo React Native, Cloudflare Workers, and GraphQL
Development guidelines for Safarnak, a full-stack offline-first travel application built with Expo React Native and Cloudflare Workers.
Safarnak is a unified single-root monorepo combining client and server code:
**Frontend**: Expo ~54.0.20, React Native 0.81.5, React 19.1.0, Expo Router ~6.0.13, NativeWind ^4.1.21, Apollo Client 3.8.0, Redux Toolkit ^2.9.2
**Backend**: Cloudflare Workers, GraphQL Yoga ^5.16.0, Drizzle ORM ^0.44.7, graphql-workers-subscriptions ^0.1.6
**Shared**: TypeScript ~5.9.3, GraphQL ^16.11.0, ESLint ^9.38.0, Prettier ^3.6.2
**Understand the monorepo layout**:
**Always use path aliases, never relative imports**:
```typescript
"@/*" → "./*"
"@ui/*" → "./ui/*"
"@graphql/*" → "./graphql/*"
"@database/*" → "./database/*"
"@worker/*" → "./worker/*"
"@api" → "./api"
"@api/*" → "./api/*"
"@state" → "./ui/state"
"@hooks" → "./ui/hooks"
"@constants" → "./constants"
"@locales" → "./locales"
"@assets" → "./assets"
```
**Use unified schema with separate adapters**:
```typescript
// In worker resolvers
import { getServerDB } from '@database/server';
const db = getServerDB(env.DB);
// In client components
import { getLocalDB } from '@database/client';
const db = await getLocalDB();
```
**Schema is shared** (`database/schema.ts`) - single source of truth defining both server tables (users, trips) and client cached tables (cachedUsers, cachedTrips) with UUID IDs.
**After ANY schema or operation changes**:
```bash
yarn codegen
```
**Never manually edit**:
**Workflow**:
1. Update `graphql/schema.graphql`
2. Update or add queries in `graphql/queries/*.graphql`
3. Run `yarn codegen`
4. Implement resolvers in `worker/queries/`, `worker/mutations/`, or `worker/subscriptions/`
**Prefer Tailwind classes via className**:
```tsx
// Good
<View className="flex-1 bg-white p-4">
<Text className="text-lg font-bold text-gray-900">Title</Text>
</View>
// Avoid inline styles
<View style={{ flex: 1, backgroundColor: 'white' }}>
```
**Before testing APIs locally**:
```bash
yarn db:generate # Generate migration from schema changes
yarn db:migrate # Apply migrations to local D1
```
1. **Never add** `"type": "module"` to `package.json`
2. **Keep** `eslint.config.mjs` format - do not convert
3. **Maintain strict separation**:
- `graphql/` is shared
- `api/` is client-only (generated)
- `worker/` is server-only
- `database/schema.ts` is shared between client and worker
4. **Always run** `yarn codegen` after GraphQL changes
5. **Never manually edit** generated files
6. **Use path aliases exclusively** - no relative imports
7. **Worker entry** is `worker/index.ts` - do not change `wrangler.toml` main
```bash
yarn dev # Start worker (8787) + Expo dev server
yarn start # Expo dev server only
yarn worker:dev # Worker only
yarn codegen # Generate types & hooks
yarn codegen:watch # Watch mode
yarn db:generate # Generate migration
yarn db:migrate # Apply migrations
yarn db:studio # Drizzle Studio
yarn android # Run on Android
yarn build:debug # EAS debug build
yarn build:release # EAS release build
yarn lint
yarn lint:fix
yarn clean
```
Client endpoint resolution (priority order):
1. `EXPO_PUBLIC_GRAPHQL_URL_DEV` or `EXPO_PUBLIC_GRAPHQL_URL` (build-time)
2. `GRAPHQL_URL_DEV` or `GRAPHQL_URL` (runtime)
3. Dev fallback: `http://192.168.1.51:8787/graphql`
4. Production default: `https://safarnak.app/graphql`
Configured in `api/client.ts` and `app.config.js`.
**Follow this workflow**:
1. Understand existing patterns and directory ownership
2. Use path aliases exclusively
3. Update GraphQL schema (`graphql/schema.graphql`) and operations (`graphql/queries/*.graphql`) first
4. Run `yarn codegen` to generate types and hooks
5. Implement worker resolvers in `worker/*` only
6. Use `@database/server` for worker resolvers, `@database/client` for client components
7. Never touch generated files (`api/hooks.ts`, `api/types.ts`)
8. Test thoroughly with `yarn dev`
```bash
query GetMyData($id: ID!) {
myData(id: $id) {
id
name
}
}
yarn codegen
export const myDataResolver = async (_, { id }, { db }) => {
return await db.query.myTable.findFirst({ where: eq(myTable.id, id) });
};
import { useGetMyDataQuery } from '@api';
const { data, loading } = useGetMyDataQuery({ variables: { id: '123' } });
```
```bash
export const newTable = sqliteTable('new_table', {
id: text('id').primaryKey().$defaultFn(() => createId()),
name: text('name').notNull(),
});
yarn db:generate
yarn db:migrate
import { getServerDB } from '@database/server';
const db = getServerDB(env.DB);
await db.insert(newTable).values({ name: 'example' });
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/safarnak-app-development/raw