Languages
The same API in six languages. What changes between them is casing, how the return value is packed, and which dependency manifest the element carries, not what you can call.
Conventions
- Handler casing
- PascalCase
- Example
PostItems- Dependencies
- stdlib only
- Handler casing
- snake_case
- Example
post_items- Dependencies
- stdlib only
- Handler casing
- camelCase
- Example
postItems- Dependencies
- built-in APIs only
- Handler casing
- snake_case
- Example
post_items- Dependencies
- stdlib only
- Handler casing
- snake_case
- Example
post_items- Dependencies
- built-in functions only
- Handler casing
- snake_case
- Example
post_items- Dependencies
- serde_json + ureq (no TLS by default)
Casing matters only in the one-function-per-folder layout, where the CLI derives the handler name from the route. In the flat layout the name is yours; see Writing a handler.
The same handler, six ways
A GET that reads a value from cache, in every language. The return is (status, message,
payload) everywhere; Go adds its headers map.
Go
// @atomic http=get:menu auth=none
func GetMenu(req drift.Request) (int, string, any, map[string]string) {
menu, _ := drift.Backbone.Cache.Get("menu")
return 200, "OK", menu, nil
}Python
# @atomic http=get:menu auth=none
def get_menu(req):
menu = drift.backbone.cache.get("menu")
return 200, "OK", menuNode.js
// @atomic http=get:menu auth=none
async function getMenu(req) {
const menu = await drift.backbone.cache.get("menu");
return [200, "OK", menu];
}
module.exports = { getMenu };Ruby
# @atomic http=get:menu auth=none
def get_menu(req)
menu = Drift::Backbone::Cache.get("menu")
[200, "OK", menu]
endPHP
// @atomic http=get:menu auth=none
function get_menu($req) {
$menu = \Drift\Backbone\Cache::get("menu");
return [200, "OK", $menu];
}Rust
// @atomic http=get:menu auth=none
pub fn get_menu(_req: Value) -> (i64, &'static str, Value) {
// cache::get returns Option<Value>: a miss is None, not an error
let menu = drift_sdk::backbone::cache::get("menu").unwrap_or(Value::Null);
(200, "OK", menu)
}Packaging & dependencies
The packaging unit is an element: one directory, one language, one dependency manifest,
and any number of @atomic functions across flat files inside it. The flat source directly
under atomic/ is the default element; a subdirectory of atomic/ holding
annotated source is a named one. The CLI scans every file in an element for an
@atomic annotation, and files without one are ordinary package code.
-
The version is the CLI's, the manifest is mostly yours. On
deploy(and ondrift atomic fetchfor offline work) the CLI resolves dependencies once for the whole element, referencing the SDK unversioned at its latest tag. For Go it generates thego.moditself and runsgo get github.com/ondrift/cloud/sdk@latestplusgo mod tidy; import the SDK asgithub.com/ondrift/cloud/sdk/goand leave the rest alone. For Python, Node, Ruby and PHP the element's own manifest has to name the SDK, and the deploy checks before it builds and refuses with the line to add, because a manifest that exists but omits it resolves nothing and fails at the first request instead. Don't pin a version yourself in either case. -
Third-party dependencies work. Because the CLI runs your language's own resolver
(
go mod tidy,npm,pip,bundler, …), your code is not limited to the standard library: add a normal import and it's fetched at deploy. Use a real password-hashing library, a real validation crate, whatever you need. The zero-dependency rule applies to the SDK itself (our promise to you), not to your functions. - Sharing code across functions. Functions in the same element share the directory, so a helper file beside them is ordinary package code they can all call. Across elements there is no implicit sharing, because each is its own package and sibling directories aren't on the import path. For logic two elements need, publish it as a normal module and import it like any other dependency.
Local development
drift atomic run serves your function locally over HTTP, backed by an
in-memory Backbone: NoSQL, cache, queues, blobs, locks, and secrets
all work with no platform running.
In-memory state is wiped when the process stops, secrets are read from your .env file, and
outbound HTTP requests hit real endpoints. Three parts of the surface behave differently:
- Locally
- Unserved. The zero-dependency SDKs bundle no SQL engine, so
drift.Backbone.SQL(…)has nothing to talk to. - What to do
- Exercise that path against a deployed slice.
- Locally
- Every KeyAuth, JWT, Vault, Link and Pocket call errors:
deed requires a running slice (DEED_URL). - What to do
- Deploy to a slice to test a login flow. The error is deliberate, because a silent no-op would report success while storing nothing.
- Locally
- Callable but inert.
PublishandPresencereturn0, because there is no WebSocket hub without a slice. - What to do
- Safe to leave in the code path; verify recipients on a slice.
So the whole authentication flow is a deployed-slice exercise. Structure a function that needs identity so its Deed calls sit behind one seam, and the rest stays testable on your laptop.
That is the single-function loop.
drift project run is the other one
It boots the real slice runtime in Docker and serves every function plus your Canvas sites.