NeevJS Documentation

Plugin-driven, offline-first React framework for real-world business applications.
Version 1.0.1-beta  ·  Built by Rahul Raj Kushwaha

Introduction

NeevJS ("नींव" = Foundation) is a React framework designed specifically for business applications — CRMs, admin dashboards, POS systems, booking platforms, and internal tools.

Instead of fetch, axios, and separate state management, NeevJS gives you a single abstraction: useModel(). It works like Laravel Eloquent, but in React.

💡 NeevJS is NOT a Next.js replacement. It's a framework for CRUD-heavy business apps — not landing pages, not blogs.

NeevJS CLI

The fastest way to start a new NeevJS project is with the interactive CLI.

npx @neevjs/cli init my-app

The CLI will guide you through selecting a Project Mode and setting up your environment.

Installation

Client (React)

npm install @neevjs/client react

Server (Optional Node backend)

npm install @neevjs/server

Quick Start

// 1. Create a client
import { createClient, NeevProvider, useModel, AuthPlugin } from '@neevjs/client'

const client = createClient({ baseURL: 'https://api.yourapp.com/api' })
client.use(AuthPlugin)

// 2. Wrap your app
function App() {
  return (
    <NeevProvider client={client}>
      <Dashboard />
    </NeevProvider>
  )
}

// 3. Use data anywhere
function Dashboard() {
  const { data, loading, create, remove } = useModel('orders')

  if (loading) return <p>Loading...</p>

  return (
    <div>
      {data.map(order => (
        <div key={order.id}>
          {order.name}
          <button onClick={() => remove(order.id)}>Delete</button>
        </div>
      ))}
      <button onClick={() => create({ name: 'New Order' })}>Add</button>
    </div>
  )
}

API Contract

Every backend that works with NeevJS must follow this response format:

// Success response
{
  "data": [],        // array or object
  "meta": {
    "pagination": {
      "page": 1,
      "perPage": 20,
      "total": 100,
      "lastPage": 5
    }
  },
  "error": null
}

// Error response
{
  "data": null,
  "meta": {},
  "error": "Descriptive error message"
}
This contract is non-negotiable. It's what lets NeevJS work with any backend — Node, Laravel, Django, or anything else.

createClient

The core engine. Creates a client instance that all hooks and components use.

import { createClient } from '@neevjs/client'

const client = createClient({
  baseURL: 'http://localhost:3001/api'  // optional, defaults to ''
})

// Register plugins
client.use(AuthPlugin)
client.use(LoggerPlugin)
client.use(CachePlugin)
client.use(OfflinePlugin)
Option Type Default Description
baseURL string '' Base URL prepended to all API requests

NeevProvider

Provides the client to the entire React tree via context. Wrap your root <App /> with it.

import { NeevProvider } from '@neevjs/client'

function App() {
  return (
    <NeevProvider client={client}>
      {/* all components have access to client */}
      <Router />
    </NeevProvider>
  )
}

useModel

The core hook. Replaces fetch, axios, useState, and useEffect for data management.

const {
  data,     // T[]         — current records
  loading,  // boolean     — fetching state
  error,    // Error|null  — any error
  create,   // (payload) => Promise<void>
  update,   // (id, payload) => Promise<void>
  remove,   // (id) => Promise<void>
  refresh,  // () => Promise<void>  — manual re-fetch
  mutate,   // (data) => void        — manual cache update
} = useModel<YourType>('model-name')

// With Query Parameters (isolated cache per param set)
const { data } = useModel<YourType>('users', { 
  params: { role: 'admin', page: 1 } 
})

// With React Suspense (eliminates loading checks)
const { data } = useModel<YourType>('model-name', { suspense: true })

// Hybrid Mode (override backend for this model)
const { data } = useModel<YourType>('payments', { 
  baseURL: 'https://api.payments-service.com/api' 
})

With TypeScript generics

interface Order {
  id: number
  customer: string
  amount: number
  status: 'pending' | 'completed'
}

const { data, create } = useModel<Order>('orders')

// data is typed as Order[]
// create expects Omit<Order, 'id'>

React Suspense Mode

Pass { suspense: true } to enable Suspense-based data fetching. Wrap the component in <NeevBoundary /> to handle loading and error states declaratively — no if (loading) needed.

function UsersContent() {
  // Component "suspends" until data is ready
  const { data, remove } = useModel<User>('users', { suspense: true })

  return (
    <ul>
      {data.map(user => (
        <li key={user.id}>
          {user.name}
          <button onClick={() => remove(user.id)}>Delete</button>
        </li>
      ))}
    </ul>
  )
}

// Wrap with NeevBoundary — handles loading + errors
export function UsersPage() {
  return (
    <NeevBoundary loadingFallback={<p>Loading...</p>}>
      <UsersContent />
    </NeevBoundary>
  )
}

Caching & Deduplication

Results are cached in memory by model name with a 60-second stale time. If 3 components mount simultaneously and all call useModel('users'), only one network request is made — all components share the result.

💡 Pub/Sub Reactivity: When you call create(), update(), or remove() in any component, every other component using the same model re-renders automatically with fresh data.

Offline Behaviour

When navigator.onLine is false and cached data exists, useModel skips the network request entirely and serves from cache. No errors, no failed requests in the console.

useAuth

Access the auth system anywhere in your app.

const { login, logout, user, isAuthenticated, register } = useAuth()

// Login
await login('email@example.com', 'password')

// Get current user
const currentUser = await user()  // returns AuthUser | null

// Check auth state
if (isAuthenticated()) { ... }

// Logout
logout()

// Register
await register('email@example.com', 'password', 'Full Name')

useStore & SecureStore

Client-side global state management — NeevJS's built-in answer to Redux for most use cases. Any component calling useStore('same-key') shares the same reactive state with zero configuration. Built on React 18’s useSyncExternalStore for concurrent-mode safety.

💡 NeevJS has no restrictions. You can freely use Redux, Zustand, Jotai, or any other state management library alongside NeevJS. useStore is a convenient zero-dependency option for most business apps — use what fits your team best.

Basic Usage

import { useStore } from '@neevjs/client'

// Works exactly like useState, but shared across ALL components
const [count, setCount] = useStore('counter', 0)

// Functional updates work too
setCount(prev => prev + 1)

Shopping Cart Example

interface CartItem {
  id: number
  name: string
  qty: number
  price: number
}

// In any component — they all share the same cart state
const [cart, setCart] = useStore<CartItem[]>('cart', [])

// Add item
setCart(prev => [...prev, { id: 1, name: 'Widget', qty: 1, price: 99 }])

// Remove item
setCart(prev => prev.filter(item => item.id !== 1))

// Update quantity
setCart(prev =>
  prev.map(item => item.id === 1 ? { ...item, qty: item.qty + 1 } : item)
)

With localStorage Persistence

Pass { persist: true } and the store value survives page reloads automatically. The key is saved under neev_store_<key> in localStorage.

// Theme preference — survives refresh
const [theme, setTheme] = useStore('theme', 'dark', { persist: true })

// Cart — survives refresh
const [cart, setCart] = useStore<CartItem[]>('cart', [], { persist: true })

UI State Examples

// Sidebar open/close — shared between layout and toggle button
const [sidebarOpen, setSidebarOpen] = useStore('sidebar', true)

// Toast notification queue
const [toasts, setToasts] = useStore<string[]>('toasts', [])
const addToast = (msg: string) => setToasts(prev => [...prev, msg])

// Multi-step wizard position
const [step, setStep] = useStore('wizard-step', 1)

Imperative API (Outside React)

Use getStore and setStore from service layers, plugins, or event handlers outside of React components.

import { getStore, setStore } from '@neevjs/client'

// Read the current value anywhere
const cart = getStore<CartItem[]>('cart')

// Set a value — all subscribed components re-render
setStore('toasts', ['Payment successful!'])

API Reference

Export Signature Description
useStore (key, initial, options?) => [value, setter] React hook. All components with same key share state.
getStore (key) => T | undefined Read a store value imperatively (outside React).
setStore (key, value) => void Set a store value imperatively. Notifies all subscribers.
Option Type Default Description
persist boolean false Persist value to localStorage. Survives page reloads.
session boolean false Persist to sessionStorage. Survives refresh but cleared on tab close.
ttl number undefined Time-to-live in milliseconds. Only works with persist: true.
session boolean false Persist to sessionStorage. Survives refresh but cleared on tab close.
ttl number undefined Time-to-live in milliseconds. Only works with persist: true.

useStore vs useModel — When to Use Which

Scenario Recommended
Fetching users, orders, products from your API useModel
Shopping cart, wishlist useStore (in-memory or persist)
Theme, language preference useStore({ persist: true })
Toast / notification queue useStore (in-memory)
Sidebar open, modal visibility useStore or local useState
Multi-step form draft useStore({ session: true })
OTP state, masked payment info during session SecureStore
Tokens, auth data useAuth() (handled by NeevJS internally)
Server filters / pagination useModel + URL params

The Three Storage Tiers

Choose based on the sensitivity and lifetime of your data:

Tier API Survives Refresh? XSS Safe? Disk Safe?
In-Memory (default) useStore('k', v)
Session useStore('k', v, { session: true }) ✅ (tab only)
Persist useStore('k', v, { persist: true })
Encrypted SecureStore.set('k', v)
⚠️ Security Note: When using persist: true or session: true, your data is stored in plain text in the browser storage. Avoid storing highly sensitive data like plain-text passwords or secret keys in these tiers. Use SecureStore or in-memory state for sensitive info.
await SecureStore.set('k', v) ❌ by design ✅ ✅

Session Storage Tier { session: true }

Data survives page refresh but is cleared when the tab closes and is not shared across tabs. Safer than localStorage for semi-sensitive data.

// Unsaved form draft
const [draft, setDraft] = useStore('invoice_draft', {}, { session: true })

// Current user object for the session
const [currentUser, setCurrentUser] = useStore('session_user', null, { session: true })

Persist Tier { persist: true, ttl? }

⚠️ localStorage is readable by any JS on the page. Do NOT store passwords, tokens, payment info, or PII here.
const [theme, setTheme] = useStore('theme', 'dark', { persist: true })

// Cart with 7-day expiry
const [cart, setCart] = useStore<CartItem[]>('cart', [], {
  persist: true,
  ttl: 7 * 24 * 60 * 60 * 1000,
})

SecureStore — AES-256-GCM Encrypted

For sensitive in-session data. Uses the Web Crypto API with a non-extractable session key — the raw key bytes can never be exported even by JavaScript. The ciphertext in localStorage is unreadable after a page refresh (key is gone), which is intentional.

import { SecureStore } from '@neevjs/client'

await SecureStore.set('pending_payment', { amount: 4999, currency: 'INR' })
const payment = await SecureStore.get<Payment>('pending_payment') // null after refresh
SecureStore.remove('pending_payment')
SecureStore.clear() // call on logout

Logout Cleanup

import { SecureStore, clearPersistedStore } from '@neevjs/client'

function handleLogout() {
  auth.logout()
  SecureStore.clear()           // Wipe all encrypted session data
  clearPersistedStore('cart')   // Wipe persisted cart
}

Full API Reference

Export Signature Description
useStore (key, init, opts?) → [T, setter] React hook. Shared across all components with same key.
getStore (key) → T | undefined Read imperatively outside React.
setStore (key, value) → void Set imperatively. Notifies all subscribers.
clearPersistedStore (key) → void Remove from localStorage and sessionStorage.
SecureStore.set (key, value) → Promise<void> Encrypt and store.
SecureStore.get (key) → Promise<T | null> Decrypt. Returns null if key is gone or tampered.
SecureStore.remove (key) → void Remove one encrypted entry.
SecureStore.clear () → void Wipe all encrypted entries. Use on logout.

useStore vs Redux vs Zustand

NeevJS has no restrictions. Redux, Zustand, Jotai, and Recoil all work alongside NeevJS without any conflicts. useStore is zero-dependency and covers most business app needs out of the box.
Feature useStore (NeevJS) Redux Toolkit Zustand
Zero dependencies ❌ (redux, immer, reselect)
Setup boilerplate None store + slice + Provider create() call
API style Identical to useState actions + dispatch Custom (get/set)
sessionStorage tier built-in ❌ (needs middleware)
localStorage + TTL built-in ❌ (needs redux-persist) ❌ (needs middleware)
AES-256-GCM encrypted storage ✅ SecureStore
Concurrent Mode safe ✅ useSyncExternalStore
Fine-grained selectors Planned v0.2 ✅ useSelector ✅ built-in
Time-travel DevTools Planned v0.2 ✅ Redux DevTools
Best for Business apps, no config Large teams, complex flows Medium apps

When to choose Redux or Zustand instead

Prefer an external library when you need:

Using Redux or Zustand alongside NeevJS

// ✅ Redux + NeevJS — no conflict
import { useSelector } from 'react-redux'
import { useModel } from '@neevjs/client'

function OrdersPage() {
  const { data: orders } = useModel('orders')          // server data
  const filters = useSelector(state => state.ui.filters) // UI state
  return <Table model="orders" />
}

// ✅ Zustand + NeevJS — no conflict
import { create } from 'zustand'

const useFilterStore = create(set => ({
  status: 'all',
  setStatus: (status) => set({ status }),
}))

function OrdersPage() {
  const { data: orders } = useModel('orders')
  const { status, setStatus } = useFilterStore()
  // ...
}

useSyncStatus

Monitor offline/online status and pending sync queue. Used with OfflinePlugin.

const { isOffline, pending, syncing, errors, clearErrors } = useSyncStatus()

function SyncIndicator() {
  const { isOffline, pending, syncing, errors, clearErrors } = useSyncStatus()

  if (errors.length > 0) return (
    <div style={{ color: 'red' }}>
      ⚠️ {errors.length} action(s) failed permanently.
      <button onClick={clearErrors}>Dismiss</button>
    </div>
  )
  if (syncing)   return <span>↻ Syncing...</span>
  if (isOffline) return <span>● Offline — {pending} queued</span>
  return <span>● Online</span>
}
Value Type Description
isOffline boolean true when navigator.onLine is false
pending number Number of actions in the offline queue
syncing boolean true while background sync is in progress
errors Error[] Permanently failed actions (e.g. server returned 4xx)
clearErrors () => void Clears the error list

Table

Auto-renders a table for any model. Columns are auto-detected from data or can be customized.

// Auto columns — detected from first row
<Table model="users" />

// Custom columns with render functions
<Table<User>
  model="users"
  columns={[
    { key: 'name', label: 'Full Name' },
    { key: 'email', label: 'Email' },
    {
      key: 'role',
      label: 'Role',
      render: (val) => <Badge>{val}</Badge>
    },
  ]}
  onEdit={(user) => setEditUser(user)}
  onDelete={(user) => remove(user.id)}
  emptyMessage="No records yet."
/>
Prop Type Description
model string Model name (matches useModel key)
columns TableColumn[] Optional. Auto-detected from data if omitted.
onEdit (row: T) => void Edit button click handler
onDelete (row: T) => void Delete button click handler
emptyMessage string Message shown when there is no data
classNames TableClassNames Override CSS class names for any element
styles TableStyles Override inline styles for any element
unstyled boolean Strip all default styles for full custom control

Form

Model-aware form component that handles create and update in one component.

// Create new record
<Form<User>
  model="users"
  fields={[
    { name: 'name', label: 'Full Name', required: true },
    { name: 'email', type: 'email', required: true },
    {
      name: 'role',
      type: 'select',
      options: [
        { label: 'Admin', value: 'admin' },
        { label: 'User', value: 'user' },
      ]
    },
  ]}
  onSuccess={() => console.log('Created!')}
  submitLabel="Create User"
/>

// Update existing record — pass editId + initialValues
<Form<User>
  model="users"
  editId={selectedUser.id}
  initialValues={selectedUser}
  fields={fields}
  onSuccess={() => setEditing(false)}
  submitLabel="Update User"
/>

transformPayload — Modify data before sending

<Form<User>
  model="users"
  transformPayload={(payload) => ({
    ...payload,
    name: payload.name?.trim().toUpperCase(),
    createdAt: new Date().toISOString(),
  })}
  fields={fields}
/>

onSubmitOverride — Custom submission logic

<Form<User>
  model="users"
  onSubmitOverride={async (payload) => {
    await myCustomApiCall(payload)
    await sendWelcomeEmail(payload.email)
  }}
  fields={fields}
/>

Field types

type Renders
text (default) <input type="text">
email <input type="email">
password <input type="password">
number <input type="number">
textarea <textarea>
select <select> with options

All Form Props

Prop Type Description
model string Model name for POST/PUT requests
fields FormField[] Schema-driven field definitions
editId number | string If set, sends PUT instead of POST
initialValues Partial<T> Pre-fills fields for edit mode
onSuccess () => void Called after successful submission
onError (err: Error) => void Called when submission fails
submitLabel string Text on the submit button
transformPayload (payload) => payload Modify data before sending to server
onSubmitOverride (payload) => Promise<void> Fully replace default submit logic
children ReactNode Add custom JSX fields inside the form
classNames FormClassNames Override CSS class names
styles FormStyles Override inline styles
unstyled boolean Strip all default styles
fieldErrors Record<string, string> External/server errors to display per-field
validate (values) => Record<string, string> Custom form-level validation logic

NeevBoundary

Replaces all if (loading) and if (error) checks when using useModel with { suspense: true }. It combines React Suspense with an ErrorBoundary in one component.

import { NeevBoundary } from '@neevjs/client'

<NeevBoundary
  loadingFallback={<SkeletonLoader />}
  errorFallback={(error, reset) => (
    <div>
      <p>Something went wrong: {error.message}</p>
      <button onClick={reset}>Try Again</button>
    </div>
  )}
>
  <UsersContent />
</NeevBoundary>
Prop Type Description
loadingFallback ReactNode Shown while data is loading (Suspense fallback)
errorFallback (error, reset) => ReactNode Shown on error; call reset() to retry
💡 When errorFallback is not provided, NeevBoundary renders a default "Data Error" UI with a "Try again" button.

Protected

Wraps content that requires authentication. Optionally checks user role.

// Require login only
<Protected>
  <Dashboard />
</Protected>

// Require specific role
<Protected role="admin" fallback={<p>Admins only</p>}>
  <AdminPanel />
</Protected>

AuthPlugin

Automatically injects the JWT token as an Authorization: Bearer ... header in every request.

import { AuthPlugin } from '@neevjs/client'

client.use(AuthPlugin)

// Now every request automatically includes the token.
// No manual header management needed.

LoggerPlugin

Logs all requests and responses to the browser console. Useful for development.

import { LoggerPlugin } from '@neevjs/client'

client.use(LoggerPlugin)

// Console output:
// [NeevJS] → GET /users
// [NeevJS] ← 200 http://localhost:3001/api/users
⚠️ Disable LoggerPlugin in production builds to avoid leaking API info.

CachePlugin

Caches GET requests at the network level. Automatically invalidates on mutations (POST, PUT, DELETE).

import { CachePlugin, createCachePlugin } from '@neevjs/client'

// Default (1 minute TTL)
client.use(CachePlugin)

// Custom TTL
client.use(createCachePlugin({ ttl: 30_000 }))  // 30 seconds
💡 Intelligent Invalidation: CachePlugin automatically purges cached results for a resource path when any mutation occurs on that same path. No stale lists after creates or updates.

OfflinePlugin

Queues write operations (POST, PUT, DELETE) when the device is offline, then automatically retries when connectivity is restored.

import { OfflinePlugin } from '@neevjs/client'

client.use(OfflinePlugin)

// That's it. useModel().create/update/remove
// will queue automatically when offline.

// Show status to user
const { isOffline, pending } = useSyncStatus()
{isOffline && <p>Offline — {pending} actions pending</p>}
💡 Best for POS systems, field sales apps, and warehouse tools that need to work without internet.

Custom Plugin

Plugins follow a simple interface. Any of the hooks are optional.

import type { NeevPlugin } from '@neevjs/client'

const TenantPlugin: NeevPlugin = {
  name: 'tenant',

  onRequest(req) {
    return {
      ...req,
      options: {
        ...req.options,
        headers: {
          ...req.options.headers,
          'X-Tenant-ID': 'company-123',
        },
      },
    }
  },

  onResponse(res) {
    // transform or inspect response
    return res
  },

  onError(err) {
    // log to error tracking (Sentry, etc.)
    console.error('API Error:', err)
  },
}

client.use(TenantPlugin)

Feature-Driven Architecture

NeevJS recommends organizing projects by business feature, not by technical role. This prevents "Massive Component Syndrome" — 1000-line files that are impossible to debug.

Recommended Folder Structure

src/
├── app/                        ← Global app shell
│   ├── App.tsx                 ← Layout, routing, providers
│   └── main.tsx                ← React DOM entry point
│
├── core/                       ← Framework configuration
│   └── neev.ts                 ← createClient + plugin registry
│
├── models/                     ← Shared domain types
│   ├── user.ts                 ← Used by multiple features
│   └── order.ts
│
├── shared/                     ← Generic reusable UI
│   └── components/
│       └── SyncIndicator.tsx
│
└── features/                   ← One folder per domain
    ├── users/
    │   ├── components/
    │   │   ├── UserTable.tsx   ← Atomic sub-components
    │   │   └── UserForm.tsx
    │   └── UsersPage.tsx       ← The thin orchestrator
    ├── dashboard/
    │   └── DashboardPage.tsx
    └── auth/
        └── LoginPage.tsx

The Orchestrator Pattern

Keep each "Page" file as a thin orchestrator. It manages only UI state (toggling modals, passing handlers). Actual UI lives in small, atomic sub-components.

// features/users/UsersPage.tsx  ← only ~50 lines, clean and readable
function UsersContent() {
  const { remove } = useModel<User>('users', { suspense: true })
  const [showForm, setShowForm] = useState(false)
  const [editUser, setEditUser] = useState<User | null>(null)

  return (
    <div>
      <button onClick={() => setShowForm(true)}>+ Add User</button>
      {showForm && (
        <UserForm
          editUser={editUser}
          onSuccess={() => { setShowForm(false); setEditUser(null) }}
        />
      )}
      <UserTable
        onEdit={(user) => { setEditUser(user); setShowForm(true) }}
        onDelete={(user) => remove(user.id)}
      />
    </div>
  )
}
💡 Why models/ is separate: A User type is used by the Users feature, Dashboard stats, and the Auth system. Keeping it in src/models/ instead of inside any one feature prevents duplication and circular imports.

React 19 Compiler

NeevJS is built for React 19 and ships with the React Compiler pre-configured in the demo app. This means you never need to write useMemo, useCallback, or React.memo again.

What the Compiler Does Automatically

// ❌ Before (manual memoization)
const handleDelete = useCallback((id) => {
  remove(id)
}, [remove])

const expensiveList = useMemo(() => {
  return data.filter(u => u.role === 'admin')
}, [data])

// ✅ After (with React Compiler — just write normal code)
const handleDelete = (id) => remove(id)
const expensiveList = data.filter(u => u.role === 'admin')

Enabling in Your Vite Project

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: ['babel-plugin-react-compiler'],
      },
    }),
  ],
})
⚠️ React Compiler requires React 19 or above. Ensure your project uses "react": "^19.0.0" in package.json.

@neevjs/server

An optional Node.js + Express backend that follows the NeevJS API contract out of the box. It includes auth routes, a generic CRUD controller, and an in-memory DB (swap for PostgreSQL in production).

# Install dependencies
cd packages/server
npm install

# Start dev server
npm run dev
# → Running at http://localhost:3001

# Available routes:
GET    /api/health
POST   /api/auth/login
POST   /api/auth/register
GET    /api/auth/me          (protected)
POST   /api/auth/logout      (protected)
GET    /api/users
POST   /api/users
GET    /api/users/:id
PUT    /api/users/:id
DELETE /api/users/:id

Add a new model

In server.ts, add one line:

// Unprotected
app.use('/api/orders', createModelRouter('orders'))

// Protected (requires auth token)
app.use('/api/invoices', createModelRouter('invoices', true))

Seed data

In src/db/index.ts:

db.seed('orders', [
  { id: 1, customer: 'Rahul', amount: 5000, status: 'pending' },
])

Project Modes

NeevJS is designed to adapt to your architecture. Choose the mode that fits your team.

1. Fullstack Mode (Default)

The best developer experience. Uses @neevjs/server (Node.js/Express) as the backend. Everything is pre-configured with shared types and unified validation.

2. API Mode

Use NeevJS as a powerful client for your existing backend. Whether you use Laravel, Django, Rails, or a custom Go API, NeevJS works as long as you follow the API Contract.

const client = createClient({ 
  baseURL: 'https://api.your-company.com/v1' 
})

3. Hybrid Mode

The most flexible approach for microservices or legacy migration. Most models use your primary backend, but specific models connect directly to external or legacy services.

// core/neev.ts
const client = createClient({ baseURL: '/api' }) // Primary

// features/payments/PaymentsPage.tsx
const { data } = useModel('payments', { 
  baseURL: 'https://api.external-processor.com/v1' // External
})

Contributing

Contributions are highly welcome! To contribute to NeevJS, follow these steps:

  1. Fork the repository on GitHub.
  2. Create your feature branch: git checkout -b feature/MyFeature
  3. Commit your changes: git commit -m 'feat: add MyFeature'
  4. Push to the branch: git push origin feature/MyFeature
  5. Open a Pull Request.

Please ensure your code adheres to the existing style and all tests pass before submitting your PR.