Backbone
Backbone is your slice's encrypted data layer. Your functions reach it through the SDK with a single call: no connection strings, no ORM, no migrations. Queues and SQL databases spring into existence the first time you use them. NoSQL collections and blob buckets are part of the slice's shape instead, so they have to exist before you can write to them; a write naming one the slice does not have is refused rather than creating it. Seed and inspect any of them from the CLI.
The primitives
- NoSQLJSON collections, upserted on your own _id.
- SQLPer-slice SQLite, with schemas and transactions.
- BlobsFiles and uploads, organised by bucket.
- SecretsEncrypted config, injected into your functions.
- QueuesFIFO messages that can trigger a function.
- CacheStrings with an optional TTL.
- LocksStop two workers doing the same job at once.
- RealtimeIn-slice pub/sub, fanned out over WebSocket.
Each primitive has a drift backbone <name> command group of its own, and the whole surface is in the CLI reference. Inspect your slice's data and usage any time with drift backbone status.
Calling Backbone from a function
Each SDK spells the namespace the way its language does. The same insert, six ways:
| Language | Call |
|---|---|
| Go | drift.Backbone.NoSQL.Collection("users").Insert(doc) |
| Python | drift.backbone.nosql.collection("users").insert(doc) |
| Node | await drift.backbone.nosql.collection("users").insert(doc) |
| Ruby | Drift::Backbone::Nosql.collection("users").insert(doc) |
| PHP | \Drift\Backbone\Nosql::collection("users")->insert($doc) |
| Rust | drift_sdk::backbone::nosql::collection("users").insert(json) |
Every Node Backbone call returns a promise, so await it. The examples on each primitive's page are Go; the table above translates them.
Limits you cannot declare
The quotas you buy in the slice's shape (collection and bucket sizes, queue depth, realtime connections) run inside a set of fixed caps built into the slice. These are the same on every tier and there is no key for them.
| Primitive | Fixed cap |
|---|---|
| Cache | 1 MiB per value · 10,000 entries · 64 MiB of total value bytes |
| NoSQL | list returns 100 by default, 1,000 maximum |
| SQL | 64 KiB per statement · a transaction idle for 30 s is rolled back |
| Realtime | 512 KiB per message · 128 connections per channel · 2,048 channels per slice |
| Queue triggers | polled every 500 ms · 3 attempts, then <queue>-dlq |
Names are validated on the way in, and the charset differs by what the name becomes:
| Name | Charset |
|---|---|
Collection, queue, bucket, NoSQL _key | First character alphanumeric, then alphanumerics . _ -, up to 255 bytes. |
| Blob key | The same plus /, up to 513 bytes, no ... |
| Indexed field name | First character alphanumeric or _, then alphanumerics . _ -, up to 255 bytes, no ... |
Indexed value (NoSQL _id included) | Up to 255 bytes. No charset restriction: an email address, an ISO timestamp, a path all index and filter normally. |
| Realtime channel | Alphanumerics and : . _ ~ -, up to 256 bytes. |
| Lock | Any non-empty string. |
Status codes
The primitives share one status contract, so a code tells you what went wrong without reading the message. The SDKs carry it into the error they return; the CLI prints it.
| Code | Meaning |
|---|---|
200 | Done. A read carries the value; queue push carries the message id. |
204 | Nothing to return: an empty queue on pop or peek, a successful NoSQL delete, a successful SQL commit or rollback. |
400 | Bad request: malformed JSON, a missing parameter, a name outside its charset, an unindexable filter, an unknown transaction token, or an error from SQLite. |
401 | The route is internal. A function reaching for a secret it did not declare, or calling Secret.Set, lands here. |
404 | No such key, blob, queue or lock. |
409 | The lock is held by someone else, or the write collides with a collection's unique constraint. |
413 | Too big: a collection or bucket at the size you gave it, or a SQL statement over the fixed 64 KiB cap. |
429 | Over a platform cap: cache full, queue at its depth limit, concurrent-lock cap, or too many SQL databases. |
503 | The slice is at its realtime connection cap, or draining. |
Backbone under drift atomic run
Local dev has no slice behind it. Every SDK answers Backbone calls from an in-memory store inside your own process, which behaves differently from a deployed slice in ways worth knowing before you trust a green local run:
| Local | Deployed |
|---|---|
| State lives in the process and is lost on exit. Nothing is encrypted. | Encrypted at rest with the slice's own key, and durable. |
Insert returns a key. | Insert returns an empty string. |
| No quotas, no TTL expiry, none of the caps above. | All of them enforced. |
Secrets are read from the environment; the CLI loads .env from your source directory. | Declared secrets arrive as DRIFT_SECRET_<NAME> (every language), and in req.secrets everywhere but Go. |
| SQL has no local implementation. | SQLite per database, encrypted at rest. |
Blobs live in a per-SDK scratch map, and Go's Blob.Put stores nothing at all. | Stores the bytes, encrypted. |
No realtime hub: Publish and Presence report 0. | Fans out to live subscribers. |
Anything that depends on those differences needs a deployed slice to test against.
What a snapshot captures
drift slice snapshot create archives secrets, NoSQL collections, blobs, queues and SQL databases. Cache and locks are not in the archive, because both hold data that is recomputable or short-lived by design, so restoring a snapshot gives you a cold cache and no held locks.
Blobs are captured from the same top-level bucket listing the CLI uses, so a key containing a slash is absent from the archive as well as from blob list.