Markdown Converter
Agent skill for markdown-converter
Project Overview
Sign in to like and favorite skills
Safarnak App – Cursor AI Repository Rules
Project Overview
graphql/ used by both client and worker.database/schema.ts) shared between client and worker - unified schema with UUID IDs, separate adapters for server (D1) and client (expo-sqlite).database/ folder contains unified schema with separate adapters - same table definitions, different database backends.Current Stack (as of v1.13.0)
graphql-workers-subscriptions ^0.1.6 with Durable ObjectsRepository Structure
worker/ # Cloudflare Worker resolvers and entry graphql/ # Shared GraphQL schema + operations api/ # Client GraphQL types & hooks (generated, with automatic Drizzle sync) database/ # Shared database schema and adapters ├── schema.ts # Unified schema with UUIDs (server + client tables in one file) ├── server.ts # Server adapter (Cloudflare D1) - exports getServerDB() ├── client.ts # Client adapter (Expo SQLite) - exports getLocalDB(), sync utilities ├── index.ts # Main exports (re-exports from schema, server, client) ├── types.ts # Database types └── utils.ts # UUID utilities (createId, isValidId) migrations/ # Server-only migrations (Cloudflare D1, at project root) store/ # Redux Toolkit store, slices, middleware app/ # Expo Router pages components/ # UI components & contexts constants/, hooks/, locales/ ...
Worker
worker/index.ts (defined in wrangler.toml main).readGraphQLSchema from graphql/schema-loader.ts.worker/queries, worker/mutations, worker/subscriptions.SubscriptionPool.GraphQL & Codegen
graphql/schema.graphql.graphql/queries/*.graphql.yarn codegen → api/types.ts, api/hooks.ts.api/types.ts or api/hooks.ts manually.Path Aliases (TypeScript & Metro)
"@/*" → "./*" "@components/*" → "./components/*" "@graphql/*" → "./graphql/*" "@database/*" → "./database/*" # All database schemas & client utilities "@worker/*" → "./worker/*" "@api" → "./api" "@api/*" → "./api/*" "@store" → "./store" "@store/*" → "./store/*" "@hooks" → "./hooks" "@hooks/*" → "./hooks/*" "@constants" → "./constants" "@constants/*" → "./constants/*"
Updated aliases (tsconfig + Metro):
"@/*" → "./*" # e.g., import from '@/store/...' "@ui/*" → "./ui/*" "@graphql/*" → "./graphql/*" "@database/*" → "./database/*" "@worker/*" → "./worker/*" "@api" → "./api" "@api/*" → "./api/*" "@state" → "./ui/state" # UI local state utilities "@state/*" → "./ui/state/*" "@hooks" → "./ui/hooks" "@hooks/*" → "./ui/hooks/*" "@constants" → "./constants" "@constants/*" → "./constants/*" "@locales" → "./locales" "@locales/*" → "./locales/*" "@assets" → "./assets" "@assets/*" → "./assets/*"
GraphQL Endpoint Configuration (Client)
expo.extra.graphqlUrl via app.config.js (reads from env vars).EXPO_PUBLIC_GRAPHQL_URL_DEV or EXPO_PUBLIC_GRAPHQL_URL (Expo inlines these at build time)GRAPHQL_URL_DEV or GRAPHQL_URL (runtime env vars)http://192.168.1.51:8787/graphql (or auto-derived from Metro host)https://safarnak.app/graphqlapi/client.ts (URI resolution), app.config.js (configures expo.extra.graphqlUrl)Critical Rules
"type": "module" to package.json.eslint.config.mjs; do not convert ESLint config type.graphql/ is shared between client and worker.api/ is client-only, generated hooks with automatic Drizzle sync.worker/ is server-only code.database/schema.ts is SHARED between client and worker - single source of truth:
schema.ts - Unified schema defining both server tables (users, trips, etc.) and client cached tables (cachedUsers, cachedTrips, etc.) with UUID IDsserver.ts - Server adapter: getServerDB(d1) for Cloudflare D1 (worker resolvers use this)client.ts - Client adapter: getLocalDB() for Expo SQLite (client components use this)schema.ts file, ensuring schema consistencyindex.ts - Exports all schemas and utilitiesyarn codegen after changing GraphQL schema or operations.api/types.ts, api/hooks.ts).className over inline styles.yarn db:migrate to apply migrations.worker/index.ts; do not change wrangler.toml main without reason.Auth & Client Patterns
userId + username + timestamp, stored in Redux + AsyncStorage.Authorization: Bearer <token>.app/(auth), auto-redirects based on Redux isAuthenticated.Subscriptions
graphql-workers-subscriptions with Durable Object SubscriptionPool./graphql with WS subscriptions in dev.Commands Development
yarn dev # Start worker (8787) + Expo dev serveryarn start # Expo dev server onlyyarn worker:dev # Worker onlyGraphQL
yarn codegen # Generate types & hooksyarn codegen:watch # Watch modeDatabase
yarn db:generate # Generate migration from schemayarn db:migrate # Apply migrations to local D1yarn db:studio # Drizzle StudioBuild
yarn android # Run on Androidyarn android:newarch # New Architectureyarn build:debug # EAS debug build (Android)yarn build:release # EAS release build (Android)yarn build:local # Local Gradle releaseQuality & Utilities
yarn lint / yarn lint:fixyarn cleanVersioning & Releases (CI-driven)
package.json version.feat: → minorfix: → patchpackage.json; versionCode derived or overridden by ANDROID_VERSION_CODE.Debugging Tips
yarn clean then restart.wrangler logs, http://localhost:8787/graphql.yarn codegen..wrangler/state/v3/d1/, re-run yarn db:migrate.Security
Performance
React.memo, useCallback, useMemo prudently.When Making Changes
graphql/schema.graphql and graphql/queries/*.graphql first, then yarn codegen.worker/* only.api/hooks.ts, api/types.ts).@database/server for worker resolvers, @database/client for client components.You are working on Safarnak, a full-stack offline-first travel application. This is a unified single-root monorepo where both client and server code coexist in the same directory structure.
worker/index.ts - entry point defined in wrangler.toml)worker/ folder (server-side only)graphql/) shared between client & workerdatabase/schema.ts) shared between client and worker - same table definitions with UUID IDs, separate adapters for server (D1) and client (expo-sqlite)safarnak.app/ # Single root directory ├── worker/ # GraphQL resolvers (SERVER-SIDE ONLY) │ ├── index.ts # Cloudflare Worker entry point (main export, witnesses resolvers + GraphQL Yoga) │ ├── types.ts # Server-specific types │ ├── queries/ # Query resolvers: getMessages, me, getTrips, getTrip │ ├── mutations/ # Mutation resolvers: register, login, addMessage, createTrip, updateTrip │ ├── subscriptions/ # Subscription resolvers: newMessages │ └── utilities/ # Password hashing (PBKDF2), token generation ├── graphql/ # SHARED between client & worker │ ├── schema.graphql # Pure GraphQL schema definition │ ├── queries/ # Query definitions (.graphql files) │ │ ├── addMessage.graphql │ │ ├── createTrip.graphql │ │ ├── getMessages.graphql │ │ ├── getTrip.graphql │ │ ├── getTrips.graphql │ │ ├── login.graphql │ │ ├── me.graphql │ │ └── register.graphql │ ├── generated/ │ │ └── schema.d.ts # Worker schema declarations │ ├── schema-loader.ts # Worker schema loader │ └── index.ts # Shared exports only ├── api/ # Client API layer (CLIENT-SIDE ONLY) │ ├── client.ts # Apollo Client setup with auth link and DrizzleCacheStorage │ ├── cache-storage.ts # DrizzleCacheStorage - automatic Apollo → Drizzle sync on every cache write │ ├── hooks.ts # 🤖 Auto-generated React Apollo hooks (queries, mutations, subscriptions) │ ├── types.ts # 🤖 Auto-generated GraphQL types │ ├── utils.ts # Client utility functions (storage, error handling, network checks, logout, API types) │ └── index.ts # Main API exports (re-exports hooks + all utilities) ├── database/ # Shared database schema and adapters │ ├── schema.ts # Unified schema with UUIDs (server + client tables in one file) │ ├── server.ts # Server adapter (Cloudflare D1) - exports getServerDB() │ ├── client.ts # Client adapter (Expo SQLite) - exports getLocalDB(), sync utilities │ ├── index.ts # Main exports (re-exports from schema, server, client) │ ├── types.ts # Database types │ └── utils.ts # UUID utilities (createId, isValidId) ├── migrations/ # Server-only migrations (Cloudflare D1, at project root) ├── store/ # Redux state management (CLIENT) │ ├── index.ts # Store configuration with persist │ ├── hooks.ts # Typed useAppDispatch, useAppSelector │ ├── slices/ # Redux slices │ │ ├── authSlice.ts # Auth state (user, token, isAuthenticated) │ │ └── themeSlice.ts # Theme state │ └── middleware/ # Redux middleware │ └── offlineMiddleware.ts # Offline mutation queue ├── app/ # Expo Router pages (CLIENT) │ ├── _layout.tsx # Root layout with providers │ ├── (auth)/ # Authentication route group (public routes) │ │ ├── _layout.tsx # Auth routing layout │ │ ├── welcome.tsx # Welcome/onboarding page │ │ ├── login.tsx # Login page (with auto-redirect for logged-in users) │ │ └── register.tsx # Registration page (with auto-redirect for logged-in users) │ └── (app)/ # Main app route group (protected routes with tabs) │ ├── _layout.tsx # Tab bar layout (feed, explore, trips, profile) │ ├── (feed)/ # Feed tab - social posts │ ├── (explore)/ # Explore tab - search & discovery │ ├── (trips)/ # Trips tab - trip management │ └── (profile)/ # Profile tab - user account ├── components/ # React components (CLIENT) │ ├── AuthWrapper.tsx # Authentication guard component │ ├── MapView.tsx # Interactive MapLibre GL map (native) │ ├── context/ # React contexts │ │ ├── LanguageContext.tsx # i18n language switching │ │ ├── LanguageSwitcher.tsx # Language selector UI │ │ └── ThemeContext.tsx # Dark/light theme │ └── ui/ # Themed UI components │ ├── Themed.tsx # Theme-aware View/Text │ ├── ThemeToggle.tsx # Dark mode toggle │ ├── CustomText.tsx # i18n-aware text component │ └── ... # Other UI components ├── constants/ # App constants (CLIENT) │ ├── app.ts # App configuration │ ├── Colors.ts # Theme colors │ └── index.ts # Constants exports ├── hooks/ # Custom React hooks (CLIENT) ├── locales/ # i18n translations │ ├── en/translation.json # English │ └── fa/translation.json # Persian (Farsi) ├── assets/ # Static assets (images, fonts) ├── metro.config.js # Metro bundler config with NativeWind ├── babel.config.js # Babel config with NativeWind preset ├── tailwind.config.js # Tailwind CSS configuration ├── global.css # Tailwind CSS directives (@tailwind base/components/utilities) ├── wrangler.toml # Cloudflare Workers config (worker entry: worker/index.ts) ├── drizzle.config.ts # Drizzle ORM config ├── codegen.yml # GraphQL Codegen configuration ├── eslint.config.mjs # ESLint flat config (ES module) ├── app.config.js # Expo configuration ├── tsconfig.json # TypeScript config with enhanced checking └── package.json # Single package.json (no workspaces)
"@/*" → "./*" // Root files (use '@/api' not '../api') "@components/*" → "./components/*" // UI components "@graphql/*" → "./graphql/*" // Shared GraphQL definitions "@database/*" → "./database/*" // Shared database schema & adapters (schema.ts, server.ts, client.ts) "@worker/*" → "./worker/*" // Worker resolvers "@api" → "./api" // API layer (client hooks) "@api/*" → "./api/*" // API subfolder imports "@store" → "./store" // Redux store "@store/*" → "./store/*" // Store subfolder imports "@hooks" → "./hooks" // Custom React hooks "@hooks/*" → "./hooks/*" // Hooks subfolder imports "@constants" → "./constants" // App constants "@constants/*" → "./constants/*" // Constants subfolder imports "@locales" → "./locales" // i18n translations "@locales/*" → "./locales/*" // Locales subfolder imports
Shared (graphql/):
schema.graphql - Pure GraphQL schema definition (shared)queries/*.graphql - Query/mutation definitions (shared)schema-loader.ts - Worker schema loadergenerated/schema.d.ts - Worker schema declarationsClient-Side (api/):
client.ts - Apollo Client setup with auth link and DrizzleCacheStorage persistencecache-storage.ts - DrizzleCacheStorage implements Apollo's PersistentStorage interface, automatically syncs to structured tables on every cache writehooks.ts - Auto-generated React Apollo hooks (queries, mutations, subscriptions)types.ts - Auto-generated GraphQL typesutils.ts - Utility functions (storage, error handling, network checks, logout)index.ts - Main exports (re-exports hooks + all utilities)Server-Side (worker/):
queries/ - Query resolver functions (getMessages, me, getTrips, getTrip)mutations/ - Mutation resolver functions (register, login, addMessage, createTrip, updateTrip)subscriptions/ - Subscription resolvers (newMessages)utilities/ - Password hashing, token generationWorker (worker/index.ts):
import { readGraphQLSchema } from '@graphql/schema-loader'; import { Query } from './queries'; import { Mutation } from './mutations'; import { Subscription } from './subscriptions'; // GraphQL Yoga server using shared schema + resolvers
Codegen Workflow (dev-time):
graphql/schema.graphqlgraphql/queries/*.graphqlyarn codegen to generate api/types.ts and api/hooks.ts@api in client components (never import raw .graphql files)Shared Drizzle Schema (database/):
database/schema.ts - Unified schema defining both server tables and client cached tables with UUID IDsusers, trips, tours, messages, subscriptions, etc. (used by worker via database/server.ts)cachedUsers, cachedTrips, cachedTours, etc. (used by client via database/client.ts)userFields, tripFields, etc.)Server Usage (Worker):
database/server.ts → getServerDB(d1) for Cloudflare D1 databasedatabase/schema.ts (e.g., users, trips, tours)ID! typepasswordHash (users), aiGenerated (trips), etc.Client Usage:
database/client.ts → getLocalDB() for Expo SQLite databasedatabase/schema.ts (e.g., cachedUsers, cachedTrips)DrizzleCacheStorage - syncs on every cache write (no manual sync needed)Worker Resolvers:
import { getServerDB, users, trips } from '@database/server'; import { eq } from 'drizzle-orm'; const db = getServerDB(context.env.DB); const user = await db.select().from(users).where(eq(users.id, userId)).get(); // userId is a UUID string - no conversions needed!
Client Local Database:
import { getLocalDB, cachedTrips } from '@database/client'; import { eq } from 'drizzle-orm'; const db = await getLocalDB(); const trips = await db.select().from(cachedTrips).where(eq(cachedTrips.userId, userId)).all(); // Works offline - queries local SQLite database
Migrations:
yarn db:generate (for server database from database/schema.ts)yarn db:migrate (for server D1 database)database/client.ts)drizzle.config.ts points to schema.ts (exports serverSchema as schema for migrations)Password Hashing (worker/utils.ts):
Token Generation:
userId + username + timestampAuth Flow:
register or login mutationauthLinkAuth Pages:
app/(auth)/welcome.tsx - Welcome/onboarding pageapp/(auth)/login.tsx - Dedicated login page with auto-redirect if already logged inapp/(auth)/register.tsx - Dedicated registration page with auto-redirect if already logged inisAuthenticated from Redux and redirect to /(app) if user is logged in/(auth)/loginRouting & Navigation:
(auth) (see app/_layout.tsx unstable_settings.initialRouteName)app/(auth)/_layout.tsx defines welcome, login, and register(app) route group with 4 tabs: (feed), (explore), (trips), (profile)(auth) → /auth, (app) → /)Client-Side Storage:
apollo_cache_entries table (Drizzle SQLite)safarnak_local.dbAutomatic Sync:
api/cache-storage.ts): Implements Apollo's PersistentStorage interfacesetItem())Sync Mechanism:
DrizzleCacheStorage.setItem()apollo_cache_entries (raw cache) + structured tables (cachedUsers, cachedTrips, etc.)Offline Capabilities:
Reconnect & Queue Behavior:
Network Detection:
NativeWind Configuration:
tailwind.config.js - Tailwind configuration with NativeWind presetglobal.css - Tailwind directives (imported in app/_layout.tsx)babel.config.js - NativeWind Babel presetmetro.config.js - NativeWind Metro integration with withNativeWind()Using Tailwind Classes:
import { View, Text } from 'react-native'; export default function MyComponent() { return ( <View className="flex-1 bg-white dark:bg-black p-4"> <Text className="text-lg text-gray-800 dark:text-gray-200 font-bold"> Hello World </Text> </View> ); }
Key Features:
flex-1, p-4, bg-white)dark: prefix (e.g., dark:bg-black)Theme Integration:
Appearance.setColorScheme() changesBest Practices:
Themed.tsx) for complex themingclassName prop on React Native components (View, Text, TouchableOpacity, etc.)Functional Components:
interface Props { title: string; onPress: () => void; className?: string; // Optional Tailwind classes } export default function MyComponent({ title, onPress, className }: Props) { // Use hooks const { t } = useTranslation(); // Use callbacks for performance const handlePress = useCallback(() => { onPress(); }, [onPress]); return ( <View className={`p-4 ${className || ''}`}> <Text className="text-base font-semibold">{title}</Text> </View> ); }
Context Usage:
// Provide at root (_layout.tsx) <LanguageProvider> <ThemeProvider> <Stack /> </ThemeProvider> </LanguageProvider> // Consume anywhere const { currentLanguage, changeLanguage } = useLanguage();
Always type:
Development-Friendly Approach:
any type is allowed for development flexibility@ts-ignore and @ts-expect-error are allowed when neededUse:
@ts-expect-error with explanatory commentsNEVER use relative imports - Always use path aliases:
@api instead of ../../api or ../api@store/hooks instead of ../../store/hooks@hooks/useColorScheme instead of ../../hooks/useColorScheme@constants/Colors instead of ../../constants/Colors@components/ui/Themed instead of ../../../components/ui/ThemedThis ensures consistent, maintainable imports across the entire codebase.
package.jsonapp.config.js (no hardcoded string)master/main based on the latest commit message:
feat: or feature: → minor bump (e.g., 0.5.0 → 0.6.0)fix: or bugfix: → patch bump (e.g., 0.5.0 → 0.5.1)ci:, docs:, chore:, refactor:…) → build only, no version changepackage.json versionANDROID_VERSION_CODEfeat: (minor) or fix: (patch)Always use path aliases instead of relative imports!
Good:
import { View } from '@components/ui/Themed'; import { useLoginMutation, useMeQuery, useAddMessageMutation } from '@api'; import { useAppDispatch, useAppSelector } from '@store/hooks'; import { login } from '@store/slices/authSlice'; import { useColorScheme } from '@hooks/useColorScheme'; import { colors } from '@constants/Colors'; import { client } from '@api'; import { persistor, store } from '@store';
Better (matches current aliases):
import { useLoginMutation, useMeQuery, useAddMessageMutation } from '@api'; import { useColorScheme } from '@hooks/useColorScheme'; import { colors } from '@constants/Colors'; import { client } from '@api'; import { useAppDispatch } from '@/store/hooks'; import { login } from '@/store/slices/authSlice';
Bad:
import { View } from '../../../components/ui/Themed'; // Use @components import { useLoginMutation } from '../../api'; // Use @api import { useAppDispatch } from '../../store/hooks'; // Use @store/hooks import { login } from '../../store/slices/authSlice'; // Use @store/slices/authSlice import { useColorScheme } from '../../hooks/useColorScheme';// Use @hooks import { client } from '../api'; // Use @api
GraphQL Resolvers:
export const register = async (_, { username, password }, context) => { // Validate input if (!username || !password) { throw new Error('Username and password are required'); } // Check existing const existing = await db.select()...; if (existing) { throw new Error('User already exists'); } // Perform operation // ... };
Client Components:
const [mutate, { loading, error }] = useMutation(REGISTER_MUTATION); if (error) return <ErrorDisplay error={error} />; if (loading) return <LoadingSpinner />;
# Development yarn dev # Start both worker and client yarn start # Expo dev server only yarn worker:dev # Worker only # GraphQL Codegen yarn codegen # Generate types and hooks from GraphQL schema yarn codegen:watch # Watch mode for development # Database yarn db:generate # Generate migration from schema yarn db:migrate # Apply migrations to local D1 yarn db:studio # Open Drizzle Studio # Code Quality yarn lint # Run ESLint with developer-friendly rules yarn lint:fix # Fix linting issues automatically yarn format # Format with Prettier (optional) # Build yarn android # Run on Android yarn ios # Run on iOS yarn build:release # EAS build for production # Utilities yarn clean # Clear all caches
PascalCase.tsx (e.g., AuthWrapper.tsx)camelCase.ts starting with 'use' (e.g., useAuth.ts)camelCase.ts (e.g., formatDate.ts)SCREAMING_SNAKE_CASE inside filesinterface UserProps)Using translations:
import { useTranslation } from 'react-i18next'; const { t } = useTranslation(); <Text>{t('common.welcome')}</Text>
Translation files:
locales/en/translation.jsonlocales/fa/translation.jsonRTL Support:
supportsRtl=false)metro.config.js - Metro bundler (CommonJS)
withNativeWind() wrapperglobal.css for Tailwindeslint.config.mjs - ESLint flat config (ES Module)
wrangler.toml - Cloudflare Workers
worker/index.ts (defined in main field)babel.config.js - Babel configuration
tailwind.config.js - Tailwind CSS configuration
app/**/*.{js,jsx,ts,tsx}, components/**/*.{js,jsx,ts,tsx}global.css - Tailwind directives
@tailwind base - Base styles@tailwind components - Component classes@tailwind utilities - Utility classesapp/_layout.tsx for global availabilitydrizzle.config.ts - Database (Worker)
database/schema.ts (exports serverSchema as schema for migrations)migrations/ (at project root, for D1 database)database/ - Shared Drizzle Schema
schema.ts - Unified schema with UUIDs (server + client tables in one file)server.ts - Server adapter: getServerDB(d1) for Cloudflare D1client.ts - Client adapter: getLocalDB() for Expo SQLite, sync utilities, statsindex.ts - Main exports (re-exports from schema, server, client)utils.ts - UUID utilities (createId, isValidId) - runtime-optimized for each platform"type": "module" to package.json - causes metro.config.js to faildatabase/schema.ts is SHARED between client and worker (unified schema with separate adapters)yarn codegen after modifying schema or operationsclassName prop, not inline stylesyarn clean fixes most issuesany type and @ts-expect-error when needed for developmentapi/hooks.ts or api/types.tsworker/index.ts (defined in wrangler.toml main field)Metro bundler issues:
yarn clean.expo and node_modules/.cacheyarn start --clearWorker issues:
.wrangler/state/v3/d1/ for databaseyarn db:migrate to reset databaseType errors:
graphql/schema.graphqlnpx tsc --noEmit to see all errorsyarn codegen to regenerate typesGraphQL errors:
http://localhost:8787/graphqlyarn codegen after schema changesAPI/Client errors:
api/hooks.ts and api/types.ts are up to dateyarn codegen to regenerate client codeworker/utils.ts)React.memo for expensive componentsuseCallback for functions passed as propsuseMemo for expensive calculations@/, @components/, @graphql/)yarn codegen after GraphQL changesgraphql/schema.graphql.graphql files to graphql/queries/yarn codegen (auto-generates hooks in api/hooks.ts)worker/mutations/ or worker/queries/api/ (e.g., import { useLoginMutation } from 'api')For Server (Worker):
database/schema.ts (e.g., users, trips, tours)
text('id').primaryKey().$defaultFn(() => createId())serverSchema exportyarn db:generate (from database/schema.ts)yarn db:migrate (for D1 database)graphql/schema.graphql if exposing data to clientyarn codegen to create client hooksFor Client Cached Tables (if needed):
database/schema.ts (e.g., cachedUsers, cachedTrips)
cachedAt, lastSyncAt, pending, deletedAtclientSchema exportdatabase/client.ts)DrizzleCacheStorage (no manual sync needed)Key Points:
database/schema.ts filegetServerDB(d1) from @database/servergetLocalDB() from @database/clientcomponents/ui/ or components/locales/en/ and locales/fa/Remember: This is a unified monorepo with perfect separation. GraphQL folder is shared, API folder is client-only, Worker folder is server-only,
database/schema.ts is SHARED between client and worker (unified schema with separate adapters). Always run yarn codegen after GraphQL changes and never manually edit auto-generated files. Use @database/server for worker resolvers and @database/client for client components.