Boot and Initialization
How SveltyCMS boots, compiles collections on restart, and moves from setup to READY.
On this page
This page is the engine side of first run: the installer’s phases, the config it generates, the state machine that follows, and how a restart recompiles collections without moving UUIDs. The operator’s procedure — prerequisites, the six steps, verification, and recovery — is the Setup wizard guide, and install commands are Getting started.
On this page
Install and first run
Install, the setup wizard, and the first collection are documented once:
- Getting started — clone, CLI, or Docker, then the wizard
- Setup walkthrough — the short first run
- Setup wizard guide — every option and preset
- Production deployment
The rest of this page is what happens after that install: how a restart compiles collections, how collection UUIDs stay stable, and how the state machine reaches READY.
Part II: System Architecture {#body-part-ii}
Visual Overview
Complete System Lifecycle
graph TB
subgraph "First-Time Setup"
A1[Fresh Install] -->|No private.ts| A2[Vite: Setup Mode]
A2 --> A3[Create Blank Config]
A3 --> A4[Open /setup Wizard]
A4 --> A5[User: Enter DB Config]
A5 --> A6[Action: testDatabase]
A6 -->|✓ Valid| A7[User: Select Language]
A7 --> A8[Action: seedDatabase]
A8 --> A9[Write Credentials]
A8 --> A10[Seed Settings/Themes]
A8 --> A11[Create Collections]
A12[User: Admin Form] --> A13[Action: completeSetup]
A13 --> A14[Create Admin + Session]
A13 --> A15[Init Global System]
A13 --> A16[Content System Init]
A16 --> A17[READY State]
end
subgraph "Server Restart (Phased)"
B1[Server Start] -->|private.ts exists| B2[Vite: Normal Mode]
B2 --> B3[Scan Compiled Collections]
B3 --> B4{For Each Collection}
B4 --> B5{Hash Changed?}
B5 -->|No| B6[Skip - Preserve UUID]
B5 -->|Yes| B7[Recompile - Keep UUID]
B6 --> B8[First Request]
B7 --> B8
B8 --> B9[handleSystemState]
B9 --> B10[Boot Phase: SETUP]
B10 --> B11[Boot Phase: CORE (Auth/Content)]
B11 --> B13[READY State]
B13 --> B12[Boot Phase: FULL (Cache/Themes)]
B12 --> B14[WARMED State]
end
A17 -.->|Restart Server| B1
style A1 fill:#f0f0f0,stroke:#333,stroke-width:3px
style A4 fill:#e0e0e0,stroke:#333,stroke-width:2px
style A8 fill:#e0e0e0,stroke:#333,stroke-width:2px
style A13 fill:#e0e0e0,stroke:#333,stroke-width:2px
style A17 fill:#d0d0d0,stroke:#333,stroke-width:4px
style B1 fill:#f0f0f0,stroke:#333,stroke-width:3px
style B7 fill:#e0e0e0,stroke:#333,stroke-width:2px
style B14 fill:#d0d0d0,stroke:#333,stroke-width:4px
First-Time Setup Architecture
graph TB
A[Vite Startup] -->|No private.ts| B[setupWizardPlugin]
B --> C[Create Blank private.ts]
B --> D[Compile Collections]
B --> E[Open /setup in Browser]
E --> F[User: DB Config]
F --> G[Action: testDatabase]
G -->|Success| H[User: System Language]
H --> I[Action: seedDatabase]
I --> J[Write private.ts]
I --> K[Seed Settings]
I --> L[Seed Themes]
I --> M[Seed Collections]
N --> O[Action: completeSetup]
O --> P[Create Admin User]
O --> Q[Initialize Global System]
O --> R[Initialize Content System]
O --> S[Set Session Cookie]
S --> T[Redirect to CMS]
style A fill:#f0f0f0,stroke:#333,stroke-width:3px
style E fill:#e0e0e0,stroke:#333,stroke-width:2px
style I fill:#e0e0e0,stroke:#333,stroke-width:2px
style O fill:#e0e0e0,stroke:#333,stroke-width:2px
style T fill:#d0d0d0,stroke:#333,stroke-width:4px
Server Restart Architecture
graph TB
A[Vite Startup] -->|private.ts exists| B[cmsWatcherPlugin]
B --> C[Scan Compiled Collections]
C --> D{For Each Collection}
D --> E{Hash Changed?}
E -->|No| F[Skip Compilation]
E -->|Yes| G[Recompile]
F --> H[Preserve UUID]
G --> H
H --> I[First HTTP Request]
I --> J[handleSystemState]
J --> K[Boot Phase: SETUP (Connection)]
K --> L[Boot Phase: CORE (Auth/Settings/Content)]
L --> N[READY State]
N --> M[Boot Phase: FULL (Cache/Themes/Background)]
M --> P[WARMED State]
N --> Q[Content System Auto-Init]
Q --> R[Register Collections]
style A fill:#f0f0f0,stroke:#333,stroke-width:3px
style B fill:#e0e0e0,stroke:#333,stroke-width:2px
style G fill:#e0e0e0,stroke:#333,stroke-width:2px
style H fill:#d0d0d0,stroke:#333,stroke-width:3px
style N fill:#d0d0d0,stroke:#333,stroke-width:4px
First-Time Setup Flow
Phase 1: Vite Startup (Build Time) {#setup-phase1}
File: vite.config.ts
sequenceDiagram
participant Vite
participant FS as Filesystem
participant Browser
Note over Vite: Startup Check
Vite->>FS: Check config/private.ts exists?
FS-->>Vite: ❌ Not found
Note over Vite: Setup Mode Activated
Vite->>FS: Create blank private.ts
Vite->>FS: Create placeholder collections
Vite->>FS: Compile any existing collections
Note over Vite: Open Setup Wizard
Vite->>Browser: Open http://localhost:5173/setup
Note over Browser: User sees setup wizard
Created config/private.ts:
export const privateEnv = createPrivateConfig({
DB_TYPE: "mongodb",
DB_HOST: "", // ← Empty values
DB_PORT: 27017,
DB_NAME: "", // ← Empty values
DB_USER: "",
DB_PASSWORD: "",
JWT_SECRET_KEY: "", // ← Empty
ENCRYPTION_KEY: "", // ← Empty
MULTI_TENANT: false,
});
Phase 2: Server Hooks (Runtime)
File: src/hooks.server.ts
📚 For detailed middleware architecture, see Server Hooks & Middleware
flowchart TB
A[Incoming Request] --> B{handleSystemState}
B -->|System: IDLE| C{handleSetup}
C -->|Check Config| D{private.ts valid?}
D -->|No DB creds| E[isSetupComplete = false]
E --> F{Path check}
F -->|/setup| G[Allow Request]
F -->|Other paths| H[Redirect to /setup]
C -->|Check Database| I{hasAdminUsers?}
I -->|No| E
I -->|Yes| J[Allow Request - Setup Complete]
style E fill:#d0d0d0,stroke:#333,stroke-width:3px
style G fill:#e0e0e0,stroke:#333,stroke-width:2px
style J fill:#e0e0e0,stroke:#333,stroke-width:2px
Phase 3: Setup GUI Interaction
File: src/routes/setup/+page.svelte
stateDiagram-v2
[*] --> database-config: Step 1
database-config --> DatabaseTest: User fills DB form
DatabaseTest --> SystemLanguage: Test passed ✓
SystemLanguage --> SeedData: Language selected
SeedData --> AdminUser: Database seeded ✓
AdminUser --> Complete: Admin form submitted
Complete --> [*]: Redirect to CMS
DatabaseTest --> database-config: Test failed ✗
note right of DatabaseTest
POST /setup?/testDatabase
- Validates connection
- Installs drivers if needed
end note
note right of SeedData
POST /setup?/seedDatabase
- Writes private.ts
- Seeds settings/themes (background)
- Creates collections (background)
- Polling: /api/system/health
end note
note right of Complete
POST /setup?/completeSetup
- Creates admin user
- Initializes system
- Sets session cookie
end note
User completes setup wizard:
Step 1: Database Configuration
↓
Step 2: Database Test → /setup?/testDatabase
↓
Step 3: System Language Selection
↓
Step 4: Seed Data → /setup?/seedDatabase
↓
Step 5: Admin User Creation → /setup?/completeSetup
Phase 4: Database Test (Server Function)
Action: src/routes/setup/+page.server.ts (Action: testDatabase)
sequenceDiagram
participant Client as Browser
participant Setup as /setup Actions
participant PM as Package Manager
participant Mongoose
participant DB as MongoDB
Client->>Setup: POST {host, port, name, user, pass}
Note over Setup: Check Driver
Setup->>Mongoose: Try import mongoose
alt Driver Missing
Setup->>PM: Detect package manager (bun/npm/pnpm/yarn)
Setup->>PM: Install mongoose
alt Permission Error
PM-->>Setup: EACCES ✗
Setup-->>Client: {success: false, manualCommand: "..."}
else Success
PM-->>Setup: Installation complete ✓
end
end
Note over Setup: Build Connection
Setup->>Setup: buildConnectionString()
Note over Setup: Test Connection
Setup->>DB: Connect with 15s timeout
alt Success
DB-->>Setup: Connected ✓
Setup->>DB: Authenticate
DB-->>Setup: Auth OK ✓
Setup->>DB: Get database stats
DB-->>Setup: {collections: 0, dataSize: 0}
Setup-->>Client: {success: true, details: {...}}
else Failure
DB-->>Setup: Connection error
Setup-->>Client: {success: false, error: "..."}
end
POST /setup?/testDatabase
Body: {
type: "mongodb",
host: "localhost",
port: 27017,
name: "sveltycms",
user: "admin",
password: "secret123"
}
Process:
- Detects package manager (bun/npm/pnpm/yarn)
- Installs MongoDB driver if missing (
mongoose) - Builds connection string
- Tests connection with 15s timeout
- Returns detailed validation results
Response:
{
"success": true,
"message": "Connection successful",
"details": {
"authenticated": true,
"dbStats": { "collections": 0, "dataSize": 0 },
"warnings": []
}
}
Phase 5: Seed Database
Action: /setup?/seedDatabase (+page.server.ts → setup.server.ts)
Called when user clicks “Next” after successful DB test. This process now runs in the background to prevent UI timeouts and allow real-time progress tracking.
┌─────────────────────────────────────────────────────┐
│ 1. Write config/private.ts │
│ - Writes DB credentials from test step │
│ - Generates JWT_SECRET_KEY (32 bytes base64) │
│ - Generates ENCRYPTION_KEY (32 bytes base64) │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 2. Initialize database adapter (setup mode) │
│ - getSetupDatabaseAdapter(dbConfig) │
│ - Creates MongoDBAdapter │
│ - Connects to database │
│ - Calls setupAuthModels() │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 3. Seed default data (seed.ts) │
│ A. seedSettings(adapter) │
│ - Creates system_settings collection │
│ - Seeds defaultPublicSettings (53 keys) │
│ - Seeds defaultPrivateSettings (23 keys) │
│ B. seedDefaultTheme(adapter) │
│ - Creates system_themes collection │
│ - Seeds Default theme │
│ C. seedCollectionsForSetup(adapter) │
│ - Scans .compiledCollections folder │
│ - Creates collection models in MongoDB │
│ - Reports progress via setupManager │
│ - Returns firstCollection info │
└─────────────────────────────────────────────────────┘
Key Function: seedCollectionsForSetup()
// File: src/routes/setup/seed.ts
export async function seedCollectionsForSetup(dbAdapter) {
// 1. Scan filesystem for compiled collections
const { scanCompiledCollections } = await import("@src/content/collectionScanner");
const collections = await scanCompiledCollections();
// 2. Register each collection as a model
for (const schema of collections) {
await dbAdapter.collection.createModel(schema);
// ⚠️ Creates MongoDB collection: collection_<uuid>
}
// 3. Return first collection for fast redirect
return { firstCollection: { name: "Posts", path: "/Collections/Posts" } };
}
Database Collections Created:
system_settings ← Public/private settings
system_themes ← Theme configurations
system_content_structure ← Navigation/hierarchy (empty for now)
collection_<uuid_1> ← First collection (e.g., Posts)
collection_<uuid_2> ← Second collection (e.g., Names)
Phase 6: Complete Setup
Action: /setup?/completeSetup (+page.server.ts → setup.server.ts)
Called when user submits admin user form.
┌─────────────────────────────────────────────────────┐
│ 1. Parse private.ts from filesystem │
│ - Bypasses Vite's import cache │
│ - Uses regex to extract config values │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 2. Create setup database adapter │
│ - getSetupDatabaseAdapter(dbConfig) │
│ - Creates Auth instance │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 3. Create admin user + session (single transaction)│
│ - setupAuth.createUserAndSession({ │
│ username, email, password, │
│ role: 'admin', isRegistered: true │
│ }) │
│ - Inserts into auth_users collection │
│ - Creates session in auth_sessions collection │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 4. Initialize global system (db.ts) │
│ - initializeWithFreshConfig() │
│ - Reloads private.ts from filesystem │
│ - Connects global dbAdapter │
│ - Loads settings from database │
│ - Initializes ThemeManager │
│ - Initializes MediaFolder │
│ - State: IDLE → INITIALIZING → READY │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 5. Initialize Content System │
│ - contentSystem.initialize(undefined, true) │
│ - Loads collections from database │
│ - Builds content structure cache │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 6. Invalidate caches │
│ - invalidateSettingsCache() │
│ - invalidateSetupCache() │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 7. Store SMTP settings (no mail sent here) │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 8. Set session cookie │
│ - Creates httpOnly session cookie │
│ - Sets theme cookies │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 9. Return redirect path │
│ - Uses firstCollection from seed step (fast!) │
│ - Redirects to /{lang}/{firstCollection.path} │
│ - NO SERVER RESTART REQUIRED! ✨ │
└─────────────────────────────────────────────────────┘
Response (setup.server.ts):
{
"success": true,
"message": "Setup completed",
"sessionId": "…",
"sessionCookie": {
"name": "…",
"value": "…",
"attributes": { "path": "/", "httpOnly": true, "sameSite": "lax", "maxAge": 86400 }
},
"publicSettings": {
"SITE_NAME": "…",
"DEFAULT_LANGUAGE": "en",
"MULTI_TENANT": false,
"DEMO": false,
"USE_REDIS": false
},
"hardware": { "…": "host profile the run tuned itself to" },
"redirectPath": "/en/Collections/Posts"
}
Setup Wizard Internals {#setup-wizard-internals}
How the wizard is built and what it keeps in memory while it runs. The operator procedure is the Setup wizard guide.
Component Structure
src/routes/setup/
├── +page.svelte # Main orchestrator UI (Svelte 5 Runes)
├── +page.server.ts # Consolidated setup actions and DB orchestration
├── setup.remote.ts # Remote functions: test, seed, complete, email, Redis, driver install
├── database-config.svelte # Step 1: Database setup, connection test, driver install
├── admin-config.svelte # Step 2: Admin user with Strength-Validation
├── system-config.svelte # Step 3: System settings, i18n, preset carousel
├── email-config.svelte # Step 4: SMTP (optional)
└── review-config.svelte # Step 5: Review & zero-latency completion
System Hardening:
├── src/databases/db-init.ts # Topological Phased Boot engine
└── src/databases/config-state.ts # Security-masked configuration loader
State-Aware Initialization
This separation prevents the “Initialization Storm” where multiple requests try to boot the database simultaneously. The Turbo Pipeline (introduced in v0.0.8) ensures that setup checks consume zero CPU/IO cycles once the system is READY.
High-Performance Lifecycle
SveltyCMS employs a “State-Aware” initialization strategy to ensure the setup wizard is ultra-responsive while the production environment remains enterprise-grade.
sequenceDiagram
participant V as Vite / Build
participant H as hooks.server.ts
participant D as db-init.ts
participant U as User
Note over V, U: PHASE 1: FRESH INSTALL
V->>H: __SVELTY_SETUP_COMPLETE__ = false
H->>H: Select "Bootstrap Pipeline" (Minimal)
H->>U: Redirect to /setup
U->>H: POST /setup (Complete)
H->>V: Write config/private.ts
V->>V: Hot Restart / Reload
Note over V, U: PHASE 2: PRODUCTION BOOT
V->>H: __SVELTY_SETUP_COMPLETE__ = true
H->>H: Select "Full Pipeline" (Enterprise)
H->>D: Trigger ensureFullInitialization()
D->>D: Topological Phased Boot (READY)
H->>U: Serve Dashboard (Full Speed)
Note over V, U: PHASE 3: STEADY STATE
U->>H: Incoming Request
H->>H: Fast Check: __SVELTY_SETUP_COMPLETE__ (Zero I/O)
H->>H: Execute Turbo Pipeline (~0.05ms)
Pipeline latency benchmarks
Based on tests/benchmarks/hooks-performance.test.ts, the setup-aware gating achieves significantly lower latency for fresh installs:
| Pipeline | Features | Avg Latency | Context |
|---|---|---|---|
| Bootstrap | Health, Security, Compression | < 0.1ms | During /setup phase |
| Turbo | Fast-path routing, Assets | ~0.05ms | Production steady-state |
| Full | Auth, Audit, Content, SDK | ~1.2ms | Standard API operations |
On completion, nothing restarts. The completion step invalidates the settings cache, clears and reloads the private config in-process, and drops the setup cache; the wizard then hard-redirects. In development, writing config/private.ts additionally makes the Vite plugin send a full-reload to the browser.
Security Hardening
The setup process includes several enterprise-grade security features:
- Log Sanitization: Sensitive values (passwords, connection strings) are excluded from structured log output during setup operations.
- Secret Redaction: The configuration loader (
config-state.ts) masks connection strings before logging them. - Write-Mutex Protection: SQLite writes during high-pressure setup are serialized by the adapter’s single-writer mutex (
src/databases/sqlite/adapter-core.ts), so parallel seeding cannot produce “Database is locked” errors.
Setup Store State {#security-state-mgmt}
// Central state via setup-store (Svelte 5 runes)
{
wizard: {
dbConfig: {
type: 'sqlite',
host: 'config/database',
port: '',
name: 'sveltycms.db',
user: '',
password: '',
replicaUrls: []
},
adminUser: {
username: '',
email: '',
password: '',
confirmPassword: ''
},
systemSettings: {
siteName: 'SveltyCMS',
hostProd: 'https://localhost:5173',
defaultSystemLanguage: 'en',
systemLanguages: ['en', 'de'],
defaultContentLanguage: 'en',
contentLanguages: ['en', 'de'],
mediaStorageType: 'local',
mediaFolder: './mediaFolder',
preset: 'website',
passwordMinLength: 8,
timezone: 'UTC',
useRedis: false,
redisHost: 'localhost',
redisPort: '6379',
redisPassword: '',
multiTenant: false,
demoMode: false,
cfApiToken: '',
cfZoneId: '',
cfPurgeMode: 'tags'
},
emailSettings: {
smtpConfigured: false,
skipWelcomeEmail: true,
host: '',
port: '587',
user: '',
password: '',
from: '',
secure: false
},
currentStep: 0,
highestStepReached: 0,
dbTestPassed: false,
lastDbTestResult: null,
lastTestFingerprint: null,
isSeeding: false,
redisTestPassed: false
}
}
setup-store Architecture {#state-management-2}
File: src/stores/setup-store.svelte.ts
Pattern: Svelte 5 class-based store with runes
function createSetupStore() {
const wizard = $state({
dbConfig: { ... },
adminUser: { ... },
systemSettings: { ... },
emailSettings: { ... },
currentStep: 0,
highestStepReached: 0,
dbTestPassed: false,
validationErrors: {},
stepErrors: { ... },
isLoading: false,
isSubmitting: false,
});
// Methods
function load() {
// rehydrate each section from localStorage
}
function clear() {
// reset to defaults
}
function setupPersistence() {
$effect(() => {
if (!isLoaded || !storage) return;
storage.setItem(KEYS.db, JSON.stringify(wizard.dbConfig));
// … one key per section, plus step / highestStep / dbTestPassed
});
}
return {
wizard,
load,
clear,
setupPersistence
};
}
export const setupStore = createSetupStore();
Derived state is computed, never stored: stepCompleted, stepClickable (highestStepReached >= n), canProceed (per-step gate, including the Redis test), passwordRequirements, and dbConfigChangedSinceTest (a fingerprint comparison against the last successful test).
Persistence Strategy
Keys: prefix setupWizard: — dbConfig, adminUser, systemSettings, emailSettings, currentStep, highestStep, dbTestPassed.
When Data is Saved:
- Automatically on every state change (via
$effect) - Only in the browser (localStorage), and only when a storage probe succeeds
When Data is Loaded:
- On component mount, for a browser session that already carries the
sveltycms_setup_activeflag — a refresh, or navigating back in the same session - A brand-new browser session starts from defaults: the store is cleared on first entry
When Data is Cleared:
- Through the store’s
clear()method (reset to defaults) - Never on validation errors (user input is preserved)
- Not on completion — the wizard hard-redirects and the keys are left behind
Unsaved Changes Warning
Implementation (src/routes/setup/+page.svelte):
// Capture initial state on mount, after load()/clear()
let initialDataSnapshot = $state<string>("");
onMount(() => {
initialDataSnapshot = JSON.stringify(wizard);
// Warn before navigation
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (hasUnsavedChanges() && !wizard.isSubmitting) {
e.preventDefault();
}
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
});
// Check if data changed
const hasUnsavedChanges = $derived(() => {
if (!initialDataSnapshot) return false;
return JSON.stringify(wizard) !== initialDataSnapshot;
});
Behavior:
- Shows browser warning if user tries to close/refresh with unsaved changes
- Suppressed during final submission (
wizard.isSubmitting = true) - Cross-browser compatible
Validation Schemas
Located in src/utils/schemas.ts and shared with login, password reset, and SMTP configuration:
import * as v from "valibot";
const dbTypeSchema = picklist(
["mongodb", "mongodb+srv", "postgresql", "mysql", "mariadb", "sqlite"],
"Invalid database type",
);
// Database configuration — strictObject blocks extra field injection
export const dbConfigSchema = pipe(
strictObject({
type: dbTypeSchema,
host: dbHostSchema, // required, max 255
port: dbPortSchema, // optional, 1-65535
name: dbNameSchema, // required, letters/numbers/._- only
user: dbUserSchema,
password: dbPasswordSchema,
replicaUrls: optional(array(string())),
}),
check((input) => {
const requiresAuth = ["postgresql", "mysql", "mariadb", "mongodb+srv"];
return (
!requiresAuth.includes(input.type) ||
((input.user?.length ?? 0) > 0 && (input.password?.length ?? 0) > 0)
);
}, "Username and password are required for this database type."),
);
The wizard validates three schemas per step — dbConfigSchema, setupAdminSchema, systemSettingsSchema — and smtpConfigSchema covers the optional email step.
Password rules have two layers: passwordSchema (inside setupAdminSchema) requires the configured minimum length plus a letter, a number, and a special character, while Auth.validatePasswordStrength() — which runs in Auth.createUser() at completion — additionally requires an uppercase and a lowercase letter.
Dual Validation Approach
The wizard implements a two-tier validation system:
1. Client-Side Validation (Real-time)
Technology: Valibot schemas with Svelte 5 runes
Implementation:
// In each step component (e.g., database-config.svelte)
import { safeParse } from "valibot";
import { dbConfigSchema } from "@utils/schemas";
// Real-time validation state
let localValidationErrors = $state<Record<string, string>>({});
// Validate form data in real-time
const validationResult = $derived(
safeParse(dbConfigSchema, {
type: dbConfig.type,
host: dbConfig.host,
port: dbConfig.port,
name: dbConfig.name,
user: dbConfig.user,
password: dbConfig.password,
}),
);
const isFormValid = $derived(validationResult.success);
// Update errors when validation changes
$effect(() => {
const newErrors: Record<string, string> = {};
if (!validationResult.success) {
for (const issue of validationResult.issues) {
const path = issue.path?.[0]?.key as string;
if (path) {
newErrors[path] = issue.message;
}
}
}
localValidationErrors = newErrors;
});
// Combine local and parent validation errors
const displayErrors = $derived<Record<string, string>>({
...localValidationErrors,
...validationErrors, // Server errors take precedence
});
Benefits:
- Instant feedback as users type
- No server round-trip needed
- Reduces form submission errors
- Better user experience
The step components only surface a local error once the field has been touched (blurred), then let parent/server errors win — see touchedFields in admin-config.svelte.
2. Server-Side Validation
Location: the store (src/stores/setup-store.svelte.ts)
Implementation: steps 0-2 map to a schema; the validator clears only the errors for the fields it is about to check, then merges the new ones, so unrelated errors survive:
const validationMap: Record<number, { schema: unknown; data: () => unknown }> = {
0: { schema: dbConfigSchema, data: () => wizard.dbConfig },
1: { schema: setupAdminSchema, data: () => wizard.adminUser },
2: { schema: systemSettingsSchema, data: () => wizard.systemSettings },
// Steps 3 (Email) and 4 (Review) have no validation and are treated as valid
};
function validateStep(step: number, mutateErrors = true): boolean {
const validationEntry = validationMap[step];
if (!validationEntry) return true;
const { schema, data: getData } = validationEntry;
const result = safeParse(schema, getData());
// … delete the errors for the validated keys, then merge the new issues
}
Benefits:
- Final validation before API calls
- Server-authoritative (client can be bypassed)
- Consistent with API validation
- Security checkpoint
Error Display Pattern
Each input field follows this pattern (the shared <Input> component implements the same contract):
<input
id="db-host"
bind:value={dbConfig.host}
type="text"
class="w-full rounded border border-surface-300 px-3 py-2 {displayErrors.host ? 'border-error-500' : ''}"
aria-invalid={!!displayErrors.host}
aria-describedby={displayErrors.host ? 'db-host-error' : undefined}
/>
{#if displayErrors.host}
<div id="db-host-error" class="mt-1 text-xs text-error-500" role="alert">
{displayErrors.host}
</div>
{/if}
Accessibility Features:
aria-invalidindicates validation statearia-describedbylinks to error messagerole="alert"announces errors to screen readers- Visual red border for quick identification
Error Recovery Strategies
Retrying a failed test {#auto-retry}
testDatabaseConnection() calls the remote function exactly once and surfaces the result — there is no client-side retry loop. On failure the step renders the classification, the hint, and the detail block; the retry is the user pressing Test Connection again.
// src/stores/setup-store.svelte.ts — single call, no retry loop
const data = await testDbRemote({
configData: wizard.dbConfig,
createIfMissing,
allowOverwrite,
// the wizard owns this DB once a test has succeeded for this exact config
ownsDatabase: wizard.dbTestPassed && !dbConfigChangedSinceTest,
});
wizard.lastDbTestResult = data;
if (data?.success) {
wizard.dbTestPassed = true;
wizard.lastTestFingerprint = dbConfigFingerprint;
}
Graceful Degradation
Completion keeps going when a side effect fails. SMTP values are written to settings but no mail is sent at completion; the Website Starter plugin auto-enable, the session-cache priming, and the first-collection cache lookup are each best-effort and log a warning instead of failing the run. A failed completion itself returns success: false with a message, leaving the wizard in place with the entered data intact.
User Guidance
classifyDatabaseError() (src/routes/setup/error-classifier.ts) maps raw driver errors to a classification, a user-facing sentence, and a hint:
if (/authentication failed|bad auth|not authorized|bad login/i.test(lower)) {
return {
classification: "AUTH_FAILED",
userFriendly: "Authentication failed. Please check your username and password.",
hint: "The provided user does not have permission to access this database or the password is incorrect.",
};
}
// … CONNECTION_REFUSED (refused / DNS / timeout / not allowlisted),
// DB_NOT_FOUND, DB_ALREADY_EXISTS, CASE_MISMATCH, INVALID_CONFIG
Two flags drive the follow-up dialogs: dbDoesNotExist offers to create the database, canOverwrite offers to drop and recreate it.
DB_ALREADY_EXISTS (“Database not empty”) is only raised for a foreign database. A successful test runs ensureAuth() / ensureSystem() / … and the store then auto-seeds, so the wizard’s own database is non-empty on the next test. The guard is therefore skipped when the caller reports it already owns that exact config (ownsDatabase) — re-testing your own initialised database succeeds instead of offering to overwrite it.
Adding a New Step
- Create Step Component
<!-- src/routes/setup/my-new-step.svelte -->
<script lang="ts">
import type { MyStepData } from '@stores/setup-store.svelte';
let { stepData = $bindable(), validationErrors } = $props<{
stepData: MyStepData;
validationErrors: Record<string, string>;
}>();
</script>
<div class="fade-in">
<p class="mb-8 text-center">Step description</p>
<div class="space-y-6">
<!-- Your form fields -->
</div>
</div>
- Add Validation Schema
// src/utils/schemas.ts
export const myStepSchema = v.object({
field1: v.pipe(v.string(), v.minLength(1, "Field 1 is required")),
field2: v.pipe(v.string(), v.email("Invalid email")),
});
- Update setup-store
// src/stores/setup-store.svelte.ts
class SetupStore {
wizard = $state({
// ... existing fields
myStepData: {
field1: "",
field2: "",
},
});
}
- Register in Main Wizard — one entry per step, labelled from Paraglide messages, then the store’s
validationMapgets a case:
<!-- src/routes/setup/+page.svelte -->
<script lang="ts">
import MyNewStep from './my-new-step.svelte';
const steps = $derived([
{ label: setup_step_database(), shortDesc: setup_step_database_desc() },
// … one entry per step
]);
</script>
Extending Validation
Add Cross-field Validation:
export const adminSchema = v.object(
{
password: v.string(),
confirmPassword: v.string(),
},
[
v.forward(
v.partialCheck(
[["password"], ["confirmPassword"]],
(input) => input.password === input.confirmPassword,
"Passwords must match",
),
["confirmPassword"],
),
],
);
Testing {#validation-testing}
Unit Tests (Vitest):
import { describe, it, expect } from "vitest";
import { safeParse } from "valibot";
import { dbConfigSchema } from "@utils/schemas";
describe("dbConfigSchema", () => {
it("validates correct database config", () => {
const result = safeParse(dbConfigSchema, {
type: "mongodb",
host: "localhost",
port: "27017",
name: "test",
user: "admin",
password: "secret",
});
expect(result.success).toBe(true);
});
it("rejects empty host", () => {
const result = safeParse(dbConfigSchema, {
type: "mongodb",
host: "",
port: "27017",
name: "test",
user: "",
password: "",
});
expect(result.success).toBe(false);
expect(result.issues[0].message).toContain("Host is required");
});
});
Test Suites {#testing-2}
| Suite | Path | Covers |
|---|---|---|
| Store | tests/unit/stores/setup-store.test.ts |
Steps, persistence, password requirements |
| Utilities | tests/unit/api/setup-utils.test.ts |
Connection strings, error classification |
| Presets | tests/unit/setup/preset-collections.test.ts, tests/integration/setup-presets.test.ts |
Blueprint to collection files |
| Locales | tests/unit/setup/language-packs.test.ts, tests/unit/setup/sync-inlang-locales.test.ts |
System languages, inlang sync |
| Hooks | tests/unit/hooks/setup.test.ts |
Setup gating in the request pipeline |
| Integration | tests/integration/setup-wizard.test.ts, tests/integration/api/setup-actions.test.ts |
Real SQLite, end to end |
Run them with bun run test:unit, bun run test:integration (builds the harness first), and bun run test:e2e.
End-to-End {#automated-testing-e2e}
tests/e2e/routes/setup/setup-wizard.spec.ts drives the wizard in a browser: it starts at /, expects the redirect to /setup, clears storage, walks the steps, and waits for the post-setup redirect. It runs in the e2e-prep chain (wizard → first user → auth setup) that seeds tests/e2e/.auth/admin.json before the chromium shards start, so it is never part of a --grep group.
Wizard UI Troubleshooting
Issue: Validation Errors Not Showing
Symptoms: Input changes but no error messages appear
Cause: Missing $effect hook or incorrect error prop passing
Solution:
// Ensure $effect updates localValidationErrors
$effect(() => {
const newErrors: Record<string, string> = {};
if (!validationResult.success) {
for (const issue of validationResult.issues) {
const path = issue.path?.[0]?.key as string;
if (path) {
newErrors[path] = issue.message;
}
}
}
localValidationErrors = newErrors;
});
// Ensure displayErrors combines both sources
const displayErrors = $derived<Record<string, string>>({
...localValidationErrors,
...validationErrors,
});
Issue: Form Data Not Persisting
Symptoms: Data lost on page refresh
Cause: setupPersistence() not called, or localStorage unavailable
Solution:
onMount(() => {
// Fresh session clears; a reload in the same session loads
if (!sessionStorage.getItem('sveltycms_setup_active')) {
clearStore();
sessionStorage.setItem('sveltycms_setup_active', 'true');
} else {
loadStore();
}
// Capture initial state AFTER loading
initialDataSnapshot = JSON.stringify(wizard);
setupPersistenceFn();
});
Issue: Duplicate Submissions
Symptoms: Multiple API calls on button click
Cause: Missing isSubmitting guard
Solution:
async function completeSetup() {
// Guard at the top
if (wizard.isSubmitting) {
return false;
}
wizard.isSubmitting = true;
try {
// ... API call
} finally {
wizard.isLoading = false;
wizard.isSubmitting = false; // Always reset
}
}
Issue: Unsaved Changes Warning Not Working
Symptoms: No warning when closing browser with unsaved data
Cause: Missing beforeunload handler or incorrect change detection
Solution:
onMount(() => {
// Capture initial state after loading from localStorage
initialDataSnapshot = JSON.stringify(wizard);
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (hasUnsavedChanges() && !wizard.isSubmitting) {
e.preventDefault();
}
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
};
});
Issue: Step Navigation Broken
Symptoms: Cannot click on completed steps
Cause: stepClickable derived state not updated
Solution:
const stepClickable = $derived.by<boolean[]>(() => [
true, // Step 0 (Database) is always clickable
wizard.highestStepReached >= 1, // Step 1 (Admin) is clickable if we've reached it
wizard.highestStepReached >= 2,
wizard.highestStepReached >= 3,
wizard.highestStepReached >= 4,
]);
Server Restart Flow
Phase 1: Vite Startup (Build Time) {#phase-1-vite-startup-build-time-2}
File: vite.config.ts
┌─────────────────────────────────────────────────────┐
│ 1. Vite starts │
│ - Checks if config/private.ts exists │
│ - isSetupComplete() → true (config exists + │
│ has valid DB credentials) │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 2. cmsWatcherPlugin() activates │
│ - Initializes collection structure │
│ - Compiles collections intelligently │
│ - Sets up file watchers │
└─────────────────────────────────────────────────────┘
Phase 2: Intelligent Collection Compilation
File: src/utils/compilation/compile.ts
The compilation system uses content hashing and UUID preservation to avoid unnecessary recompilation:
┌─────────────────────────────────────────────────────┐
│ 1. Scan existing compiled collections │
│ - Builds map: path → { jsPath, uuid, hash } │
│ - Extracts UUID and hash from JS files │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 2. Scan source collections (config/collections) │
│ - Gets list of .ts files │
│ - Calculates content hash for each │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 3. Smart recompilation decision │
│ For each collection: │
│ IF hash matches existing compiled file │
│ → SKIP (no changes detected) │
│ → PRESERVE existing UUID │
│ ELSE │
│ → RECOMPILE │
│ → PRESERVE existing UUID (if found) │
│ → GENERATE new UUID (if new file) │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 4. AST transformation │
│ - Converts TypeScript to JavaScript │
│ - Adds .js extensions to imports │
│ - Injects _id (UUID) into schema │
│ - Injects __HASH__ comment │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 5. Cleanup orphaned files │
│ - Removes .js files without .ts source │
│ - Removes empty directories │
└─────────────────────────────────────────────────────┘
Example Compiled Collection:
// .compiledCollections/Collections/Posts.js
// __HASH__:a7f3e9c2d8b1f4a6e3c9d2b8f1a4e6c3
export const schema = {
_id: "550e8400-e29b-41d4-a716-446655440000", // ← UUID preserved!
name: "Posts",
icon: "bi:file-text",
fields: [
{ label: "Title", db_fieldName: "title", widget: "text" },
{ label: "Content", db_fieldName: "content", widget: "richtext" },
],
};
Phase 3: Server Initialization
File: src/hooks.server.ts + src/databases/db.ts
┌─────────────────────────────────────────────────────┐
│ handleSystemState │
│ - System state: IDLE │
│ - First request triggers: await dbInitPromise │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ initializeOnRequest() (db.ts) │
│ 1. Load private config (privateEnv) │
│ 2. Connect to database │
│ 3. Load adapters (MongoDBAdapter) │
│ 4. Setup auth models │
│ 5. Load settings from database │
│ 6. Initialize default theme │
│ 7. Initialize theme manager │
│ 8. Initialize media folder │
│ 9. Initialize virtual folders │
│ State: IDLE → INITIALIZING → READY │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ Content System auto-initializes on first access │
│ - Scans .compiledCollections │
│ - Registers collection models in database │
│ - Builds content structure cache │
└─────────────────────────────────────────────────────┘
Collection UUID Management
UUID Assignment Strategy
The Problem: Collections need stable, persistent IDs for:
- Database table names (
collection_<uuid>) - Content references (foreign keys)
- API routes
- Permissions/roles
The Solution: UUIDs are assigned once at compilation and preserved across restarts.
UUID Lifecycle
┌─────────────────────────────────────────────────────┐
│ 1. New Collection Created │
│ config/collections/Posts.ts │
│ export const schema = { name: "Posts", ... } │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 2. First Compilation │
│ - Generates new UUID │
│ - Injects into schema._id │
│ - Saves to .compiledCollections/Collections/ │
│ Posts.js │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 3. Collection Modified │
│ config/collections/Posts.ts changed │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 4. Smart Recompilation │
│ - Detects hash change │
│ - PRESERVES existing UUID from compiled file │
│ - Recompiles with same UUID │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 5. Server Restart │
│ - Reads compiled collections │
│ - UUIDs remain stable │
│ - Database tables remain consistent │
└─────────────────────────────────────────────────────┘
UUID Extraction Logic
File: src/utils/compilation/compile.ts
// Extract UUID from compiled JS file
function extractUUIDFromJs(content: string): string | null {
const match = content.match(new RegExp("_id:\\s*" + "[\"']" + "([^\"'\\s]+)" + "[\"']"));
return match ? match[1] : null;
}
// Extract hash from compiled JS file
function extractHashFromJs(content: string): string | null {
const match = content.match(/__HASH__:(\w+)/);
return match ? match[1] : null;
}
AST Transformation:
// Injects UUID into schema during compilation
const schemaUuidTransformer =
(uuid: string): ts.TransformerFactory<ts.SourceFile> =>
(context) =>
(sourceFile) => {
// Finds: export const schema = { ... }
// Injects: _id: "uuid-here"
// Result: export const schema = { _id: "uuid-here", ... }
};
Key Components
1. Configuration Files
| File | Purpose | Created When |
|---|---|---|
config/private.ts |
Database credentials, security keys | Vite startup (empty) → Seed step (populated) |
config/collections/*.ts |
Collection definitions (TypeScript) | User-created |
.compiledCollections/**/*.js |
Compiled collections (JavaScript + UUID) | Build time |
2. API Endpoints
| Endpoint | Purpose | Called When |
|---|---|---|
/setup?/testDatabase |
Validates DB connection | Setup wizard (Step 2) |
/setup?/seedDatabase |
Seeds settings, themes, collections | Setup wizard (Step 4) |
/setup?/completeSetup |
Creates admin user, initializes system | Setup wizard (Step 5) |
3. Hooks & Middleware
| Hook | Purpose | Order |
|---|---|---|
handleSystemState |
Blocks requests if system FAILED | 1st |
handleSetup |
Redirects to /setup if incomplete | 2nd |
handleAuthentication |
Validates session, identifies user | 6th |
4. Core Services
| Service | Purpose | File |
|---|---|---|
dbAdapter |
Database abstraction layer | src/databases/db.ts |
contentSystem |
Public system facade | src/content/index.ts |
Auth |
Authentication/authorization | src/databases/auth/index.ts |
SettingsService |
Settings cache management | src/services/core/settings-service.ts |
State Machine
System States
IDLE → INITIALIZING → READY → DEGRADED → FAILED
↑ ↓
└──────────────────────┘
(reinit)
| State | Description | Allowed Routes |
|---|---|---|
| IDLE | No valid config or not initialized | /setup, /api/system/health, /login, /static, /.well-known |
| INITIALIZING | Loading config, connecting to DB | /api/system/health |
| READY | Fully operational | All routes |
| DEGRADED | Some services failed, but operational | All routes (with warnings) |
| FAILED | Critical failure, unusable | None (503 errors) |
Transition Triggers
// File: src/stores/system/state.svelte.ts:395 — the single state-transition entry point
export function setSystemState(state: SystemState, reason?: string): void {
const now = Date.now();
system.update((current) => {
const transition = {
from: current.overallState,
to: state,
timestamp: now,
...(reason && { reason }),
};
// Keep last 50 transitions for analysis
const stateTransitions = [...current.performanceMetrics.stateTransitions, transition].slice(
-50,
);
// …
});
}
Service-level health is reported through updateServiceHealth() (state.svelte.ts:345), and the filesystem-backed bootstrap config is (re)read by loadPrivateConfig(forceReload) (src/databases/config-state.ts:62) — not by Vite’s module graph.
Flow Diagrams
First-Time Setup (Complete Flow)
sequenceDiagram
participant V as Vite
participant H as Hooks
participant U as User (Browser)
participant API as Setup API
participant DB as Database
participant CS as Content System
V->>V: Check private.ts (missing)
V->>V: Create blank private.ts
V->>V: Compile collections
V->>U: Open /setup in browser
U->>**API Call**: `POST /setup?/testDatabase` (SvelteKit Server Action)
API->>DB: Test connection
DB-->>API: Success
API-->>U: Connection valid
U->>API: POST /setup?/seedDatabase
API->>API: Write private.ts (with credentials)
API->>DB: Seed settings
API->>DB: Seed themes
API->>DB: Seed collections (create models)
API-->>U: Seed complete + firstCollection
U->>API: POST /setup?/completeSetup
API->>DB: Create admin user + session
API->>API: initializeWithFreshConfig()
API->>CS: Initialize Content System
API->>API: Invalidate caches
API-->>U: Setup complete + redirect + session cookie
U->>H: GET /en/Collections/Posts
H->>H: handleSetup: isSetupComplete() → true
H->>CM: Load collection
CM-->>U: Render collection view
Server Restart (Smart Compilation)
sequenceDiagram
participant V as Vite
participant FS as Filesystem
participant CM as Compilation
participant DB as Database
V->>V: Check private.ts (exists + valid)
V->>CM: Trigger intelligent compilation
CM->>FS: Scan .compiledCollections/
FS-->>CM: {path → {jsPath, uuid, hash}}
CM->>FS: Scan config/collections/
FS-->>CM: List of .ts files
loop For each collection
CM->>CM: Calculate content hash
alt Hash matches
CM->>CM: SKIP (no changes)
else Hash differs
CM->>CM: RECOMPILE
CM->>CM: PRESERVE UUID
CM->>FS: Write updated .js file
end
end
CM->>V: Compilation complete
V->>DB: Initialize database connection
V->>CS: Initialize Content System
CM->>DB: Register collection models
Best Practices
For Developers
-
Never manually edit compiled collections (
.compiledCollections/)- Always edit source files (
config/collections/) - Let the compilation system handle UUID preservation
- Always edit source files (
-
Don’t delete compiled collections during development
- UUIDs would be regenerated, breaking database references
- Use
git cleancarefully
-
Use content hashing for change detection
- The system automatically detects changes via SHA-256 hash
- No manual intervention needed
-
Validate on both sides — the wizard validates in the step component and again in the store and
completeSetup(); never trust the client alone. -
Preserve user input — clear field errors, never the form.
validateStep()deletes only the errors for the fields it re-checks. -
Keep state minimal — derived values (
stepClickable,canProceed,passwordRequirements,dbConfigChangedSinceTest) are computed, not stored. -
Document new steps — update the Setup wizard guide when a step or field changes.
-
Performance first — lazy-load step components and keep step transitions off the boot path.
For System Administrators
-
Backup compiled collections before migrations
tar -czf .compiledCollections.backup.tar.gz .compiledCollections/ -
Database migrations preserve UUIDs
- Collection IDs are stored in
_idfield - Never modify
_idin database or compiled files
- Collection IDs are stored in
-
Setup completion is atomic
- If setup fails, system reverts to setup mode
- No partial state left behind
Tests and CI
How the suite and the pipeline run is documented with the tests, not here:
Part IV: Troubleshooting & Reference {#body-part-iv}
Troubleshooting
Setup wizard loops infinitely
Cause: isSetupCompleteAsync() returns false due to missing users
Fix:
# Check if admin users exist
mongo sveltycms --eval "db.auth_users.find().pretty()"
# If empty, complete setup wizard again
# or manually create admin user via MongoDB shell
Collections not appearing after setup
Cause: seedCollectionsForSetup() failed silently
Fix:
# Check logs for collection seeding errors
tail -f logs/app.log | grep "seedCollections"
# Manually trigger collection initialization
# Restart server with DEBUG=true
UUID changed after recompilation
Cause: Compiled collection was deleted or corrupted
Fix:
# Restore from backup
tar -xzf .compiledCollections.backup.tar.gz
# Or manually restore UUID in MongoDB
mongo sveltycms --eval 'db.system_content_structure.find({name: "Posts"})'
# Copy _id, then update compiled file
Summary
This page covers the engine side of first run:
✅ First-Time Setup - Installer phases, the config they generate, and what completion triggers ✅ Server Restart - Intelligent recompilation and UUID preservation ✅ Setup Wizard Internals - Store, validation layers, error classification ✅ Collection Management - UUID assignment and lifecycle ✅ State Machine - IDLE → READY, with the transition triggers ✅ Troubleshooting - Boot, collection, and wizard-UI issues
Key Highlights
🚀 Zero-Restart Setup - System becomes operational without server restart
🔑 UUID Preservation - Collections maintain stable IDs across restarts
⚡ Smart Compilation - Only recompiles changed collections using content hashing
🧪 Comprehensive Testing - Setup suites across unit, integration, and E2E
🤖 Automated CI/CD - GitHub Actions for testing and releases
📝 Automated Scripts - setup-system.ts for headless CI/CD setup
⚠️ Known Issue: Collection UUID table creation may have race conditions during seedCollectionsForSetup()
The system is production-ready and fully automated for both local development and CI/CD environments.
Related Documentation
Architecture
- Collection Store Data Flow - How data flows through the collection store
- State Management - System state machine details
- Cache System - Caching strategy and TTLs
API References
- Setup Actions - Complete setup API reference
- Collection Management - Collection CRUD operations
- Authentication & Identity - User authentication and security
Database
- Core Infrastructure - Database adapter system
- MongoDB Methods - MongoDB-specific implementation
Guides
- Boot and initialization - This document (first-run phases, restart, wizard internals)
- Troubleshooting - Common issues and solutions
Quick Reference
Key Files
| File | Purpose | When Modified |
|---|---|---|
vite.config.ts |
Build-time setup detection | Rarely |
src/hooks.server.ts |
Runtime request gatekeeper | When adding middleware |
src/databases/db.ts |
Database initialization | When adding adapters |
src/content/index.ts |
Collection management | When changing collection logic |
src/utils/compilation/compile.ts |
Smart compilation | When changing collection format |
Key Concepts
- Setup Mode: System state when no admin users exist
- UUID Preservation: Collections keep the same ID across restarts
- Smart Compilation: Only recompiles changed collections using content hashing
- Zero-Restart Setup: System becomes operational without server restart
- State Machine: IDLE → INITIALIZING → READY → DEGRADED → FAILED
Performance Benchmarks
| Operation | Expected Time | Notes |
|---|---|---|
| Vite Startup | 2-5 seconds | Includes TypeScript compilation |
| Database Test | 1-3 seconds | May include driver installation |
| Seed Database | 3-8 seconds | Depends on collection count |
| Complete Setup | 4-6 seconds | Includes full system init |
| Total Setup | 10-25 seconds | Excluding user input |
| Collection Compile | 50-200ms per file | With UUID preservation |
| Server Restart | 3-5 seconds | To READY state |