Database Methods Interface
Comprehensive reference for the IDBAdapter interface and its modular namespaces, including findPage, count modes, and shared count caching across MongoDB, PostgreSQL, MariaDB, and SQLite.
On this page
π― Architectural Vision
SveltyCMS leverages a Modular Namespace pattern to organize its database capabilities. Instead of a single monolithic adapter, functionality is divided into specialized domains. This ensures that as the system grows, the database layer remains maintainable, testable, and strictly type-safe.
Core Principle: Write once (in the application layer), deploy everywhere (MongoDB, SQL, etc.).
π οΈ Global Interface Contract
All methods in the SveltyCMS database layer follow the DatabaseResult<T> pattern. They MUST NOT throw exceptions for expected failures; instead, they return a structured object.
export type DatabaseResult<T> =
| { success: true; data: T; meta?: QueryMeta }
| { success: false; message: string; error: DatabaseError };
π‘οΈ auth.* Namespace
Responsibility: Identity, Session Management, and RBAC.
| Method | Description |
|---|---|
createUser(data, options) |
Creates a new user with encrypted password. |
getUserById(id) |
Retrieves a user by their primary key. |
getUserByEmail(email) |
Retrieves a user for login flows. |
validatePassword(userId, password) |
Securely verifies password hashes. |
createSession(userId, data) |
Generates a persistent session. |
invalidateSession(sessionId) |
Revokes a specific session. |
listUsers(options) |
Paginated retrieval of system users. |
ποΈ collection.* Namespace
Responsibility: Dynamic Schema and Model Management.
| Method | Description |
|---|---|
createModel(schema) |
Provisions tables or collections for a new content type. |
listSchemas(tenantId) |
Retrieves all registered collection definitions. |
updateModel(schema) |
Handles bi-directional schema synchronization. |
deleteModel(id) |
Drops the physical storage and definition. |
β‘ crud.* Namespace
Responsibility: High-performance, standardized data operations.
| Method | Description |
|---|---|
find(collection, criteria, options) |
Advanced filtering (fused mapQuery β no IR). |
findMany(collection, query, options) |
Multi-row read with limit/offset/fields/sort. |
findPage(collection, query, options) |
Preferred list API β limit+1 β { items, hasMore, nextCursor?, total? }. |
findOne(collection, criteria) |
Single record retrieval fast-path. |
findByIds(collection, ids, options) |
Batch PK lookup (relational widgets / loaders). |
count(collection, query, options) |
Cardinality with mode: "exact" \| "estimate" \| "auto". |
exists(collection, query, options) |
Existence check (LIMIT 1 / lean projection) β prefer over count > 0. |
insert / insertMany |
Standard and bulk create. |
update / updateMany |
Scoped updates with multi-tenant safety. |
delete / deleteMany |
Permanent or soft-deletion. |
upsert / upsertMany |
Atomic update-or-insert. |
streamMany |
Async-iterable scan for export / migration. |
findPage (shared product path)
Use for admin tables, media grids, and REST/GraphQL list connections. Avoids a default COUNT(*).
import type { FindPageResult } from "@src/databases/db-interface";
const res = await db.crud.findPage<Post>("posts", filter, {
limit: 50,
tenantId,
sort: { updatedAt: -1 },
fields: ["_id", "title", "status", "updatedAt"],
total: "none", // or "exact" | "estimate" | "auto"
});
if (res.success) {
const { items, hasMore, nextCursor, total, totalEstimated }: FindPageResult<Post> = res.data;
}
options.total |
Effect |
|---|---|
"none" (default) |
No count query β use hasMore only |
"exact" |
Always exact count (after page fetch) |
"estimate" |
Stats/metadata when unfiltered + untenanted; else exact |
"auto" |
Same eligibility as estimate |
Files: core/sql-adapter-core.ts, mongodb/crud-methods.ts, core/page-utils.ts.
count modes & short-lived cache
// Exact (billing / RBAC)
await db.crud.count("posts", { status: "publish" }, { tenantId, mode: "exact" });
// Estimate when empty + no tenant (dashboard global size)
await db.crud.count("posts", {}, { mode: "estimate" });
// Force skip L1/L2 count cache
await db.crud.count("posts", filter, { tenantId, bypassCache: true });
| Layer | Module | Role |
|---|---|---|
| Modes | core/page-utils.ts β shouldUseEstimateCount |
Shared eligibility (tenant-safe) |
| SQL estimate | SqlAdapterCore.estimateTableRows |
PG reltuples, Maria TABLE_ROWS, SQLite sqlite_stat1 |
| Mongo estimate | MongoCrudMethods.count |
estimatedDocumentCount() |
| Cache | core/count-cache.ts β createCountCachedCrud |
30s L1/L2; wired in db.ts after tenant guard |
| Invalidation | BaseAdapter.invalidateQueryCache |
Clears count:{collection}:* + tag count:{collection} |
Tenant-scoped counts never use whole-table estimates. Prefer findPage + hasMore for list UIs; use cached exact counts for badges; use estimate only for unscoped dashboards.
See Performance Architecture β Product-layer list & count.
π¦ content.* Namespace
Responsibility: CMS Workflows (Nodes, Drafts, Revisions).
| Method | Description |
|---|---|
nodes.getStructure(tenantId) |
Retrieves the hierarchical content tree. |
nodes.upsertNode(node) |
Persists structural nodes (Menus, Categories). |
drafts.create(data) |
Manages work-in-progress content nodes. |
revisions.list(contentId) |
Retrieves historical audit trail and snapshots. |
πΌοΈ media.* Namespace
Responsibility: File Metadata and Organization.
| Method | Description |
|---|---|
saveMetadata(data) |
Persists file size, dimensions, and SHA-256 hash. |
getFilesByFolder(folderId) |
Paginated retrieval of media items. |
updateMetadata(id, data) |
Updates Alt text, tags, or focal points. |
βοΈ system.* Namespace
Responsibility: Preferences, Jobs, Multi-Tenancy, and external API credentials.
| Method | Description |
|---|---|
preferences.get(key, scope) |
Retrieves User or System settings. |
preferences.set(key, value) |
Persists settings with automatic cache invalidation. |
tenants.create(data) |
Provisions a new isolated workspace. |
themes.ensure(theme) |
Idempotent theme registration during setup. |
websiteTokens.create(data, tenantId?) |
Creates a website token; returns plaintext once. |
websiteTokens.getAll(options, tenantId?) |
Paginated list; hashes scrubbed from response. |
websiteTokens.getByToken(token, tenantId?) |
Auth lookup by bearer value (hashed at adapter). |
websiteTokens.getByName(name, tenantId?) |
Resolves a token record by display name. |
websiteTokens.delete(id, tenantId?) |
Hard-deletes the credential (SQL) or permanent delete (Mongo). |
π Credential Storage (Website Tokens & API Keys)
SveltyCMS treats website tokens and sck_ API keys as credentials, not user records. The database layer enforces the same invariants on all four engines while allowing engine-native optimizations.
Shared contract (ISystemAdapter.websiteTokens)
| Invariant | Behavior |
|---|---|
| Hash at rest | Plaintext is hashed with hashCredentialSha256Hex() (src/utils/security/credential-hash.ts) before insert. |
| One-time reveal | create() returns the raw token in the response; only the digest is persisted. |
| List scrubbing | getAll() omits the token field from every row in the response. |
| Tenant scope | Optional tenantId on all methods; SDK and handlers pass request context. |
| Auth alignment | handle-authentication.ts calls getByToken(value, locals.tenantId) β same pattern as auth.getApiKey(hash, { tenantId }). |
Per-engine implementation
| Engine | Module | Storage | Soft-delete | List performance |
|---|---|---|---|---|
| MongoDB | mongodb/website-token-methods.ts |
system_website_tokens collection; strict: true schema with tenantId, isDeleted |
safeQuery + { $ne: true } via MongoCrudMethods |
crud.findPage (limit+1 hasMore) + total: "exact" (count hits 30s L1 cache) |
| PostgreSQL / MariaDB / SQLite | core/relational-system.ts |
website_tokens table via Drizzle |
Hard delete (no isDeleted column) |
Parallel select limit+1 + COUNT(*) (Promise.all); trim sentinel row |
Indexes (all SQL engines + MongoDB)
- Unique on
token(hashed value) β O(1) bearer authentication lookup - Compound
{ tenantId, name }β tenant-scoped name resolution and admin list filters
MongoDB-specific notes
- Mongoose
strict: truerequires every persisted field to be declared on the schema. Undeclared fields (e.g.tenantId) are silently stripped on insert and break tenant-scoped list queries. - Credential lookup without request tenant context uses
bypassTenantCheckonly (notbypassSafeQuery), sosafeQuerystill applies soft-delete boundaries. - Do not use
bypassSafeQueryfor routine website-token CRUD; it is reserved for other internal fast-paths documented in the MongoDB implementation guide.
SQL migration note
After pulling schema changes that add the { tenantId, name } compound index, run:
bun run db:push
π Implementation Matrix (Status 2026)
| Namespace | MongoDB | PostgreSQL | MariaDB | SQLite |
|---|---|---|---|---|
| Auth | β Production | β Production | π‘ Planned | β Production |
| CRUD | β Production | β Production | π‘ Planned | β Production |
| Content | β Production | β Production | π‘ Planned | β Production |
| Media | β Production | β Production | π‘ Planned | β Production |
| System | β Production | β Production | π‘ Planned | β Production |
| Batch | β Production | β Production | π‘ Planned | β Production |
| SCIM | β ProductionΒΉ | β ProductionΒΉ | π‘ PlannedΒΉ | β ProductionΒΉ |
ΒΉ SCIM 2.0 endpoints are production-ready (RFC 7644: Users, Groups, Bulk, filters, PATCH, Okta/Azure) but route through the auth.* adapter directly rather than a dedicated IScimAdapter namespace.
π Developer Best Practices
- Always use the Interface: Never cast
IDBAdapterto a concrete class likeSQLiteAdapterunless performing emergency maintenance. - Favor
LocalCMS: Use the SDK bridge in server files for 0ms internal latency. - Respect
tenantId: Ensure every query includes a tenant scope to maintain data isolation. For bearer auth, passlocals.tenantIdintowebsiteTokens.getByToken()when the request tenant is already resolved. - Error Checking: Always check
result.successbefore accessingresult.data.
Related Documentation
- Core Infrastructure - The internal engine and
db.tslifecycle. - Database Resilience - Error handling and recovery patterns.
- SQLite Implementation - Specific optimizations for edge deployments.
- PostgreSQL Implementation - Enterprise scaling with JSONB.