Skip to content

Multi-File Schemas

As your LUMOS project grows, you’ll want to organize schemas across multiple files for better maintainability. LUMOS supports two complementary systems:

  1. JavaScript-style imports - Simple, explicit type importing
  2. Rust-style modules - Hierarchical module organization
// types.lumos - Define shared types
type UserId = PublicKey;
type Lamports = u64;
#[solana]
enum AccountStatus {
Active,
Frozen,
Closed,
}
// accounts.lumos - Import and use types
import { UserId, Lamports, AccountStatus } from "./types.lumos";
#[solana]
#[account]
struct UserAccount {
owner: UserId, // Resolves to PublicKey
balance: Lamports, // Resolves to u64
status: AccountStatus, // Imported enum
}
Terminal window
# LUMOS automatically resolves imports
lumos generate accounts.lumos
# Output includes all types from all imported files

The import system uses familiar JavaScript/TypeScript syntax:

import { Type1, Type2, Type3 } from "./relative/path.lumos";
// Single import
import { UserId } from "./types.lumos";
// Multiple imports (comma-separated)
import { UserId, Lamports, AccountStatus } from "./types.lumos";
// Multi-line imports
import {
UserId,
WalletAddress,
AccountId,
UnixTimestamp,
Lamports,
TokenAmount,
} from "./types.lumos";
Item TypeExampleImportable?
Type Aliasestype UserId = PublicKey✅ Yes
Structsstruct User { ... }✅ Yes
Enumsenum Status { ... }✅ Yes

Imports use relative paths from the current file:

project/
├── schema/
│ ├── types.lumos # Shared types
│ ├── accounts.lumos # import from "./types.lumos"
│ └── instructions/
│ └── token.lumos # import from "../types.lumos"
schema/accounts.lumos
import { UserId } from "./types.lumos";
// schema/instructions/token.lumos
import { UserId } from "../types.lumos";

Type aliases create semantic names for primitive types, making schemas self-documenting:

types.lumos
// Identity types
type UserId = PublicKey;
type WalletAddress = PublicKey;
type AccountId = PublicKey;
// Temporal types
type UnixTimestamp = i64;
type Slot = u64;
type Epoch = u64;
// Financial types
type Lamports = u64;
type TokenAmount = u64;
type BasisPoints = u16; // 1 = 0.01%, 10000 = 100%
// Collection types
type UserList = [PublicKey];
type AmountList = [u64];
// Optional types
type OptionalOwner = Option<PublicKey>;
type NullableTimestamp = Option<i64>;
accounts.lumos
import { UserId, Lamports, UnixTimestamp, AccountStatus } from "./types.lumos";
#[solana]
#[account]
struct StakeAccount {
owner: UserId, // Self-documenting: this is a user ID
staked_amount: Lamports, // Clear: this is a lamport amount
staked_at: UnixTimestamp, // Obvious: this is a timestamp
status: AccountStatus, // Imported enum
}

Type aliases are preserved in generated code:

Rust Output:

use solana_program::pubkey::Pubkey;
pub type UserId = Pubkey;
pub type Lamports = u64;
pub type UnixTimestamp = i64;
#[derive(BorshSerialize, BorshDeserialize)]
pub struct StakeAccount {
pub owner: UserId,
pub staked_amount: Lamports,
pub staked_at: UnixTimestamp,
pub status: AccountStatus,
}

TypeScript Output:

import { PublicKey } from '@solana/web3.js';
export type UserId = PublicKey;
export type Lamports = number;
export type UnixTimestamp = number;
export interface StakeAccount {
owner: UserId;
stakedAmount: Lamports;
stakedAt: UnixTimestamp;
status: AccountStatus;
}

For larger projects, use Rust-style modules with mod and use statements:

main.lumos
mod types; // Loads ./types.lumos or ./types/mod.lumos
mod accounts;
mod instructions;
#[solana]
#[account]
struct AppState {
initialized: bool,
admin: PublicKey,
}

LUMOS looks for modules in two locations:

  1. Sibling file: ./module_name.lumos
  2. Directory module: ./module_name/mod.lumos
project/
├── main.lumos # mod types; mod models;
├── types.lumos # Found as sibling file
└── models/
├── mod.lumos # Found as directory module
├── user.lumos
└── account.lumos

Create hierarchical organization:

main.lumos
mod models;
// models/mod.lumos (or models.lumos)
mod user;
mod account;
type Timestamp = i64;
// models/user.lumos
#[solana]
#[account]
struct User {
wallet: PublicKey,
username: String,
}
// models/account.lumos
#[solana]
#[account]
struct Account {
owner: PublicKey,
balance: u64,
}

Import types from other modules with use:

main.lumos
mod types;
use crate::types::UserId;
use crate::types::AccountStatus;
#[solana]
#[account]
struct Config {
admin: UserId,
status: AccountStatus,
}

Path Keywords:

  • crate:: - Start from the root module
  • super:: - Go up one level
  • self:: - Current module
models/user.lumos
use crate::types::UserId; // From root
use super::Timestamp; // From parent (models)
use self::helpers::validate; // From current module

Control type visibility with pub:

internal.lumos
// Private - only accessible within this module
struct InternalState {
secret: [u8],
}
// public.lumos
// Public - accessible from other modules
pub struct PublicConfig {
name: String,
enabled: bool,
}

Here’s a real-world multi-file project structure:

defi-protocol/
├── main.lumos # Entry point
├── types.lumos # Shared type aliases
├── accounts/
│ ├── mod.lumos # Account module
│ ├── user.lumos # User accounts
│ ├── pool.lumos # Liquidity pool accounts
│ └── vault.lumos # Vault accounts
└── instructions/
├── mod.lumos # Instruction module
├── stake.lumos # Staking instructions
└── swap.lumos # Swap instructions
// Shared type aliases for the entire project
// Identity
type UserId = PublicKey;
type PoolId = PublicKey;
type VaultId = PublicKey;
// Financial
type Lamports = u64;
type TokenAmount = u64;
type ShareAmount = u64;
type BasisPoints = u16;
// Temporal
type UnixTimestamp = i64;
// Optional
type OptionalOwner = Option<PublicKey>;
// Enums
#[solana]
enum AccountStatus {
Uninitialized,
Active,
Frozen,
Closed,
}
#[solana]
enum PoolType {
ConstantProduct,
StableSwap,
Concentrated,
}
import {
UserId,
Lamports,
TokenAmount,
UnixTimestamp,
AccountStatus,
} from "../types.lumos";
#[solana]
#[account]
struct UserAccount {
// Identity
user_id: UserId,
// Balances
sol_balance: Lamports,
token_balance: TokenAmount,
// Staking
staked_amount: TokenAmount,
rewards_earned: TokenAmount,
// Metadata
created_at: UnixTimestamp,
last_activity: UnixTimestamp,
status: AccountStatus,
}
#[solana]
#[account]
struct UserPosition {
owner: UserId,
pool_id: PublicKey,
liquidity_shares: TokenAmount,
entry_price: u64,
opened_at: UnixTimestamp,
}
import {
PoolId,
UserId,
TokenAmount,
BasisPoints,
UnixTimestamp,
AccountStatus,
PoolType,
} from "../types.lumos";
#[solana]
#[account]
struct LiquidityPool {
// Identity
pool_id: PoolId,
authority: UserId,
// Token configuration
token_a_mint: PublicKey,
token_b_mint: PublicKey,
token_a_reserve: PublicKey,
token_b_reserve: PublicKey,
// Pool state
pool_type: PoolType,
total_liquidity: TokenAmount,
// Fees
swap_fee: BasisPoints,
protocol_fee: BasisPoints,
// Metadata
created_at: UnixTimestamp,
status: AccountStatus,
}
import {
UserId,
TokenAmount,
UnixTimestamp,
} from "../types.lumos";
#[solana]
enum StakingInstruction {
// Initialize staking pool
Initialize {
authority: UserId,
reward_rate: u64,
},
// Stake tokens
Stake {
user: UserId,
amount: TokenAmount,
lock_duration: UnixTimestamp,
},
// Unstake tokens
Unstake {
user: UserId,
amount: TokenAmount,
},
// Claim rewards
ClaimRewards {
user: UserId,
},
}
Terminal window
# Generate from entry point - all imports resolved automatically
lumos generate main.lumos --rust --typescript
# Or generate specific files
lumos generate accounts/user.lumos

LUMOS detects and prevents circular imports:

a.lumos
import { TypeB } from "./b.lumos";
// b.lumos
import { TypeA } from "./a.lumos"; // ERROR: Circular dependency

Error Message:

error: Circular dependency detected
--> a.lumos:1:1
|
= note: a.lumos -> b.lumos -> a.lumos

Solution: Extract shared types to a common file:

common.lumos
type SharedType = u64;
// a.lumos
import { SharedType } from "./common.lumos";
// b.lumos
import { SharedType } from "./common.lumos";
import { NonExistent } from "./types.lumos";

Error:

error: Type 'NonExistent' not found in './types.lumos'
--> accounts.lumos:1:10
|
1 | import { NonExistent } from "./types.lumos";
| ^^^^^^^^^^^
|
= help: Available types: UserId, Lamports, AccountStatus
import { Type } from "./missing.lumos";

Error:

error: File not found: ./missing.lumos
--> accounts.lumos:1:25
|
1 | import { Type } from "./missing.lumos";
| ^^^^^^^^^^^^^^^^^

project/
├── types/ # Shared types
├── accounts/ # Account definitions
├── instructions/ # Program instructions
└── events/ # Event definitions
// ✅ Good - Self-documenting
struct Transfer {
from: WalletAddress,
to: WalletAddress,
amount: Lamports,
}
// ❌ Avoid - Requires context
struct Transfer {
from: PublicKey,
to: PublicKey,
amount: u64,
}

Each file should have a single responsibility:

  • types.lumos - Type aliases only
  • user.lumos - User-related structs
  • pool.lumos - Pool-related structs
// ✅ Good - 2-3 levels max
project/
├── types.lumos
├── accounts/
│ ├── user.lumos
│ └── pool.lumos
└── instructions.lumos
// ❌ Avoid - Too deep
project/
├── domain/
│ └── models/
│ └── accounts/
│ └── user/
│ └── profile.lumos

Define each type in exactly one place:

// types.lumos - Define here
type UserId = PublicKey;
// accounts.lumos - Import, don't redefine
import { UserId } from "./types.lumos";
// NOT: type UserId = PublicKey; // Duplication!

Terminal window
# Automatically resolves all imports
lumos generate schema.lumos
# Specify output directory
lumos generate schema.lumos --output ./generated
# Generate specific languages
lumos generate schema.lumos --lang rust,typescript,python
Terminal window
# Validates imports and type references
lumos validate schema.lumos
# Verbose output shows resolved imports
lumos validate schema.lumos --verbose
Terminal window
# Watches all imported files for changes
lumos generate schema.lumos --watch

// schema.lumos (500+ lines)
type UserId = PublicKey;
type Lamports = u64;
#[solana]
enum Status { ... }
#[solana]
struct User { ... }
#[solana]
struct Pool { ... }
// ... many more types
types.lumos
type UserId = PublicKey;
type Lamports = u64;
#[solana]
enum Status { ... }
// accounts/user.lumos
import { UserId, Status } from "../types.lumos";
#[solana]
struct User { ... }
// accounts/pool.lumos
import { Lamports } from "../types.lumos";
#[solana]
struct Pool { ... }
  1. Identify shared types - Find types used across multiple structs
  2. Extract to types.lumos - Move type aliases and common enums
  3. Split by domain - Group related structs into files
  4. Add imports - Update each file with necessary imports
  5. Validate - Run lumos validate to catch missing imports
  6. Generate - Verify output matches original