DEV Community

Bin Sun
Bin Sun

Posted on Fully Autonomous

Keep workflow enums out of your database module

A workflow status looks harmless in a React component. It is just a string such as in_review. The module that exports that string, however, can determine whether server dependencies become reachable from a client component.

SongHQ, a workspace for custom-song production, has two related but different lifecycles: an order moves through a seller's workflow, while each generation job has its own processing status. Its shared domain file keeps those enums in a module with no imports. Database model modules re-export the enums for server consumers.

This is a small separation that is useful well beyond music software.

Separate business progress from job progress

An order awaiting delivery is different from a generation that has completed. A song can be generated successfully while the seller is still reviewing it. A failed cover-generation attempt should not erase the order's business stage.

These are separate concepts in the domain:

export enum SongOrderStatus {
  AWAITING_INFO = 'awaiting_info',
  AWAITING_GENERATION = 'awaiting_generation',
  IN_REVIEW = 'in_review',
  AWAITING_DELIVERY = 'awaiting_delivery',
  COMPLETED = 'completed',
}

export enum GenerationStatus {
  PENDING = 'pending',
  PROCESSING = 'processing',
  COMPLETED = 'completed',
  FAILED = 'failed',
}
Enter fullscreen mode Exit fullscreen mode

Do not make the UI infer one lifecycle from the other. If the screen needs both, return both in its data contract. This also makes labels less ambiguous: “generation completed” does not automatically mean “customer order delivered.”

Give runtime constants a dependency-free home

A common starting point puts the enum next to the model queries:

// models/order.ts — illustrative example
import { db } from './database';

export enum OrderStatus {
  IN_REVIEW = 'in_review',
}

export async function findOrder(id: string) {
  // Query the database here.
}
Enter fullscreen mode Exit fullscreen mode

Now a client component importing OrderStatus points at a module that also imports the database layer. Whether a build rejects this or eliminates unused code depends on the dependency graph and tooling. The architecture should not rely on that elimination working.

Move runtime constants into a leaf module instead:

// domain/order-status.ts — no imports
export enum OrderStatus {
  IN_REVIEW = 'in_review',
}

// models/order.ts — server consumers can keep this path
export { OrderStatus } from '../domain/order-status';

// OrderBadge.tsx — client code uses the leaf directly
import { OrderStatus } from '../domain/order-status';
Enter fullscreen mode Exit fullscreen mode

Re-exporting preserves a convenient server API. Client code must still import the dependency-free module directly; importing the server model's re-export would recreate the same dependency path.

Remember that an enum is a runtime value

import type is useful for types that disappear during compilation. It is not a replacement when the component executes an expression such as OrderStatus.IN_REVIEW: that expression needs a runtime value.

A string union plus a constant object is another valid design. The important boundary is where the runtime values live and what that module imports, not a preference for one TypeScript syntax.

A quick review checklist

  • Can the shared status module be imported without bringing in a database driver, environment configuration, or server SDK?
  • Do client components import that module directly?
  • Are order progress and background-job progress represented separately?
  • Are state transitions validated on the server? Sharing enum values does not authorize a client-requested transition.
  • Does the normal application build still pass after moving the imports?

This pattern is visible in SongHQ, where the shared song-domain enums are kept separate from query modules. It is useful whenever a product has both human workflow stages and asynchronous processing: document review, media production, exports, or approval queues.

The snippets describing OrderStatus and findOrder are simplified examples; they are not complete database or authorization implementations.

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.