Domain Layer
Overview
The domain layer (@onscript/domain) contains core business logic, types, and domain models for the entire Onscript system. This is the foundation layer that all other packages depend on.
Purpose
The domain layer serves as the single source of truth for:
- Business entities and their relationships
- Data validation and transformation rules
- Type definitions used across backend and frontend
- Domain-specific error types
Technology Stack
Effect Schema is used throughout for:
- Runtime type validation
- Data encoding/decoding
- Schema composition
- Type inference for TypeScript
Domain Models
Authentication Models
User
Represents a user account in the system.
- Fields: id, email, first_name, last_name, created_at, updated_at
- Used across auth, user management, and post ownership
AuthSession
Manages user authentication sessions.
- Fields: id, user_id, token, expires_at, created_at
- Links to User for session validation
AuthToken
Stores OTP-based authentication tokens.
- Fields: id, email, provider, purpose, otp, expires_at, created_at
- Short-lived tokens for email-based authentication
- Purposes: SIGN_UP_VERIFICATION, SIGN_IN_VERIFICATION
AuthProfile
Additional authentication profile information.
- Fields: id, user_id, provider_type, provider_data
- Extends user authentication with third-party providers (Farcaster, etc.)
Content Models
Post
Represents a social media post.
- Fields: id, user_id, content, media_ids, scheduled_at, status, created_at, updated_at
- Status types: DRAFT, SCHEDULED, PUBLISHED, FAILED
- Links to User (owner)
PostPlatform
Represents a post's relationship with a specific platform.
- Fields: id, post_id, platform, platform_post_id, published_at, status, created_at
- Enables multi-platform publishing
- Tracks publish status per platform
PostStatus
Enum for post lifecycle states:
DRAFT- Post is being editedSCHEDULED- Post is scheduled for future publishPUBLISHED- Post has been successfully publishedFAILED- Post publishing failed
PostWithPlatforms
Service-level model combining Post with its Platform relationships.
- Not a database table
- Used in service layer responses
Social Integration Models
ConnectedAccount
Represents a user's connection to a social media platform.
Key Fields:
- id, user_id, platform, platform_account_id
- platform_username, platform_display_name, profile_url, avatar_url
- is_active, is_primary
- created_at, updated_at, last_connected_at, disconnected_at
Platform Types: TWITTER, FACEBOOK, INSTAGRAM, FARCASTER, BASEAPP, ZORA
Purpose: Account metadata only (no OAuth tokens)
Lifecycle: Created on OAuth connect, soft-deleted on disconnect
OAuthToken
Stores OAuth authentication credentials.
Key Fields:
- id, connected_account_id, provider, token_type, platform_account_id
- access_token, refresh_token, expires_at, scopes
- is_active, created_at, updated_at, last_used_at, revoked_at
Token Types:
USER_TOKEN- User-level token (post to user feed)PAGE_TOKEN- Page-level token (post to Facebook Pages)APP_TOKEN- Application-level token (server-to-server)
Security: access_token and refresh_token MUST be encrypted at rest
Purpose: Separate storage from account metadata
OAuthProvider
Enum for OAuth2 providers:
- Inherits from PlatformName
- Values: TWITTER, FACEBOOK, INSTAGRAM, FARCASTER, BASEAPP, ZORA
OAuthTokenType
Enum for OAuth token types:
USER_TOKENPAGE_TOKENAPP_TOKEN
OAuthError
Domain errors for OAuth operations:
OAuthTokenExpiredError- Token has expiredOAuthTokenInvalidError- Token is invalidOAuthInsufficientScopeError- Token lacks required permissions
PlatformPostResult
Result of publishing to a platform:
- Fields: platform, platform_post_id, published_at, status
- Used for tracking multi-platform publish results
PlatformPublishError
Domain errors for platform publishing:
RateLimitError- Platform rate limit exceededPermissionDeniedError- Insufficient permissionsValidationError- Invalid post content for platform
PostContent
Content to be published to platforms:
- Fields: message, media_ids, hashtags, mentions
- Used in publish operations across platforms
ConnectedAccountWithTokens
Service-level model combining ConnectedAccount with OAuth tokens.
- Combines account metadata with available tokens
- Used in service responses
- Not a database table
Storage Models
StorageFile
Represents a media file stored in the system.
- Fields: id, original_name, file_data, mime_type, user_id, created_at
- Used for post media (images, videos)
- Binary data stored in database
Analytics Models
DashboardAnalytics
Analytics data for user dashboard.
- Fields: total_posts, scheduled_posts, published_posts, engagement_rate, created_at
CreatorAnalytics
Creator-focused analytics.
- Fields: followers, engagement, growth_rate, created_at
DashboardInfoTypeString
String type for dashboard info categories.
DashboardInfo
General dashboard information.
Waitlist Models
WaitlistEntry
Represents a waitlist registration.
- Fields: id, email, created_at
Supporting Types
Pagination
Pagination parameters and metadata.
Id
Unique identifier type.
Timestamp
Unix timestamp type.
TimestampFromString
Timestamp that can be parsed from string.
TimeString
String representation of time.
Url
URL type with validation.
Models (Legacy/Refactoring)
Some models marked for potential refactoring:
- SocialPlatform
- CreatorProfile
- CoverImage
- Email, FirstName, LastName
- MediaDescription
Usage Patterns
Domain Model Creation
import { Schema } from 'effect'
import Id from './Id'
class User extends Schema.Class<User>('User')({
id: Id,
email: Schema.String,
first_name: Schema.String,
last_name: Schema.String,
created_at: Timestamp,
updated_at: Schema.NullOr(Timestamp)
}) {}
export default UserSchema Validation
import { Schema } from 'effect'
const userInput = Schema.parse(User)(rawData)
// Returns User | Validation errorsType Export Pattern
// packages/domain/src/index.ts
export { default as User } from './User'
export { default as Post } from './Post'
// ...Domain Model Relationships
User → Posts (One-to-Many)
User (1) ───────────┐
│
├── Post (1)
├── Post (2)
└── Post (3)Post → Platforms (One-to-Many)
Post (1) ───────────┐
│
├── PostPlatform (Facebook)
├── PostPlatform (Twitter)
└── PostPlatform (Instagram)User → ConnectedAccount (One-to-Many)
User (1) ──────────────┐
│
├── ConnectedAccount (Facebook)
├── ConnectedAccount (Twitter)
└── ConnectedAccount (Instagram)ConnectedAccount → OAuthToken (One-to-Many)
ConnectedAccount (1) ───────┐
│
├── OAuthToken (USER_TOKEN)
├── OAuthToken (PAGE_TOKEN_1)
└── OAuthToken (PAGE_TOKEN_2)See OAuth Integration for detailed OAuth architecture.
Design Principles
- Single Responsibility: Each model represents one business concept
- Type Safety: All models use Effect Schema for runtime validation
- Immutability: Models are immutable by default
- Composition: Complex models composed from simpler ones
- Export from index: Central exports in
index.tsfor clean imports
Migration Guidelines
When modifying domain models:
- Update the model file with new fields
- Re-export from
index.tsif it's a new model - Create database migration in backend
- Update types in
apps/backend/src/types.ts - Update API schemas if model used in endpoints
- Run typecheck:
pnpm typecheck
Dependencies
The domain layer has no external dependencies besides:
effect(core library)- All other packages depend on domain layer
This ensures domain logic is pure and can be tested in isolation.
Summary
The domain layer is the foundation of Onscript:
- Defines all business entities
- Provides type safety across the system
- Uses Effect Schema for validation
- Organized by feature (Auth, Post, Integrations, etc.)
- Minimal dependencies for maximum portability
All other packages (api, backend, api-client) build on top of these domain models.