Content System Architecture
Reactive store, ContentSync coordinator, loader sandboxing, schema contract, and soft HMR without session loss.
On this page
The SveltyCMS Content System is a high-performance, reactive engine. The server tier centers on engine.server.ts (scan, reconcile, cache, watcher, CRUD), loader.server.ts (path security + worker pool), and sync-content-state.server.ts (unified ContentSync for boot, IDE watch, and Collection Builder).
π Module Layout (src/content/)
| File | Role |
|---|---|
index.ts |
Browser-safe public facade (contentSystem, SSE init) |
index.server.ts |
Server facade + ensureContentInitialized() + re-exports ContentSync |
sync-content-state.server.ts |
ContentSync β boot / compile / gui-save / watcher / collection-save / reorder |
schema-contract.ts |
Post-load schema assert (shape, duplicate fields) + optional structure fingerprint helpers |
engine.server.ts |
Scanner, reconciliation, cache invalidation, dev watcher, CRUD |
loader.server.ts |
loadSchema(), worker pool, isSafeCollectionPath(), contentRuntime |
module-worker.server.ts |
Worker-thread entry (spawned by loader pool; must stay separate) |
content-utils.ts |
Navigation tree, metrics, shared pure helpers |
content-sse.svelte.ts |
Client SSE β debounced contentSystem.refresh() |
types.ts / types.generated.ts |
Domain types + build-time collection unions (generate-content-types.ts) |
ποΈ The 5 Pillars
1. Reactive Stores (Client)
| Store | File | Role |
|---|---|---|
| Content registry | src/stores/content-registry.svelte.ts |
Global tree + schema catalog; contentVersion; waitForReload() |
| Collection store | src/stores/collection-store.svelte.ts |
Active collection, mode, form activeValue; FNV structure fingerprint; patchActiveSchema() |
| Consent store | src/stores/consent-store.svelte.ts |
GDPR prefs (localStorage) β never cleared by content HMR |
Content registry uses Svelte 5 runes as the single source of truth for the content tree. Soft HMR only runs invalidate("app:content") so consent, session cookies, and editor mode survive schema recompiles.
2. Public API Facade (Universal)
Files: src/content/index.ts (browser), src/content/index.server.ts (server)
Exposes contentSystem. Handles initialization, context (useContent), and delegates server operations to engine.server.ts. Fully integrated with the Local SDK (locals.cms).
Init coordinator β ensureContentInitialized(tenantId, options?, adapter?) in index.server.ts is the single entry point used by hooks and direct callers. One initPromises map per tenant prevents reload storms.
3. Incremental Reconciliation (Server)
File: src/content/engine.server.ts
The intelligent core. Uses mtime-based scanning + schema hashing to skip unchanged files and minimize database writes.
Unified ContentSync + refresh API
Prefer syncContentState for boot, filesystem, and GUI entry points. It compiles when needed, refreshes the engine, and returns metrics:
import { syncContentState } from "@src/content/sync-content-state.server";
import { refreshContent } from "@src/content/engine.server";
// Boot / drift heal
await syncContentState({ reason: "boot", tenantId: null, adapter: db });
// IDE edit (Vite) β single-file or fullBuild
await syncContentState({
reason: "watcher",
targetFile: "posts.ts",
fullBuild: false,
});
// Collection Builder schema save (holds GUI lock β watcher dedupe)
await syncContentState({
reason: "collection-save",
targetFile: "posts.ts",
tenantId,
});
// Engine-only modes (internal / benchmarks)
await refreshContent(tenantId, { mode: "full", adapter: db });
await refreshContent(tenantId, { mode: "schemas", adapter: db });
await refreshContent(tenantId, { mode: "incremental", changedFile: absJsPath });
| Reason | Compile | Refresh | GUI lock |
|---|---|---|---|
boot |
On drift | Full | No |
watcher |
Yes (targetFile / fullBuild) |
Incremental / batched | Skip if GUI lock |
collection-save |
Yes | Incremental / full on rename | Yes |
gui-save |
After structure ops | Structure upsert + SSE | Yes |
sidebar-reorder |
No | Schemas mode | No |
contentService.fullReload() and refreshCollectionsCache() remain exported for benchmarks and internal callers.
Vectorized Processing & API Optimization
SveltyCMS utilizes a Vectorized modifyRequest pipeline to handle large data batches:
- Scalability: Handles 10,000+ item imports without blocking the event loop.
- Memory Efficiency: Reduced object allocations by ~90% for standard fields.
- Relational Performance: Achieved a ~28% boost in relational averages (5.29ms β 3.81ms) by removing redundant processing in hot paths.
Self-Healing & Native Introspection
SveltyCMS utilizes native database introspection via the unified listSchemas SDK method to maintain parity and recover from drift:
- MongoDB (
listCollections): Native document-store command β faster than SQL simulation. - PostgreSQL/MariaDB (
information_schema): ANSI SQL standard for multi-tenant introspection. - SQLite (
sqlite_master): High-performance table discovery for file-based deployments.
4. Smart Module Processing (Server)
Files: src/content/loader.server.ts + src/content/module-worker.server.ts
Safe sandboxed parsing of compiled collection modules with widget proxy support. Backed by a native worker_threads pool (zero external dependencies).
Worker Pool Features:
- Fixed-size pool (CPU cores / 2, minimum 2)
- Automatic worker replacement on crash/timeout
- Per-task 10s timeout guard
- Idle worker cleanup after 30s (keeps 1 alive)
getModuleWorkerPool()singleton +shutdownWorkerPool()- Production default:
loadSchema()routes toloadSchemaPooled()whenNODE_ENV=production(native import in dev/test/benchmarks viacontentRuntime.useWorkerPool()) - Path hardening:
isSafeCollectionPath()enforced in main process and worker threads
Mtime cache busting: Dynamic imports use ?v={mtimeMs} instead of Date.now(), enabling ESM module cache hits on warm scans.
Dual-layer cache alignment (inlined in engine.server.ts): Schema entries use CacheCategory.SCHEMA with tags ["schema", "schema:{id}"]; navigation trees use CacheCategory.CONTENT. Full reload clears schema: via prefix invalidation; every content:update clears navigation:tree: in L1/L2.
Watcher batching: flushChangedFiles() + processBatchedIncrementalReload() collapse multi-file saves into one debounced pass with a single content:update broadcast. The dev watcher lives in engine.server.ts (startContentWatcher()).
Security Benefit: Process isolation prevents supply-chain attacks from malicious collection files.
5. Real-time Synchronization
Files:
engine.server.tsβstartContentWatcher()(dev-only, recursive nativefs.watch)src/content/content-sse.svelte.ts(client-side SSE listener)src/routes/api/[...path]/handlers/content.ts(normalizeSseEventPayload)
Development flow: A change under .compiledCollections/ triggers a debounced incremental reload via handleIncrementalReload() β no full SCHEMA cache wipe.
Production / multi-user flow:
- Server reconciles and broadcasts
content:updateon the internaleventBus. - The SSE handler normalizes payloads to
{ type: "content_update", event, version, tenantId, timestamp }. - The browser client (
contentLiveSync) debounces and callscontentSystem.refresh(). contentSystem.refresh()fetchesGET /api/content-structure?action=getStructureand syncscontentRegistry.
π Data Flow & Lifecycle
End-to-end pipeline from collection definitions to sidebar and Collection Builder. Two organizational paths converge on the same runtime stores.
(code-first or GUI-written)"] end subgraph DevWatch["Vite + ContentSync"] VITE["vite.config.ts Β· sveltyCmsPlugin
watch + debounce 150ms"] SYNC["syncContentState(watcher)
GUI lock Β· targetFile Β· metrics"] COMPILE["compile()
atomic .js Β· xxhash64 Β· noOp"] HMR["svelty:content-update
structured payload β invalidate"] end subgraph Compiled["Compiled output"] JS[".compiledCollections/**/*.js"] MANIFEST[".compilation-manifest.json
compile hashes Β· collectionOrder Β· structureNodes"] end subgraph Engine["Content engine (server)"] SCAN["scanCompiledCollections()
engine.server.ts"] LOAD["loadSchema()
loader.server.ts"] RECON["fullReload / reconcile
FS paths β DB content_nodes"] WATCH["startContentWatcher()
dev incremental reload"] end subgraph Persist["Persistence"] DB[("content_nodes table
parentId Β· order Β· categories")] end subgraph UI["Client UI"] STORE["contentStore + collection-store
contentStructure"] SIDEBAR["left-sidebar β collections.svelte
tree-view.svelte"] BUILDER["config/collectionbuilder
tree-view-board"] ORDER["page.data.collectionOrder
manifest sort overrides"] end TS --> VITE VITE --> SYNC SYNC --> COMPILE SYNC --> HMR COMPILE --> JS COMPILE --> MANIFEST JS --> SCAN JS --> WATCH SCAN --> LOAD LOAD --> RECON WATCH --> RECON RECON --> DB RECON --> STORE MANIFEST --> RECON MANIFEST --> ORDER HMR --> STORE DB --> STORE STORE --> SIDEBAR STORE --> BUILDER ORDER --> SIDEBAR
Path A β Code / filesystem (e.g. config/collections/test/posts.ts)
- Developer creates folder
config/collections/test/and movesposts.tsinto it. - Vite detects the change β
syncContentState({ reason: "watcher", fullBuild })βcompile()writes atomic.compiledCollections/test/posts.jsand updates compile hashes. - Incremental refresh loads the schema via schema contract, provisions models for changed schemas only, upserts
contentStore. enrichSchemaWithMetadata()sets schema path/collection/test/posts; path-derived categories reconcile as needed.- Structured
svelty:content-updateβ clientinvalidate("app:content")whennoOpis false (no full page reload).
Filesystem limits: folder nesting defines category structure; explicit sort order is not encoded by the OS. Order comes from DB + optional collectionOrder in the manifest (set via GUI Save or sidebar drag β POST /api/collections/reorder).
Path B β GUI Collection Builder (virtual organization)
- User creates category
testin the builder and dragspostsunder it, then clicks Save. saveContentStructure()persistsmove/createops tocontent_nodes(no destructivefullReload).setOrganizationalManifest()writescollectionOrder+structureNodes(GUI categories withsource: "builder") to.compilation-manifest.json.contentStoreupdates in memory;invalidate("app:content")reloads layout data for sidebar and builder.
GUI advantages: arbitrary category order, collection order, and parent links without moving .ts files. The schema file may stay at config/collections/posts.ts while the UI shows it under test.
Is code vs GUI fully dynamic?
| Capability | Code / filesystem | GUI Collection Builder |
|---|---|---|
| Collection schema on disk | Required β .ts is definition truth |
Written when saving a new collection; drag-reorder does not relocate existing files |
| Subfolder β category | Automatic from path | Virtual category in DB + structureNodes |
| Compiled JS mirror path | Yes β test/posts.ts β test/posts.js |
Unchanged unless the .ts file path changes |
| Manifest compile hashes | Updated on every compile | Preserved across compiles |
Manifest collectionOrder |
Applied on reconcile; optional via sidebar API | Written explicitly on Save |
Manifest structureNodes |
GUI categories only | Written on Save |
| Sidebar + builder refresh | ContentSync watcher + structured HMR | collection-save / gui-save + invalidate (no full reload) |
Summary: Both paths are live and reactive for UI updates. They are not identical:
- Filesystem owns collection existence and path-derived folders.
- GUI owns fine-grained order and virtual categories without OS constraints.
- Dragging in the builder updates DB + manifest, not the on-disk folder layout. Moving files on disk updates compile output + path-derived categories, not GUI-only virtual folders unless reconciled from manifest.
For identical behavior, keep filesystem folders aligned with GUI categories, or treat GUI layout as the display layer and files as the schema layer.
π Performance Architecture
The 2026 update introduced a massive architectural leap in content processing. Measured scan and reload latencies are documented in Performance Benchmarks.
π§΅ 1. Worker Thread Pooling
Module parsing and widget proxy creation run in a dedicated pool of background workers (module-worker.server.ts, managed by loader.server.ts). The main UI thread stays responsive even when thousands of schemas are processed simultaneously.
π³ 2. Persistent Mtime Tree (Dirty Bits)
The scanner maintains a persistent, in-memory βDirty Bitβ tree:
- Steady State: If no files changed, the scanner skips unnecessary work and returns the cached schema map instantly.
- Micro-Surgical Invalidation: When a file changes, only that node is marked dirty.
π¦ 3. Batch Cache Retrieval (getMany)
Individual cache lookups are replaced by Vectorized Batching. The system fetches all schema metadata blocks in a single O(1) trip to Redis/Memory via CacheService.getMany.
π’ 4. Fast Deterministic Hashing
Lightweight numeric string hash in loader.server.ts (generateSchemaHash). Reduces reconciliation time for unchanged schemas by ~40%.
π‘οΈ Schema Validation & Path Hardening
Schema contract (schema-contract.ts β used by loader.server.ts):
- Normalizes module export β schema object; fills
name/_idfrom path when missing - Hard-fail: non-object, missing
fieldsarray, duplicatedb_fieldName - Soft: empty
fields(draft) loads with a debug note; engine may still refuse provision
Engine hard validation (validateSchemaFields in engine.server.ts):
- Rejects missing
nameor emptyfieldsbefore cache/DB - Invalid schemas are skipped β last-good runtime state retained
Path Security (isSafeCollectionPath in loader.server.ts):
- Must be within
.compiledCollections(.js) orconfig/collections(.ts) - Blocks path traversal outside allowed directories
- Enforced in both main process and worker threads
Compile lock (beginGuiCompileSession / shouldSkipWatcherSync):
- Builder writes trigger Vite; lock + cooldown suppress redundant watcher compile
- Prevents double work and racey dual refresh
π οΈ Developer Experience
- Zero full reload: Soft HMR (
invalidate("app:content")) keeps session, consent, and form mode. - Structured HMR payload:
{ reason, contentVersion, changedIds, processed, durationMs, noOp }. - Strong Typing:
types.generated.tsviascripts/generate-content-types.tsafter non-no-op compiles. - Tenant Isolation: All content operations respect the
tenantIdcontext from middleware. - Single coordinator: Vite, builder, and boot all call
syncContentStateβ no ad-hoc model loops.
Related Documentation
- Compilation Pipeline β compile, transformers, ContentSync HMR
- Collection Builder Architecture
- Collection Store Data Flow
- Performance Benchmarks
- Local SDK vs HTTP API
- Server Hooks
Tests
| Layer | Path |
|---|---|
| Unit | tests/unit/content/sync-content-state.test.ts |
| Unit | tests/unit/content/collection-save-sync.test.ts |
| Unit | tests/unit/content/schema-contract.test.ts |
| Unit | tests/unit/content/content-reconcile.test.ts |
| Integration | tests/integration/collectionbuilder/* |
| E2E | tests/e2e/routes/collection-builder/builder.spec.ts |