Core Concepts

Comparisons with Other Libraries

If you're coming from React Query, Prisma, or other data management libraries, this guide helps you understand how @wayvo-ai/core compares and what makes it different.

DataSource + Store vs React Query

Conceptual Mapping

React Query@wayvo-ai/coreNotes
useQueryuseStore + useDBRowsStore handles querying automatically
useMutationstore.save()Save handles both insert and update
queryClient.invalidateQueriesAuto-refresh after saveNo manual invalidation needed
queryClient.setQueryDatastore.setValue() / store.updateRow()Direct state updates
isLoadinguseIsStoreLoading()Reactive loading state
isErroruseStoreError()Error state management
refetchstore.executeQuery()Manual refresh when needed

Key Differences

1. Automatic Cache Management

React Query:

// Manual cache invalidation
const mutation = useMutation({
  mutationFn: updateUser,
  onSuccess: () => {
    queryClient.invalidateQueries(['users']);
  },
});

@wayvo-ai/core:

// Automatic refresh after save
await store.save({ feedback: 'User updated' });
// Store automatically refreshes - no manual invalidation needed

2. Type Safety

React Query:

// Manual type definitions
const { data } = useQuery<User[]>({
  queryKey: ['users'],
  queryFn: () => fetchUsers(),
});

@wayvo-ai/core:

// Types inferred from DataSource
const store = useStore<User>({
  datasourceId: 'Users',
  // TypeScript knows User type from DataSource
});
const rows = useDBRows(store); // rows: ReadonlyArray<DBRow<User>>

3. CRUD Operations

React Query:

// Separate mutations for each operation
const createMutation = useMutation({ mutationFn: createUser });
const updateMutation = useMutation({ mutationFn: updateUser });
const deleteMutation = useMutation({ mutationFn: deleteUser });

// Manual optimistic updates
queryClient.setQueryData(['users'], (old) => [...old, newUser]);

@wayvo-ai/core:

// Single save method handles insert/update
store.createNew({ partialRecord: { name: 'New User' } });
await store.save(); // Automatically detects insert vs update

// Delete is built-in
store.deleteRow(rowId);
await store.save();

4. Form State Management

React Query:

// Manual form state + mutation
const [formData, setFormData] = useState({});
const mutation = useMutation({ mutationFn: saveUser });

const handleSubmit = () => {
  mutation.mutate(formData);
};

@wayvo-ai/core:

// Store tracks form state automatically
const row = useCurrentRowSync(store);
const isDirty = useIsStoreDirty(store);

// Direct updates
store.setValue('name', 'New Name');

// Save with dirty tracking
await store.save();

When to Use What

Use React Query when:

  • ✅ You need fine-grained control over caching strategies
  • ✅ You're building a public API or external-facing app
  • ✅ You need to work with multiple external APIs
  • ✅ You want to use React Query's ecosystem (devtools, etc.)

Use @wayvo-ai/core when:

  • ✅ You're building an internal/admin application
  • ✅ You want automatic CRUD operations
  • ✅ You need built-in form state management
  • ✅ You want type-safe database operations
  • ✅ You need role-based access control built-in

DataSource vs Prisma

Conceptual Mapping

Prisma@wayvo-ai/coreNotes
schema.prismaDataSource definitionTypeScript-based schema
prisma.user.findMany()useStore + queryReactive queries via Store
prisma.user.create()store.createNew() + store.save()Two-step process
prisma.user.update()store.setValue() + store.save()Direct updates
MiddlewareLifecycle hooksbeforeInsert, afterUpdate, etc.
@relationJoins arrayExplicit join definitions

Key Differences

1. Schema Definition

Prisma:

// schema.prisma
model User {
  id        String   @id @default(uuid())
  email     String   @unique
  name      String?
  posts     Post[]
  createdAt DateTime @default(now())
}

@wayvo-ai/core:

// TypeScript-based schema
export const UserDataSource: DataSource<User> = {
  ...DefaultDataSource,
  id: 'User',
  tableName: 'users',
  attributes: [
    {
      ...DefaultAttribute,
      code: 'id',
      type: 'Text',
      column: 'id',
    },
    {
      ...DefaultAttribute,
      code: 'email',
      type: 'Text',
      column: 'email',
    },
    // ...
  ],
};

2. Querying

Prisma:

// Server-side only
const users = await prisma.user.findMany({
  where: { isActive: true },
  include: { posts: true },
});

@wayvo-ai/core:

// Client-side reactive queries
const store = useStore<User>({
  datasourceId: 'Users',
  autoQuery: true,
  query: {
    filters: [{ isActive: { is: true } }],
  },
});
const rows = useDBRows(store); // Reactive, updates automatically

3. Mutations

Prisma:

// Server-side only
await prisma.user.create({
  data: { email: 'user@example.com', name: 'John' },
});

await prisma.user.update({
  where: { id: userId },
  data: { name: 'Jane' },
});

@wayvo-ai/core:

// Client-side mutations with automatic sync
store.createNew({
  partialRecord: { email: 'user@example.com', name: 'John' },
});
await store.save();

// Or update existing
store.setValue('name', 'Jane', rowId);
await store.save();

4. Access Control

Prisma:

// Manual access control in application code
if (!session.user.roles.includes('admin')) {
  throw new Error('Unauthorized');
}
const users = await prisma.user.findMany();

@wayvo-ai/core:

// Built-in role-based access control
access: [
  {
    ...DefaultFullAccess,
    roleCode: 'admin',
  },
  {
    ...DefaultReadOnlyAccess,
    roleCode: 'viewer',
  },
],
// Access control enforced automatically

5. Lifecycle Hooks

Prisma:

// Middleware (limited)
prisma.$use(async (params, next) => {
  if (params.action === 'create') {
    // Pre-processing
  }
  return next(params);
});

@wayvo-ai/core:

// Rich lifecycle hooks
beforeInsert: async ({ rows, session, client }) => {
  // Validation, transformation
  return { rows };
},
afterInsert: async ({ rows, session, client }) => {
  // Post-processing, notifications
  return rows;
},
beforeUpdate: async ({ rows, session, client }) => {
  // Validation, audit logging
  return { rows };
},

When to Use What

Use Prisma when:

  • ✅ You need a standalone ORM for any framework
  • ✅ You want database migrations managed by Prisma
  • ✅ You're building a REST API or GraphQL API
  • ✅ You need Prisma's ecosystem (Studio, etc.)

Use @wayvo-ai/core when:

  • ✅ You're building a Next.js application
  • ✅ You want client-side reactive state management
  • ✅ You need built-in access control
  • ✅ You want automatic form state tracking
  • ✅ You need calculated fields and complex joins
  • ✅ You want type-safe operations end-to-end

DataSource + Store vs Redux/Zustand

Conceptual Mapping

Redux/Zustand@wayvo-ai/coreNotes
useSelectoruseDBRows(), useCurrentRowSync()Reactive selectors
dispatch(action)store.setValue(), store.save()Direct state updates
ReducersDataSource hooksBusiness logic in hooks
MiddlewareLifecycle hooksbeforeInsert, afterUpdate
createSliceDataSource definitionSchema + logic together

Key Differences

1. State Management

Redux:

// Manual state management
const userSlice = createSlice({
  name: 'users',
  initialState: { users: [], loading: false },
  reducers: {
    setUsers: (state, action) => {
      state.users = action.payload;
    },
  },
});

@wayvo-ai/core:

// Automatic state management
const store = useStore<User>({
  datasourceId: 'Users',
  autoQuery: true,
});
// State managed automatically, synced with server

2. Server Sync

Redux:

// Manual API calls and state updates
const fetchUsers = async (dispatch) => {
  dispatch(setLoading(true));
  const users = await api.getUsers();
  dispatch(setUsers(users));
  dispatch(setLoading(false));
};

@wayvo-ai/core:

// Automatic server sync
const store = useStore<User>({
  datasourceId: 'Users',
  autoQuery: true, // Automatically queries on mount
});
// Store handles loading, error states, and server sync

3. Optimistic Updates

Redux:

// Manual optimistic updates
dispatch(addUserOptimistic(newUser));
try {
  await api.createUser(newUser);
  dispatch(confirmUser(newUser));
} catch (error) {
  dispatch(rollbackUser(newUser.id));
}

@wayvo-ai/core:

// Built-in optimistic updates
store.createNew({ partialRecord: newUser });
// UI updates immediately
await store.save(); // Syncs with server
// Automatically handles rollback on error

Summary: What Makes @wayvo-ai/core Different

Unique Features

  1. Unified Client-Server Model

    • Same DataSource definition works on client and server
    • Automatic type inference end-to-end
    • No manual API layer needed
  2. Built-in Access Control

    • Role-based permissions in DataSource definition
    • Automatic enforcement on all operations
    • No manual authorization checks needed
  3. Automatic State Management

    • No manual cache invalidation
    • Automatic optimistic updates
    • Built-in dirty tracking for forms
  4. Reactive by Default

    • All queries are reactive
    • UI updates automatically when data changes
    • No manual subscriptions needed
  5. Form Integration

    • Stores track form state automatically
    • Built-in dirty state tracking
    • Field-level error management

When @wayvo-ai/core Shines

  • ✅ Internal/admin applications
  • ✅ CRUD-heavy applications
  • ✅ Applications requiring role-based access control
  • ✅ Forms with complex validation
  • ✅ Real-time data updates
  • ✅ Type-safe database operations

When to Consider Alternatives

  • ❌ Public APIs or external-facing applications
  • ❌ Applications with complex caching requirements
  • ❌ Applications that need framework-agnostic solutions
  • ❌ Applications with minimal database interaction

Migration Guide

From React Query

  1. Replace useQuery with useStore + useDBRows
  2. Replace useMutation with store.save()
  3. Remove manual invalidateQueries calls
  4. Use store.setValue() instead of setQueryData

From Prisma

  1. Convert Prisma schema to DataSource definition
  2. Replace server-side queries with client-side Stores
  3. Move access control to DataSource access array
  4. Convert Prisma middleware to lifecycle hooks

From Redux/Zustand

  1. Replace state slices with DataSource definitions
  2. Replace actions with Store methods (setValue, save)
  3. Remove manual API calls - Store handles them
  4. Use Store hooks instead of selectors

Next Steps

Previous
Workflows