Unstorage
Unstorage support in Farming Labs ORM is the key-value/runtime answer for teams that want one storage layer across lightweight stores without owning a separate adapter surface for each Unstorage driver.
The supported path is a raw Unstorage storage instance created with
createStorage(...).
That makes it a good fit for:
- sessions
- verification tokens
- lightweight user/account records
- framework-owned state
- prototypes and smaller storage footprints
It is not the runtime to reach for when the workload is highly relational, join-heavy, or dependent on strong database transaction guarantees.
What this gives you
You still write the schema and storage layer once.
Then the runtime translates that layer into Unstorage operations with:
- one schema definition
- one query API
- one runtime-helper path
- one capability surface
- one normalized error surface
Create the runtime directly
import { createOrm } from "@farming-labs/orm";
import { createUnstorageDriver } from "@farming-labs/orm-unstorage";
import { createStorage } from "unstorage";
import memoryDriver from "unstorage/drivers/memory";
import { appSchema } from "./schema";
const storage = createStorage({
driver: memoryDriver(),
});
const orm = createOrm({
schema: appSchema,
driver: createUnstorageDriver({
storage,
}),
});You can use any Unstorage storage instance that exposes the standard
getItem(...), setItem(...), removeItem(...), and getKeys(...) shape.
Use the runtime helper path
If a framework or shared package wants to accept a raw Unstorage instance and normalize it later, use the runtime helpers instead:
import { createOrmFromRuntime } from "@farming-labs/orm-runtime";
const orm = await createOrmFromRuntime({
schema: appSchema,
client: storage,
});That keeps the package boundary generic instead of forcing a storage-specific branch.
Setup helpers
The setup helpers are intentionally conservative here:
pushSchema(...)is a no-opapplySchema(...)is a no-opbootstrapDatabase(...)still works and returns the ORM client after that no-op setup stage
import { bootstrapDatabase } from "@farming-labs/orm-runtime/setup";
const orm = await bootstrapDatabase({
schema: appSchema,
client: storage,
});That reflects the real backend shape: Unstorage is a generic key-value/document abstraction, not a SQL-style schema engine.
Joins, relations, and transactions
Unstorage does not have native joins.
That does not mean relation selections stop working. The ORM resolves relation branches through follow-up reads, so the same higher-level storage code can still select related records.
It is still important to be honest about the boundary:
- relation loading is fallback-based, not native-join based
orm.$driver.capabilities.supportsTransactionsisfalseorm.transaction(...)stays available as a storage-layer boundary, not as a promise of full database transaction semantics
So this runtime works well for shared package logic that needs one consistent storage contract, but it is not the best fit for workloads that are deeply relational or centered around complex join-heavy query plans.
What is supported well
- string ids
- manual numeric ids
integer()json()enumeration()bigint()decimal()- compound unique lookups and upserts
- relation selections through follow-up queries
- raw client detection
createOrmFromRuntime(...)- normalized duplicate-key errors
Important limits
- generated integer ids are not supported on Unstorage
- schema-qualified table names are not supported
- relation loading is fallback-based, not native-join based
- setup helpers do not create real tables or indexes
- non-unique filtering is key-scan based
- this runtime is not meant for highly relational or join-heavy database workloads
Local verification
The repo verifies the Unstorage runtime locally with the in-memory and
fs-lite drivers.
Run it with:
pnpm test:local:unstorageWhy it matters
This keeps Unstorage inside the same bigger ORM story:
- write your storage layer once
- keep one schema definition
- let the app choose a lightweight Unstorage-backed store
- avoid owning a separate adapter surface for every storage driver
How is this guide?